Initial commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# frontend/src/hooks
|
||||
|
||||
## OVERVIEW
|
||||
|
||||
Custom React hooks: useStats, useSettings, useSleepSession.
|
||||
|
||||
## WHERE TO LOOK
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `useSleepSession.ts` | Session start/stop/CRUD |
|
||||
| `useStats.ts` | Stats query hooks |
|
||||
| `useSettings.ts` | App settings state |
|
||||
|
||||
## CONVENTIONS
|
||||
|
||||
- TanStack Query v5 for caching
|
||||
- React 19 hooks
|
||||
- Export typed interfaces
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const STORAGE_KEY = 'baby-sleep-settings';
|
||||
|
||||
interface Settings {
|
||||
dayStart: string;
|
||||
dayEnd: string;
|
||||
wakeupRef: string;
|
||||
bedtimeRef: string;
|
||||
statsInterval: number;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
dayStart: '07:00',
|
||||
dayEnd: '20:00',
|
||||
wakeupRef: '04:00',
|
||||
bedtimeRef: '19:00',
|
||||
statsInterval: 7,
|
||||
};
|
||||
|
||||
export function useSettings() {
|
||||
const [settings, setSettings] = useState<Settings>(() => {
|
||||
if (typeof window === 'undefined') return DEFAULT_SETTINGS;
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) };
|
||||
} catch {
|
||||
return DEFAULT_SETTINGS;
|
||||
}
|
||||
}
|
||||
return DEFAULT_SETTINGS;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
||||
}, [settings]);
|
||||
|
||||
const update = (key: keyof Settings, value: string | number) => {
|
||||
setSettings((prev) => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
return { settings, update };
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { fetchActiveSession, createSession, endSession, fetchSessions, deleteSession, updateSession } from '../api/client';
|
||||
import type { SleepSessionRead } from '../types';
|
||||
|
||||
export function useSleepSession() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const activeSessionQuery = useQuery<SleepSessionRead | null>({
|
||||
queryKey: ['activeSession'],
|
||||
queryFn: fetchActiveSession,
|
||||
refetchInterval: 30000, // Poll every 30 seconds
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: createSession,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['activeSession'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
||||
},
|
||||
});
|
||||
|
||||
const endMutation = useMutation({
|
||||
mutationFn: ({ sessionId, endTime }: { sessionId: number; endTime: string }) =>
|
||||
endSession(sessionId, { end_time: endTime }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['activeSession'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
activeSession: activeSessionQuery.data,
|
||||
isLoading: activeSessionQuery.isLoading,
|
||||
isFetching: activeSessionQuery.isFetching,
|
||||
createSession: createMutation.mutateAsync,
|
||||
endSession: endMutation.mutateAsync,
|
||||
isCreating: createMutation.isPending,
|
||||
isEnding: endMutation.isPending,
|
||||
};
|
||||
}
|
||||
|
||||
export function useSessions() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const sessionsQuery = useQuery({
|
||||
queryKey: ['sessions'],
|
||||
queryFn: () => fetchSessions(),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: deleteSession,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ sessionId, payload }: { sessionId: number; payload: any }) =>
|
||||
updateSession(sessionId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
sessions: sessionsQuery.data?.sessions || [],
|
||||
total: sessionsQuery.data?.total || 0,
|
||||
isLoading: sessionsQuery.isLoading,
|
||||
isDeleting: deleteMutation.isPending,
|
||||
deleteSession: deleteMutation.mutateAsync,
|
||||
updateSession: updateMutation.mutateAsync,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchDailySummary, fetchTimetable, fetchWakeSleepTimes } from '../api/client';
|
||||
import type { DailySummaryResponse, TimetableResponse, WakeupBedtimeResponse } from '../types';
|
||||
|
||||
export function useStats(
|
||||
from_date: string,
|
||||
to_date: string,
|
||||
day_start = '07:00',
|
||||
day_end = '20:00',
|
||||
wakeup_ref = '04:00',
|
||||
bedtime_ref = '19:00'
|
||||
) {
|
||||
const dailySummaryQuery = useQuery<DailySummaryResponse>({
|
||||
queryKey: ['dailySummary', from_date, to_date, day_start, day_end],
|
||||
queryFn: () => fetchDailySummary(from_date, to_date, day_start, day_end),
|
||||
});
|
||||
|
||||
const timetableQuery = useQuery<TimetableResponse>({
|
||||
queryKey: ['timetable', from_date, to_date, day_start, day_end],
|
||||
queryFn: () => fetchTimetable(from_date, to_date, day_start, day_end),
|
||||
});
|
||||
|
||||
const wakeupBedtimeQuery = useQuery<WakeupBedtimeResponse>({
|
||||
queryKey: ['wakeSleepTimes', from_date, to_date, wakeup_ref, bedtime_ref],
|
||||
queryFn: () => fetchWakeSleepTimes(from_date, to_date, wakeup_ref, bedtime_ref),
|
||||
});
|
||||
|
||||
return {
|
||||
dailySummary: dailySummaryQuery.data,
|
||||
timetable: timetableQuery.data,
|
||||
wakeupBedtime: wakeupBedtimeQuery.data,
|
||||
isLoading: dailySummaryQuery.isLoading || timetableQuery.isLoading || wakeupBedtimeQuery.isLoading,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user