eletrotupi / tcc/ frontend/hooks/useTrigger.queries.tsbb31fc84a30face6fb284d7f0c7785eab1b7ec23
5.7 KB | 186 lines Raw
1
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2

3
import { apiClient, Trigger, CreateTriggerPayload, UpdateTriggerPayload } from "@/lib/api";
4
import { insightKeys } from "@/hooks/useInsights.queries";
5
import { moodKeys } from "@/hooks/useMoodEntries.queries";
6

7
export const triggerKeys = {
8
  all: () => ["triggers"] as const,
9
  lists: () => [...triggerKeys.all(), "list"] as const,
10
  list: (filters?: TriggerFilters) =>
11
    [...triggerKeys.lists(), filters] as const,
12
  detail: (id: string) => [...triggerKeys.all(), "detail", id] as const,
13
};
14

15
interface TriggerFilters {
16
  from?: string; // ISO date
17
  to?: string; // ISO date
18
  limit?: number;
19
}
20

21
// Queries
22

23
// Fetch a paginated list of mood entries.
24
// On cold start, returns cached data instantly then revalidates.
25
export const useTriggers = (filters?: TriggerFilters) => {
26
  return useQuery({
27
    queryKey: triggerKeys.list(filters),
28
    queryFn: () => apiClient.getTriggers(filters),
29
  });
30
};
31

32
// Single entry detail
33
export const useTrigger = (id: string) => {
34
  return useQuery({
35
    queryKey: triggerKeys.detail(id),
36
    queryFn: () => apiClient.getTrigger(id),
37
    enabled: !!id,
38
  });
39
};
40

41
// MUTATIONS
42

43
// Create a new mood entry
44
// Optimistically adds to cache, rolls back on failure
45
export const useCreateTrigger = () => {
46
  const queryClient = useQueryClient();
47

48
  return useMutation({
49
    mutationFn: (payload: CreateTriggerPayload) =>
50
      apiClient.createTrigger(payload),
51

52
    // Optimistic update so that the UI reflects the new entry immediately
53
    onMutate: async (payload) => {
54
      // Cancel any in-flight refetches so they don't overwrite our optimistic update
55
      await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
56

57
      // Snapshot current cache so we can roll back
58
      const previous = queryClient.getQueryData(triggerKeys.list());
59

60
      // Inject a temporary optimistic record
61
      // TODO: might as well create a shared attrs that we're keeping it
62
      queryClient.setQueryData(triggerKeys.list(), (old: Trigger[] = []) => [
63
        {
64
          ...payload,
65
          id: `temp-${Date.now()}`,
66
          createdAt: new Date().toISOString(),
67
          _optimistic: true,
68
        },
69
        ...old,
70
      ]);
71

72
      return { previous };
73
    },
74

75
    // On error, roll back to the snapshot
76
    onError: (_err, _payload, context) => {
77
      if (context?.previous) {
78
        queryClient.setQueryData(triggerKeys.list(), context.previous);
79
      }
80
    },
81

82
    // On success, replace optimistic record with real one from server
83
    onSuccess: () => {
84
      queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
85
      // Delay to allow the async queue job to finish writing the new insight row
86
      setTimeout(() => {
87
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
88
      }, 2000);
89
    },
90
  });
91
};
92

93
// Update a trigger
94
export const useUpdateTrigger = () => {
95
  const queryClient = useQueryClient();
96

97
  return useMutation({
98
    mutationFn: ({ id, payload }: { id: string; payload: UpdateTriggerPayload }) =>
99
      apiClient.updateTrigger(id, payload),
100

101
    onMutate: async ({ id, payload }) => {
102
      await queryClient.cancelQueries({ queryKey: triggerKeys.detail(id) });
103
      await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
104

105
      const previousDetail = queryClient.getQueryData(triggerKeys.detail(id));
106
      const previousList = queryClient.getQueryData(triggerKeys.list());
107

108
      if (previousDetail) {
109
        queryClient.setQueryData(
110
          triggerKeys.detail(id),
111
          (old: Trigger) => ({ ...old, ...payload }),
112
        );
113
      }
114

115
      return { previousDetail, previousList };
116
    },
117

118
    onError: (_err, { id }, context) => {
119
      if (context?.previousDetail) {
120
        queryClient.setQueryData(triggerKeys.detail(id), context.previousDetail);
121
      }
122
      if (context?.previousList) {
123
        queryClient.setQueryData(triggerKeys.list(), context.previousList);
124
      }
125
    },
126

127
    onSuccess: (_data, { id }) => {
128
      queryClient.invalidateQueries({ queryKey: triggerKeys.detail(id) });
129
      queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
130
      setTimeout(() => {
131
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
132
      }, 2000);
133
    },
134
  });
135
};
136

137
export const useLinkMoodToTrigger = () => {
138
  const queryClient = useQueryClient();
139

140
  return useMutation({
141
    mutationFn: ({ triggerId, moodId, perceivedImpact }: {
142
      triggerId: string;
143
      moodId: string;
144
      perceivedImpact: number
145
    }) =>
146
      apiClient.linkMoodToTrigger(triggerId, { moodId: Number(moodId), perceivedImpact }),
147

148
    onSuccess: (_data, { triggerId, moodId }) => {
149
      queryClient.invalidateQueries({ queryKey: triggerKeys.detail(triggerId) });
150
      queryClient.invalidateQueries({ queryKey: moodKeys.detail(moodId) });
151
    },
152
  });
153
};
154

155
// Delete a mood entry with optimistic removal
156
export const useDeleteTrigger = () => {
157
  const queryClient = useQueryClient();
158

159
  return useMutation({
160
    mutationFn: (id: string) => apiClient.deleteTrigger(id),
161

162
    onMutate: async (id) => {
163
      await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
164
      const previous = queryClient.getQueryData(triggerKeys.list());
165

166
      queryClient.setQueryData(triggerKeys.list(), (old: Trigger[] = []) =>
167
        old.filter((e) => e.id !== Number(id)),
168
      );
169

170
      return { previous };
171
    },
172

173
    onError: (_err, _id, context) => {
174
      if (context?.previous) {
175
        queryClient.setQueryData(triggerKeys.list(), context.previous);
176
      }
177
    },
178

179
    onSuccess: () => {
180
      queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
181
      setTimeout(() => {
182
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
183
      }, 2000);
184
    },
185
  });
186
};