eletrotupi / tcc/ frontend/hooks/useTrigger.queries.tsb4b88475b5701079de2ba2dcb338c6519f8ad7a5
5.1 KB | 167 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

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

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

20
// Queries
21

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

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

40
// MUTATIONS
41

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

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

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

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

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

71
      return { previous };
72
    },
73

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

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

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

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

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

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

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

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

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

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

136
// Delete a mood entry with optimistic removal
137
export const useDeleteTrigger = () => {
138
  const queryClient = useQueryClient();
139

140
  return useMutation({
141
    mutationFn: (id: string) => apiClient.deleteTrigger(id),
142

143
    onMutate: async (id) => {
144
      await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
145
      const previous = queryClient.getQueryData(triggerKeys.list());
146

147
      queryClient.setQueryData(triggerKeys.list(), (old: Trigger[] = []) =>
148
        old.filter((e) => e.id !== Number(id)),
149
      );
150

151
      return { previous };
152
    },
153

154
    onError: (_err, _id, context) => {
155
      if (context?.previous) {
156
        queryClient.setQueryData(triggerKeys.list(), context.previous);
157
      }
158
    },
159

160
    onSuccess: () => {
161
      queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
162
      setTimeout(() => {
163
        queryClient.invalidateQueries({ queryKey: insightKeys.byType("TRIGGER_PATTERN") });
164
      }, 2000);
165
    },
166
  });
167
};