@farmzone/fz-template-react 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +102 -102
  2. package/bin/create.js +108 -108
  3. package/package.json +24 -24
  4. package/template/.env.example +5 -5
  5. package/template/.prettierrc +9 -9
  6. package/template/eslint.config.js +26 -26
  7. package/template/index.css +32 -32
  8. package/template/index.html +19 -19
  9. package/template/package.json +54 -55
  10. package/template/pnpm-lock.yaml +4214 -4214
  11. package/template/public/mockServiceWorker.js +349 -349
  12. package/template/src/app/App.tsx +26 -26
  13. package/template/src/app/api/api.ts +178 -178
  14. package/template/src/app/api/queries.ts +335 -326
  15. package/template/src/app/api/queryKey.ts +7 -7
  16. package/template/src/app/api/token.ts +8 -7
  17. package/template/src/app/layout/Layout.tsx +33 -33
  18. package/template/src/app/layout/ListContents.tsx +9 -9
  19. package/template/src/app/layout/ListHeader.tsx +41 -41
  20. package/template/src/app/layout/MultiTabNav.tsx +106 -101
  21. package/template/src/app/layout/Sidebar.tsx +33 -33
  22. package/template/src/app/layout/UserInfo.tsx +95 -94
  23. package/template/src/app/layout/menu.ts +79 -55
  24. package/template/src/app/layout/tabSwitchStore.ts +11 -11
  25. package/template/src/app/router/Router.tsx +56 -56
  26. package/template/src/app/store/index.ts +26 -26
  27. package/template/src/index.tsx +21 -21
  28. package/template/src/mocks/browser.ts +17 -17
  29. package/template/src/mocks/handlers.ts +43 -43
  30. package/template/src/mocks/scenarios.ts +57 -57
  31. package/template/src/pages/dashboard/index.tsx +541 -541
  32. package/template/src/pages/error/Error.tsx +29 -29
  33. package/template/src/pages/error/NotFound.tsx +27 -27
  34. package/template/src/pages/login/index.tsx +317 -317
  35. package/template/src/pages/post/PostFormModal.tsx +128 -128
  36. package/template/src/pages/post/detail/index.tsx +545 -548
  37. package/template/src/pages/post/index.tsx +266 -266
  38. package/template/src/pages/sample/SampleFormModal.tsx +188 -115
  39. package/template/src/pages/sample/detail/index.tsx +551 -400
  40. package/template/src/pages/sample/index.tsx +298 -278
  41. package/template/src/pages/sample/modal/index.tsx +308 -300
  42. package/template/src/pages/system/log/index.tsx +173 -173
  43. package/template/src/pages/user/config/columns.tsx +102 -102
  44. package/template/src/pages/user/config/schema.ts +54 -54
  45. package/template/src/pages/user/index.tsx +704 -641
  46. package/template/src/shared/components/CommentInput.tsx +243 -243
  47. package/template/src/shared/components/FilePreviewCard.tsx +71 -70
  48. package/template/src/shared/config/text.ts +27 -27
  49. package/template/src/shared/config/type.ts +40 -40
  50. package/template/src/shared/utils/format.ts +11 -11
  51. package/template/src/types/auth.ts +10 -10
  52. package/template/src/types/comment.ts +33 -33
  53. package/template/src/types/common.ts +19 -19
  54. package/template/src/types/dashboard.ts +53 -53
  55. package/template/src/types/index.ts +16 -16
  56. package/template/src/types/log.ts +21 -21
  57. package/template/src/types/post.ts +32 -32
  58. package/template/src/types/sample.ts +33 -29
  59. package/template/src/types/user.ts +51 -51
  60. package/template/src/vite-env.d.ts +10 -10
  61. package/template/tsconfig.app.json +32 -32
  62. package/template/tsconfig.json +7 -7
  63. package/template/tsconfig.node.json +26 -26
  64. package/template/vite.config.ts +13 -13
@@ -1,326 +1,335 @@
1
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
- import { toast } from "@farmzone/fz-react-ui";
3
-
4
- import { apiInstance, apiFormDataInstance } from "@/app/api/api";
5
- import {
6
- COMMENT_QUERY_KEY,
7
- DASHBOARD_QUERY_KEY,
8
- LOG_QUERY_KEY,
9
- POST_QUERY_KEY,
10
- SAMPLE_QUERY_KEY,
11
- USER_QUERY_KEY,
12
- } from "@/app/api/queryKey";
13
- import { COMMON_MESSAGES } from "@/shared/config/text";
14
- import type {
15
- LoginResponse,
16
- PageResponse,
17
- Sample,
18
- GetSamplesParams,
19
- SampleForm,
20
- Post,
21
- GetPostsParams,
22
- PostForm,
23
- Comment,
24
- CommentForm,
25
- CommentTargetType,
26
- CommentEditForm,
27
- User,
28
- GetUsersParams,
29
- UserForm,
30
- UserEditForm,
31
- ActionLog,
32
- GetLogsParams,
33
- UserDashboardResponse,
34
- } from "@/types";
35
-
36
- // --- Auth ---
37
-
38
- export const usePostLogin = () => {
39
- return useMutation({
40
- mutationFn: (params: { userId: string; password: string }) =>
41
- apiInstance.post<LoginResponse>("/auth/login", params).then((res) => res.data),
42
- onSuccess: () => {
43
- toast.success("로그인 성공", { duration: 1500 });
44
- },
45
- });
46
- };
47
-
48
- // --- Sample ---
49
-
50
- export const useGetSamples = (params: GetSamplesParams) => {
51
- return useQuery({
52
- queryKey: [SAMPLE_QUERY_KEY, "list", params],
53
- queryFn: () => apiInstance.get<PageResponse<Sample>>("/samples", { params }).then((r) => r.data),
54
- });
55
- };
56
-
57
- export const useGetSample = (id: number | null) => {
58
- return useQuery({
59
- queryKey: [SAMPLE_QUERY_KEY, "detail", id],
60
- queryFn: () => apiInstance.get<Sample>(`/samples/${id}`).then((r) => r.data),
61
- enabled: id !== null,
62
- });
63
- };
64
-
65
- export const usePostSample = () => {
66
- const queryClient = useQueryClient();
67
- return useMutation({
68
- mutationFn: (data: SampleForm) => apiInstance.post<Sample>("/samples", data).then((r) => r.data),
69
- onSuccess: () => {
70
- void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
71
- toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
72
- },
73
- });
74
- };
75
-
76
- export const usePutSample = () => {
77
- const queryClient = useQueryClient();
78
- return useMutation({
79
- mutationFn: ({ id, data }: { id: number; data: SampleForm }) =>
80
- apiInstance.put<Sample>(`/samples/${id}`, data).then((r) => r.data),
81
- onSuccess: () => {
82
- void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
83
- toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
84
- },
85
- });
86
- };
87
-
88
- export const useDeleteSample = () => {
89
- const queryClient = useQueryClient();
90
- return useMutation({
91
- mutationFn: (id: number) => apiInstance.delete(`/samples/${id}`),
92
- onSuccess: () => {
93
- void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
94
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
95
- },
96
- });
97
- };
98
-
99
- export const useDeleteSamples = () => {
100
- const queryClient = useQueryClient();
101
- return useMutation({
102
- mutationFn: (ids: Array<number>) =>
103
- apiInstance.delete("/samples", {
104
- params: { ids },
105
- paramsSerializer: { indexes: null },
106
- }),
107
- onSuccess: () => {
108
- void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
109
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
110
- },
111
- });
112
- };
113
-
114
- // --- Post ---
115
-
116
- export const useGetPosts = (params: GetPostsParams) => {
117
- return useQuery({
118
- queryKey: [POST_QUERY_KEY, "list", params],
119
- queryFn: () => apiInstance.get<PageResponse<Post>>("/posts", { params }).then((r) => r.data),
120
- });
121
- };
122
-
123
- export const useGetPost = (id: number | null) => {
124
- return useQuery({
125
- queryKey: [POST_QUERY_KEY, "detail", id],
126
- queryFn: () => apiInstance.get<Post>(`/posts/${id}`).then((r) => r.data),
127
- enabled: id !== null,
128
- });
129
- };
130
-
131
- export const usePostPost = () => {
132
- const queryClient = useQueryClient();
133
- return useMutation({
134
- mutationFn: (data: PostForm) => apiInstance.post<Post>("/posts", data).then((r) => r.data),
135
- onSuccess: () => {
136
- void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
137
- toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
138
- },
139
- });
140
- };
141
-
142
- export const usePutPost = () => {
143
- const queryClient = useQueryClient();
144
- return useMutation({
145
- mutationFn: ({ id, data }: { id: number; data: PostForm }) =>
146
- apiInstance.put<Post>(`/posts/${id}`, data).then((r) => r.data),
147
- onSuccess: () => {
148
- void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
149
- toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
150
- },
151
- });
152
- };
153
-
154
- export const useDeletePost = () => {
155
- const queryClient = useQueryClient();
156
- return useMutation({
157
- mutationFn: (id: number) => apiInstance.delete(`/posts/${id}`),
158
- onSuccess: () => {
159
- void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
160
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
161
- },
162
- });
163
- };
164
-
165
- export const useDeletePosts = () => {
166
- const queryClient = useQueryClient();
167
- return useMutation({
168
- mutationFn: (ids: Array<number>) =>
169
- apiInstance.delete("/posts", {
170
- params: { ids },
171
- paramsSerializer: { indexes: null },
172
- }),
173
- onSuccess: () => {
174
- void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
175
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
176
- },
177
- });
178
- };
179
-
180
- // --- Comment ---
181
-
182
- export const useGetComments = (targetType: CommentTargetType, targetId: number) => {
183
- return useQuery({
184
- queryKey: [COMMENT_QUERY_KEY, targetType, targetId],
185
- queryFn: () =>
186
- apiInstance.get<Array<Comment>>("/comments", { params: { targetType, targetId } }).then((r) => r.data),
187
- });
188
- };
189
-
190
- export const usePostComment = () => {
191
- const queryClient = useQueryClient();
192
- return useMutation({
193
- mutationFn: (data: CommentForm) => apiInstance.post<Comment>("/comments", data).then((r) => r.data),
194
- onSuccess: () => {
195
- void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
196
- toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
197
- },
198
- });
199
- };
200
-
201
- export const usePutComment = () => {
202
- const queryClient = useQueryClient();
203
- return useMutation({
204
- mutationFn: ({ commentId, data }: { commentId: number; data: CommentEditForm }) =>
205
- apiInstance.put<Comment>(`/comments/${commentId}`, data).then((r) => r.data),
206
- onSuccess: () => {
207
- void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
208
- toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
209
- },
210
- });
211
- };
212
-
213
- export const useDeleteComment = () => {
214
- const queryClient = useQueryClient();
215
- return useMutation({
216
- mutationFn: (commentId: number) => apiInstance.delete(`/comments/${commentId}`),
217
- onSuccess: () => {
218
- void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
219
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
220
- },
221
- });
222
- };
223
-
224
- // --- Sample check ---
225
-
226
- export const checkSampleNameAvailable = (name: string): Promise<boolean> =>
227
- apiInstance.get<boolean>("/samples/check/name", { params: { name } }).then((r) => r.data);
228
-
229
- // --- User ---
230
-
231
- export const checkUserIdAvailable = (userId: string): Promise<boolean> =>
232
- apiInstance.get<boolean>("/users/check/id", { params: { userId } }).then((r) => r.data);
233
-
234
- export const useGetUsers = (params: GetUsersParams) => {
235
- return useQuery({
236
- queryKey: [USER_QUERY_KEY, "list", params],
237
- queryFn: () => apiInstance.get<PageResponse<User>>("/users", { params }).then((r) => r.data),
238
- });
239
- };
240
-
241
- export const useGetUser = (id: number | null) => {
242
- return useQuery({
243
- queryKey: [USER_QUERY_KEY, "detail", id],
244
- queryFn: () => apiInstance.get<User>(`/users/${id}`).then((r) => r.data),
245
- enabled: id !== null,
246
- });
247
- };
248
-
249
- export const usePostUser = () => {
250
- const queryClient = useQueryClient();
251
- return useMutation({
252
- mutationFn: async ({ data, file }: { data: UserForm; file?: File }) => {
253
- const formData = new FormData();
254
- formData.append("request", JSON.stringify(data));
255
- if (file) formData.append("files", file);
256
- return (await apiFormDataInstance.post<User>("/users", formData)).data;
257
- },
258
- onSuccess: () => {
259
- void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
260
- toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
261
- },
262
- });
263
- };
264
-
265
- export const usePutUser = () => {
266
- const queryClient = useQueryClient();
267
- return useMutation({
268
- mutationFn: async ({ id, data, file }: { id: number; data: UserEditForm; file?: File }) => {
269
- const formData = new FormData();
270
- formData.append("request", JSON.stringify(data));
271
- if (file) formData.append("files", file);
272
- return (await apiFormDataInstance.put<User>(`/users/${id}`, formData)).data;
273
- },
274
- onSuccess: () => {
275
- void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
276
- toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
277
- },
278
- });
279
- };
280
-
281
- export const useDeleteUser = () => {
282
- const queryClient = useQueryClient();
283
- return useMutation({
284
- mutationFn: (id: number) => apiInstance.delete(`/users/${id}`),
285
- onSuccess: () => {
286
- void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
287
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
288
- },
289
- });
290
- };
291
-
292
- export const useDeleteUsers = () => {
293
- const queryClient = useQueryClient();
294
- return useMutation({
295
- mutationFn: (ids: Array<number>) =>
296
- apiInstance.delete("/users", {
297
- params: { ids },
298
- paramsSerializer: { indexes: null },
299
- }),
300
- onSuccess: () => {
301
- void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
302
- toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
303
- },
304
- });
305
- };
306
-
307
- // --- Action Log ---
308
-
309
- export const useGetLogs = (params: GetLogsParams) => {
310
- return useQuery({
311
- queryKey: [LOG_QUERY_KEY, "list", params],
312
- queryFn: () => apiInstance.get<PageResponse<ActionLog>>("/action-logs", { params }).then((r) => r.data),
313
- });
314
- };
315
-
316
- // --- Dashboard ---
317
-
318
- export const useGetUserDashboard = (period = "30d") => {
319
- return useQuery({
320
- queryKey: [DASHBOARD_QUERY_KEY, period],
321
- queryFn: () =>
322
- apiInstance
323
- .get<UserDashboardResponse>("/users/dashboard", { params: { period } })
324
- .then((r) => r.data),
325
- });
326
- };
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { toast } from "@farmzone/fz-react-ui";
3
+
4
+ import { apiInstance, apiFormDataInstance } from "@/app/api/api";
5
+ import {
6
+ COMMENT_QUERY_KEY,
7
+ DASHBOARD_QUERY_KEY,
8
+ LOG_QUERY_KEY,
9
+ POST_QUERY_KEY,
10
+ SAMPLE_QUERY_KEY,
11
+ USER_QUERY_KEY,
12
+ } from "@/app/api/queryKey";
13
+ import { COMMON_MESSAGES } from "@/shared/config/text";
14
+ import type {
15
+ LoginResponse,
16
+ PageResponse,
17
+ Sample,
18
+ GetSamplesParams,
19
+ SampleForm,
20
+ Post,
21
+ GetPostsParams,
22
+ PostForm,
23
+ Comment,
24
+ CommentForm,
25
+ CommentTargetType,
26
+ CommentEditForm,
27
+ User,
28
+ GetUsersParams,
29
+ UserForm,
30
+ UserEditForm,
31
+ ActionLog,
32
+ GetLogsParams,
33
+ UserDashboardResponse,
34
+ } from "@/types";
35
+
36
+ // --- Auth ---
37
+
38
+ export const usePostLogin = () => {
39
+ return useMutation({
40
+ mutationFn: (params: { userId: string; password: string }) =>
41
+ apiInstance.post<LoginResponse>("/auth/login", params).then((res) => res.data),
42
+ onSuccess: () => {
43
+ toast.success("로그인 성공", { duration: 1500 });
44
+ },
45
+ });
46
+ };
47
+
48
+ // --- Sample ---
49
+
50
+ export const useGetSamples = (params: GetSamplesParams) => {
51
+ return useQuery({
52
+ queryKey: [SAMPLE_QUERY_KEY, "list", params],
53
+ queryFn: () => apiInstance.get<PageResponse<Sample>>("/samples", { params }).then((r) => r.data),
54
+ });
55
+ };
56
+
57
+ export const useGetSample = (id: number | null) => {
58
+ return useQuery({
59
+ queryKey: [SAMPLE_QUERY_KEY, "detail", id],
60
+ queryFn: () => apiInstance.get<Sample>(`/samples/${id}`).then((r) => r.data),
61
+ enabled: id !== null,
62
+ });
63
+ };
64
+
65
+ export const usePostSample = () => {
66
+ const queryClient = useQueryClient();
67
+ return useMutation({
68
+ mutationFn: async ({ data, files }: { data: SampleForm; files?: Array<File> }) => {
69
+ const formData = new FormData();
70
+ formData.append("request", JSON.stringify(data));
71
+ files?.forEach((f) => formData.append("files", f));
72
+ return (await apiFormDataInstance.post<Sample>("/samples", formData)).data;
73
+ },
74
+ onSuccess: () => {
75
+ void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
76
+ toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
77
+ },
78
+ });
79
+ };
80
+
81
+ export const usePutSample = () => {
82
+ const queryClient = useQueryClient();
83
+ return useMutation({
84
+ mutationFn: async ({ id, data, files }: { id: number; data: SampleForm; files?: Array<File> }) => {
85
+ const formData = new FormData();
86
+ formData.append("request", JSON.stringify(data));
87
+ files?.forEach((f) => formData.append("files", f));
88
+ return (await apiFormDataInstance.put<Sample>(`/samples/${id}`, formData)).data;
89
+ },
90
+ onSuccess: () => {
91
+ void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
92
+ toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
93
+ },
94
+ });
95
+ };
96
+
97
+ export const useDeleteSample = () => {
98
+ const queryClient = useQueryClient();
99
+ return useMutation({
100
+ mutationFn: (id: number) => apiInstance.delete(`/samples/${id}`),
101
+ onSuccess: () => {
102
+ void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
103
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
104
+ },
105
+ });
106
+ };
107
+
108
+ export const useDeleteSamples = () => {
109
+ const queryClient = useQueryClient();
110
+ return useMutation({
111
+ mutationFn: (ids: Array<number>) =>
112
+ apiInstance.delete("/samples", {
113
+ params: { ids },
114
+ paramsSerializer: { indexes: null },
115
+ }),
116
+ onSuccess: () => {
117
+ void queryClient.invalidateQueries({ queryKey: [SAMPLE_QUERY_KEY] });
118
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
119
+ },
120
+ });
121
+ };
122
+
123
+ // --- Post ---
124
+
125
+ export const useGetPosts = (params: GetPostsParams) => {
126
+ return useQuery({
127
+ queryKey: [POST_QUERY_KEY, "list", params],
128
+ queryFn: () => apiInstance.get<PageResponse<Post>>("/posts", { params }).then((r) => r.data),
129
+ });
130
+ };
131
+
132
+ export const useGetPost = (id: number | null) => {
133
+ return useQuery({
134
+ queryKey: [POST_QUERY_KEY, "detail", id],
135
+ queryFn: () => apiInstance.get<Post>(`/posts/${id}`).then((r) => r.data),
136
+ enabled: id !== null,
137
+ });
138
+ };
139
+
140
+ export const usePostPost = () => {
141
+ const queryClient = useQueryClient();
142
+ return useMutation({
143
+ mutationFn: (data: PostForm) => apiInstance.post<Post>("/posts", data).then((r) => r.data),
144
+ onSuccess: () => {
145
+ void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
146
+ toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
147
+ },
148
+ });
149
+ };
150
+
151
+ export const usePutPost = () => {
152
+ const queryClient = useQueryClient();
153
+ return useMutation({
154
+ mutationFn: ({ id, data }: { id: number; data: PostForm }) =>
155
+ apiInstance.put<Post>(`/posts/${id}`, data).then((r) => r.data),
156
+ onSuccess: () => {
157
+ void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
158
+ toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
159
+ },
160
+ });
161
+ };
162
+
163
+ export const useDeletePost = () => {
164
+ const queryClient = useQueryClient();
165
+ return useMutation({
166
+ mutationFn: (id: number) => apiInstance.delete(`/posts/${id}`),
167
+ onSuccess: () => {
168
+ void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
169
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
170
+ },
171
+ });
172
+ };
173
+
174
+ export const useDeletePosts = () => {
175
+ const queryClient = useQueryClient();
176
+ return useMutation({
177
+ mutationFn: (ids: Array<number>) =>
178
+ apiInstance.delete("/posts", {
179
+ params: { ids },
180
+ paramsSerializer: { indexes: null },
181
+ }),
182
+ onSuccess: () => {
183
+ void queryClient.invalidateQueries({ queryKey: [POST_QUERY_KEY] });
184
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
185
+ },
186
+ });
187
+ };
188
+
189
+ // --- Comment ---
190
+
191
+ export const useGetComments = (targetType: CommentTargetType, targetId: number) => {
192
+ return useQuery({
193
+ queryKey: [COMMENT_QUERY_KEY, targetType, targetId],
194
+ queryFn: () =>
195
+ apiInstance.get<Array<Comment>>("/comments", { params: { targetType, targetId } }).then((r) => r.data),
196
+ });
197
+ };
198
+
199
+ export const usePostComment = () => {
200
+ const queryClient = useQueryClient();
201
+ return useMutation({
202
+ mutationFn: (data: CommentForm) => apiInstance.post<Comment>("/comments", data).then((r) => r.data),
203
+ onSuccess: () => {
204
+ void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
205
+ toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
206
+ },
207
+ });
208
+ };
209
+
210
+ export const usePutComment = () => {
211
+ const queryClient = useQueryClient();
212
+ return useMutation({
213
+ mutationFn: ({ commentId, data }: { commentId: number; data: CommentEditForm }) =>
214
+ apiInstance.put<Comment>(`/comments/${commentId}`, data).then((r) => r.data),
215
+ onSuccess: () => {
216
+ void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
217
+ toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
218
+ },
219
+ });
220
+ };
221
+
222
+ export const useDeleteComment = () => {
223
+ const queryClient = useQueryClient();
224
+ return useMutation({
225
+ mutationFn: (commentId: number) => apiInstance.delete(`/comments/${commentId}`),
226
+ onSuccess: () => {
227
+ void queryClient.invalidateQueries({ queryKey: [COMMENT_QUERY_KEY] });
228
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
229
+ },
230
+ });
231
+ };
232
+
233
+ // --- Sample check ---
234
+
235
+ export const checkSampleNameAvailable = (name: string): Promise<boolean> =>
236
+ apiInstance.get<boolean>("/samples/check/name", { params: { name } }).then((r) => r.data);
237
+
238
+ // --- User ---
239
+
240
+ export const checkUserIdAvailable = (userId: string): Promise<boolean> =>
241
+ apiInstance.get<boolean>("/users/check/id", { params: { userId } }).then((r) => r.data);
242
+
243
+ export const useGetUsers = (params: GetUsersParams) => {
244
+ return useQuery({
245
+ queryKey: [USER_QUERY_KEY, "list", params],
246
+ queryFn: () => apiInstance.get<PageResponse<User>>("/users", { params }).then((r) => r.data),
247
+ });
248
+ };
249
+
250
+ export const useGetUser = (id: number | null) => {
251
+ return useQuery({
252
+ queryKey: [USER_QUERY_KEY, "detail", id],
253
+ queryFn: () => apiInstance.get<User>(`/users/${id}`).then((r) => r.data),
254
+ enabled: id !== null,
255
+ });
256
+ };
257
+
258
+ export const usePostUser = () => {
259
+ const queryClient = useQueryClient();
260
+ return useMutation({
261
+ mutationFn: async ({ data, file }: { data: UserForm; file?: File }) => {
262
+ const formData = new FormData();
263
+ formData.append("request", JSON.stringify(data));
264
+ if (file) formData.append("files", file);
265
+ return (await apiFormDataInstance.post<User>("/users", formData)).data;
266
+ },
267
+ onSuccess: () => {
268
+ void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
269
+ toast.success(COMMON_MESSAGES.SAVE_SUCCESS);
270
+ },
271
+ });
272
+ };
273
+
274
+ export const usePutUser = () => {
275
+ const queryClient = useQueryClient();
276
+ return useMutation({
277
+ mutationFn: async ({ id, data, file }: { id: number; data: UserEditForm; file?: File }) => {
278
+ const formData = new FormData();
279
+ formData.append("request", JSON.stringify(data));
280
+ if (file) formData.append("files", file);
281
+ return (await apiFormDataInstance.put<User>(`/users/${id}`, formData)).data;
282
+ },
283
+ onSuccess: () => {
284
+ void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
285
+ toast.success(COMMON_MESSAGES.UPDATE_SUCCESS);
286
+ },
287
+ });
288
+ };
289
+
290
+ export const useDeleteUser = () => {
291
+ const queryClient = useQueryClient();
292
+ return useMutation({
293
+ mutationFn: (id: number) => apiInstance.delete(`/users/${id}`),
294
+ onSuccess: () => {
295
+ void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
296
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
297
+ },
298
+ });
299
+ };
300
+
301
+ export const useDeleteUsers = () => {
302
+ const queryClient = useQueryClient();
303
+ return useMutation({
304
+ mutationFn: (ids: Array<number>) =>
305
+ apiInstance.delete("/users", {
306
+ params: { ids },
307
+ paramsSerializer: { indexes: null },
308
+ }),
309
+ onSuccess: () => {
310
+ void queryClient.invalidateQueries({ queryKey: [USER_QUERY_KEY] });
311
+ toast.success(COMMON_MESSAGES.DELETE_SUCCESS);
312
+ },
313
+ });
314
+ };
315
+
316
+ // --- Action Log ---
317
+
318
+ export const useGetLogs = (params: GetLogsParams) => {
319
+ return useQuery({
320
+ queryKey: [LOG_QUERY_KEY, "list", params],
321
+ queryFn: () => apiInstance.get<PageResponse<ActionLog>>("/action-logs", { params }).then((r) => r.data),
322
+ });
323
+ };
324
+
325
+ // --- Dashboard ---
326
+
327
+ export const useGetUserDashboard = (period = "30d") => {
328
+ return useQuery({
329
+ queryKey: [DASHBOARD_QUERY_KEY, period],
330
+ queryFn: () =>
331
+ apiInstance
332
+ .get<UserDashboardResponse>("/users/dashboard", { params: { period } })
333
+ .then((r) => r.data),
334
+ });
335
+ };