import { useQuery, useMutation, useQueryClient, useInfiniteQuery, } from "@tanstack/react-query"; import { apiClient, MoodEntry, CreateMoodEntryPayload } from "@/lib/api"; import { insightKeys } from "./useInsights.queries"; export const moodKeys = { all: () => ["mood-entries"] as const, lists: () => [...moodKeys.all(), "list"] as const, list: (filters?: MoodEntryFilters) => [...moodKeys.lists(), filters] as const, detail: (id: string) => [...moodKeys.all(), "detail", id] as const, }; interface MoodEntryFilters { from?: string; // ISO date to?: string; // ISO date limit?: number; } // Queries // Fetch a paginated list of mood entries. // On cold start, returns cached data instantly then revalidates. export const useMoodEntries = (filters?: MoodEntryFilters) => { return useQuery({ queryKey: moodKeys.list(filters), queryFn: () => apiClient.getMoodEntries(filters), }); }; // Infinite scroll version export const useMoodEntriesInfinite = (limit = 20) => { return useInfiniteQuery({ queryKey: [...moodKeys.lists(), "infinite"], queryFn: ({ pageParam = 1 }) => apiClient.getMoodEntries({ page: pageParam, limit }), getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, initialPageParam: 1, }); }; // Single entry detail export const useMoodEntry = (id: string) => { return useQuery({ queryKey: moodKeys.detail(id), queryFn: () => apiClient.getMoodEntry(id), enabled: !!id, }); }; // MUTATIONS // Create a new mood entry // Optimistically adds to cache, rolls back on failure export const useCreateMoodEntry = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: CreateMoodEntryPayload) => apiClient.createMoodEntry(payload), // Optimistic update so that the UI reflects the new entry immediately onMutate: async (payload) => { // Cancel any in-flight refetches so they don't overwrite our optimistic update await queryClient.cancelQueries({ queryKey: moodKeys.lists() }); // Snapshot current cache so we can roll back const previous = queryClient.getQueryData(moodKeys.list()); // Inject a temporary optimistic record // TODO: might as well create a shared attrs that we're keeping it queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => [ { ...payload, id: `temp-${Date.now()}`, createdAt: new Date().toISOString(), _optimistic: true, }, ...old, ]); return { previous }; }, // On error, roll back to the snapshot onError: (_err, _payload, context) => { if (context?.previous) { queryClient.setQueryData(moodKeys.list(), context.previous); } }, // On success, replace optimistic record with real one from server onSuccess: () => { queryClient.invalidateQueries({ queryKey: moodKeys.lists() }); // Invalidate insights, so we can force refresh setTimeout(() => { queryClient.invalidateQueries({ queryKey: insightKeys.byType("DAILY_ENERGY"), }); queryClient.invalidateQueries({ queryKey: insightKeys.byType("MOOD_TREND"), }); queryClient.invalidateQueries({ queryKey: insightKeys.byType("ENERGY_SLEEP_CORRELATION"), }); }, 2000); }, }); }; // Delete a mood entry with optimistic removal export const useDeleteMoodEntry = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiClient.deleteMoodEntry(id), onMutate: async (id) => { await queryClient.cancelQueries({ queryKey: moodKeys.lists() }); const previous = queryClient.getQueryData(moodKeys.list()); queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => old.filter((e) => e.id !== Number(id)), ); return { previous }; }, onError: (_err, _id, context) => { if (context?.previous) { queryClient.setQueryData(moodKeys.list(), context.previous); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: moodKeys.lists() }); setTimeout(() => { queryClient.invalidateQueries({ queryKey: insightKeys.byType("MOOD_TREND") }); queryClient.invalidateQueries({ queryKey: insightKeys.byType("ENERGY_SLEEP_CORRELATION") }); queryClient.invalidateQueries({ queryKey: insightKeys.byType("DAILY_ENERGY") }); }, 2000); }, }); };