eletrotupi / tcc/ frontend/hooks/useMoodEntries.queries.tsab21e9f9149ab92d732e9b48d9886be5c089080f
4.4 KB | 149 lines Raw
1
import {
2
  useQuery,
3
  useMutation,
4
  useQueryClient,
5
  useInfiniteQuery,
6
} from "@tanstack/react-query";
7

8
import { apiClient, MoodEntry, CreateMoodEntryPayload } from "@/lib/api";
9
import { insightKeys } from "./useInsights.queries";
10

11
export const moodKeys = {
12
  all: () => ["mood-entries"] as const,
13
  lists: () => [...moodKeys.all(), "list"] as const,
14
  list: (filters?: MoodEntryFilters) => [...moodKeys.lists(), filters] as const,
15
  detail: (id: string) => [...moodKeys.all(), "detail", id] as const,
16
};
17

18
interface MoodEntryFilters {
19
  from?: string; // ISO date
20
  to?: string; // ISO date
21
  limit?: number;
22
}
23

24
// Queries
25

26
// Fetch a paginated list of mood entries.
27
// On cold start, returns cached data instantly then revalidates.
28
export const useMoodEntries = (filters?: MoodEntryFilters) => {
29
  return useQuery({
30
    queryKey: moodKeys.list(filters),
31
    queryFn: () => apiClient.getMoodEntries(filters),
32
  });
33
};
34

35
// Infinite scroll version
36
export const useMoodEntriesInfinite = (limit = 20) => {
37
  return useInfiniteQuery({
38
    queryKey: [...moodKeys.lists(), "infinite"],
39
    queryFn: ({ pageParam = 1 }) =>
40
      apiClient.getMoodEntries({ page: pageParam, limit }),
41
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
42
    initialPageParam: 1,
43
  });
44
};
45

46
// Single entry detail
47
export const useMoodEntry = (id: string) => {
48
  return useQuery({
49
    queryKey: moodKeys.detail(id),
50
    queryFn: () => apiClient.getMoodEntry(id),
51
    enabled: !!id,
52
  });
53
};
54

55
// MUTATIONS
56

57
// Create a new mood entry
58
// Optimistically adds to cache, rolls back on failure
59
export const useCreateMoodEntry = () => {
60
  const queryClient = useQueryClient();
61

62
  return useMutation({
63
    mutationFn: (payload: CreateMoodEntryPayload) =>
64
      apiClient.createMoodEntry(payload),
65

66
    // Optimistic update so that the UI reflects the new entry immediately
67
    onMutate: async (payload) => {
68
      // Cancel any in-flight refetches so they don't overwrite our optimistic update
69
      await queryClient.cancelQueries({ queryKey: moodKeys.lists() });
70

71
      // Snapshot current cache so we can roll back
72
      const previous = queryClient.getQueryData(moodKeys.list());
73

74
      // Inject a temporary optimistic record
75
      // TODO: might as well create a shared attrs that we're keeping it
76
      queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) => [
77
        {
78
          ...payload,
79
          id: `temp-${Date.now()}`,
80
          createdAt: new Date().toISOString(),
81
          _optimistic: true,
82
        },
83
        ...old,
84
      ]);
85

86
      return { previous };
87
    },
88

89
    // On error, roll back to the snapshot
90
    onError: (_err, _payload, context) => {
91
      if (context?.previous) {
92
        queryClient.setQueryData(moodKeys.list(), context.previous);
93
      }
94
    },
95

96
    // On success, replace optimistic record with real one from server
97
    onSuccess: () => {
98
      queryClient.invalidateQueries({ queryKey: moodKeys.lists() });
99

100
      // Invalidate insights, so we can force refresh
101
      setTimeout(() => {
102
        queryClient.invalidateQueries({
103
          queryKey: insightKeys.byType("DAILY_ENERGY"),
104
        });
105
        queryClient.invalidateQueries({
106
          queryKey: insightKeys.byType("MOOD_TREND"),
107
        });
108
        queryClient.invalidateQueries({
109
          queryKey: insightKeys.byType("ENERGY_SLEEP_CORRELATION"),
110
        });
111
      }, 2000);
112
    },
113
  });
114
};
115

116
// Delete a mood entry with optimistic removal
117
export const useDeleteMoodEntry = () => {
118
  const queryClient = useQueryClient();
119

120
  return useMutation({
121
    mutationFn: (id: string) => apiClient.deleteMoodEntry(id),
122

123
    onMutate: async (id) => {
124
      await queryClient.cancelQueries({ queryKey: moodKeys.lists() });
125
      const previous = queryClient.getQueryData(moodKeys.list());
126

127
      queryClient.setQueryData(moodKeys.list(), (old: MoodEntry[] = []) =>
128
        old.filter((e) => e.id !== Number(id)),
129
      );
130

131
      return { previous };
132
    },
133

134
    onError: (_err, _id, context) => {
135
      if (context?.previous) {
136
        queryClient.setQueryData(moodKeys.list(), context.previous);
137
      }
138
    },
139

140
    onSuccess: () => {
141
      queryClient.invalidateQueries({ queryKey: moodKeys.lists() });
142
      setTimeout(() => {
143
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("MOOD_TREND") });
144
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("ENERGY_SLEEP_CORRELATION") });
145
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("DAILY_ENERGY") });
146
      }, 2000);
147
    },
148
  });
149
};