@axboot-mcp/mcp-server 1.0.0

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 (78) hide show
  1. package/CLAUDE.md +119 -0
  2. package/MCP_TOOL_PLAN.md +710 -0
  3. package/MCP_USAGE.md +914 -0
  4. package/README.md +168 -0
  5. package/REPOSITORY_CONVENTIONS.md +250 -0
  6. package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
  7. package/SEARCH_PARAMS_PLAN.md +2570 -0
  8. package/STORE_PATTERNS.md +1178 -0
  9. package/debug-dto.js +72 -0
  10. package/generate-banner-store.js +62 -0
  11. package/generation-plan.json +2176 -0
  12. package/generation-results.json +1817 -0
  13. package/package.json +45 -0
  14. package/scripts/batch-generate-all.js +159 -0
  15. package/scripts/batch-generate-mcp.js +329 -0
  16. package/scripts/batch-generate-stores-v2.js +272 -0
  17. package/scripts/batch-generate-stores.js +179 -0
  18. package/scripts/batch-plan.json +3810 -0
  19. package/scripts/batch-process.py +90 -0
  20. package/scripts/batch-regenerate.js +356 -0
  21. package/scripts/direct-generate.js +227 -0
  22. package/scripts/execute-batches.js +1911 -0
  23. package/scripts/generate-all-stores.js +144 -0
  24. package/scripts/generate-stores-mcp.js +161 -0
  25. package/scripts/generate-stores-v2.js +450 -0
  26. package/scripts/generate-stores-v3.js +412 -0
  27. package/scripts/generate-stores-v4.js +521 -0
  28. package/scripts/generate-stores.js +382 -0
  29. package/scripts/repos-to-process.json +1899 -0
  30. package/src/config/nh-layout-patterns.ts +166 -0
  31. package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
  32. package/src/docs/NH_STORE_PATTERNS.md +297 -0
  33. package/src/docs/README.md +216 -0
  34. package/src/docs/index.ts +28 -0
  35. package/src/docs/loader.ts +568 -0
  36. package/src/docs/patterns.json +419 -0
  37. package/src/docs/practical-examples.md +732 -0
  38. package/src/docs/quick-start.md +257 -0
  39. package/src/docs/requirements-analysis-guide.md +364 -0
  40. package/src/docs/rules.json +321 -0
  41. package/src/docs/store-pattern-analysis.md +664 -0
  42. package/src/docs/store-patterns-rules.md +1168 -0
  43. package/src/docs/store-patterns-usage-guide.md +1835 -0
  44. package/src/docs/troubleshooting.md +544 -0
  45. package/src/docs/type-selection-guide.md +572 -0
  46. package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
  47. package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
  48. package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
  49. package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
  50. package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
  51. package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
  52. package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
  53. package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
  54. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
  55. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
  56. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
  57. package/src/features/store-features.ts +232 -0
  58. package/src/handlers/analyze-requirements.ts +403 -0
  59. package/src/handlers/analyze.ts +1373 -0
  60. package/src/handlers/generate-from-requirements.ts +250 -0
  61. package/src/handlers/generate-hook.ts +950 -0
  62. package/src/handlers/generate-interactive.ts +840 -0
  63. package/src/handlers/generate-listdatagrid.ts +521 -0
  64. package/src/handlers/generate-multi-stores.ts +577 -0
  65. package/src/handlers/generate-requirements-from-layout.ts +160 -0
  66. package/src/handlers/generate-search-params.ts +717 -0
  67. package/src/handlers/generate.ts +911 -0
  68. package/src/handlers/list-templates.ts +104 -0
  69. package/src/handlers/scan-metadata.ts +485 -0
  70. package/src/handlers/suggest-layout.ts +326 -0
  71. package/src/index.ts +959 -0
  72. package/src/prompts/search-params.md +793 -0
  73. package/src/templates/index.ts +107 -0
  74. package/src/templates/unified.ts +462 -0
  75. package/store-generation-error-patterns.md +225 -0
  76. package/test/useAgentStore.ts +136 -0
  77. package/test-server.js +78 -0
  78. package/tsconfig.json +20 -0
@@ -0,0 +1,1178 @@
1
+ # NH Store 생성 가이드
2
+
3
+ 이 문서는 `src/pages/resources/NH` 하위 Store 파일들의 createActions 영역 API 연결 패턴을 분석하여 정리한 것입니다.
4
+
5
+ ---
6
+
7
+ ## 목차
8
+
9
+ 1. [기본 구조](#기본-구조)
10
+ 2. [Spinning State 종류](#spinning-state-종류)
11
+ 3. [API 패턴별 상세](#api-패턴별-상세)
12
+ - [Pattern 1: List Only](#pattern-1-list-only)
13
+ - [Pattern 2: List + Detail](#pattern-2-list--detail)
14
+ - [Pattern 3: List + Delete](#pattern-3-list--delete)
15
+ - [Pattern 4: List + Save](#pattern-4-list--save)
16
+ - [Pattern 5: List + Excel](#pattern-5-list--excel)
17
+ - [Pattern 6: Tree](#pattern-6-tree)
18
+ - [Pattern 7: Master-Detail + SubList](#pattern-7-master-detail--sublist)
19
+ - [Pattern 8: List + Modal](#pattern-8-list--modal)
20
+ 4. [공통 헬퍼 함수](#공통-헬퍼-함수)
21
+ 5. [공통 Setter 메서드](#공통-setter-메서드)
22
+
23
+ ---
24
+
25
+ ## 기본 구조
26
+
27
+ 모든 Store는 다음 기본 구조를 따릅니다:
28
+
29
+ ```typescript
30
+ import { pageStoreActions } from "@core/stores/pageStoreActions";
31
+ import { PageStoreActions, StoreActions } from "@core/stores/types";
32
+ import { deleteEmptyValue } from "@core/utils/object";
33
+ import { ProgramFn } from "@types";
34
+ import { XxxService, XxxRequest, XxxResponse } from "services";
35
+ import { getTabStoreListener } from "stores";
36
+ import { create } from "zustand";
37
+ import { subscribeWithSelector } from "zustand/middleware";
38
+ import { shallow } from "zustand/shallow";
39
+
40
+ // DTO 타입 정의
41
+ interface DtoItem extends XxxDto {}
42
+
43
+ // 요청 타입 정의
44
+ interface ListRequest extends XxxRequest {
45
+ dateRange?: [string, string];
46
+ // 추가 필드...
47
+ }
48
+
49
+ interface MetaData {
50
+ programFn?: ProgramFn;
51
+ // 메타데이터 필드...
52
+ }
53
+
54
+ interface States extends MetaData {
55
+ routePath?: string;
56
+ // spinning 상태 필드...
57
+ // 데이터 필드...
58
+ }
59
+
60
+ // create states
61
+ const createState: States = {
62
+ // 초기값...
63
+ };
64
+
65
+ // create actions
66
+ interface Actions extends PageStoreActions<States> {
67
+ // 액션 메서드 정의...
68
+ }
69
+
70
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
71
+ ...pageStoreActions(set, get, { createState }), // 위치: 맨 앞 또는 맨 뒤
72
+ syncMetadata: (s = createState) => set(s),
73
+ onMountApp: async () => {},
74
+ // API 호출 메서드...
75
+ });
76
+
77
+ // export
78
+ export interface xxxStore extends States, Actions, PageStoreActions<States> {}
79
+ export const useXxxStore = create(
80
+ subscribeWithSelector<xxxStore>((set, get) => ({
81
+ ...createState,
82
+ ...createActions(set, get),
83
+ })),
84
+ );
85
+
86
+ // subscribe
87
+ useXxxStore.subscribe(
88
+ (s): Record<keyof MetaData, any> => ({
89
+ // 메타데이터 필드...
90
+ }),
91
+ getTabStoreListener<MetaData>(createState.routePath),
92
+ { equalityFn: shallow },
93
+ );
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Spinning State 종류
99
+
100
+ | Spinning State | 용도 | 관련 API 메서드 |
101
+ |----------------|------|----------------|
102
+ | `listSpinning` | 리스트 조회 로딩 | `callListApi` |
103
+ | `excelSpinning` | 엑셀 다운로드 로딩 | `callExcelDownloadApi` / `downloadExcel` |
104
+ | `saveSpinning` | 저장 로딩 | `callSaveApi` / `callFormSaveApi` |
105
+ | `deleteSpinning` | 삭제 로딩 | `callDeleteApi` |
106
+ | `detailSpinning` | 상세 조회 로딩 | `callDetailApi` |
107
+ | `subListSpinning` | 서브 리스트 로딩 | `callSubListApi` |
108
+ | `treeSpinning` | 트리 로딩 | `callTreeApi` |
109
+ | `detailLoading` | 디테일 로딩 (지연) | `setSelectedItem` (with delay) |
110
+
111
+ ---
112
+
113
+ ## API 패턴별 상세
114
+
115
+ ### Pattern 1: List Only
116
+
117
+ 가장 기본적인 리스트만 있는 패턴입니다.
118
+
119
+ ```typescript
120
+ // ========== interface MetaData ==========
121
+ interface MetaData {
122
+ programFn?: ProgramFn;
123
+ listRequestValue: ListRequest;
124
+ listColWidths: number[];
125
+ listSortParams: AXDGSortParam[];
126
+ }
127
+
128
+ // ========== interface States ==========
129
+ interface States extends MetaData {
130
+ routePath?: string;
131
+ listSpinning: boolean;
132
+ listData: AXDGDataItem<DtoItem>[];
133
+ listPage: AXDGPage;
134
+ }
135
+
136
+ // ========== const createState ==========
137
+ const createState: States = {
138
+ listRequestValue: {
139
+ pageNumber: 1,
140
+ pageSize: 100,
141
+ },
142
+ listColWidths: [],
143
+ listSortParams: [],
144
+ listSpinning: false,
145
+ listData: [],
146
+ listPage: {
147
+ currentPage: 0,
148
+ totalPages: 0,
149
+ },
150
+ };
151
+
152
+ // ========== interface Actions ==========
153
+ interface Actions extends PageStoreActions<States> {
154
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
155
+ setListColWidths: (colWidths: number[]) => void;
156
+ setListSpinning: (spinning: boolean) => void;
157
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
158
+ callListApi: (request?: ListRequest) => Promise<void>;
159
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
160
+ }
161
+
162
+ // ========== const createActions ==========
163
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
164
+ ...pageStoreActions(set, get, { createState }),
165
+ syncMetadata: (s = createState) => set(s),
166
+ onMountApp: async () => {},
167
+
168
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
169
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
170
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
171
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
172
+
173
+ callListApi: async (request) => {
174
+ if (get().listSpinning) return;
175
+ set({ listSpinning: true });
176
+
177
+ try {
178
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
179
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
180
+
181
+ set({
182
+ listRequestValue: apiParam,
183
+ listData: response.ds.map((values) => ({ values })),
184
+ listPage: {
185
+ currentPage: response.page.pageNumber ?? 1,
186
+ pageSize: response.page.pageSize ?? 0,
187
+ totalPages: response.page.pageCount ?? 0,
188
+ totalElements: response.page?.totalCount,
189
+ },
190
+ });
191
+ } finally {
192
+ set({ listSpinning: false });
193
+ }
194
+ },
195
+
196
+ changeListPage: async (pageNumber, pageSize) => {
197
+ await get().callListApi({ pageNumber, pageSize });
198
+ },
199
+ });
200
+
201
+ // ========== Store.subscribe ==========
202
+ useXxxStore.subscribe(
203
+ (s): Record<keyof MetaData, any> => ({
204
+ programFn: s.programFn,
205
+ listRequestValue: s.listRequestValue,
206
+ listColWidths: s.listColWidths,
207
+ listSortParams: s.listSortParams,
208
+ }),
209
+ getTabStoreListener<MetaData>(createState.routePath),
210
+ { equalityFn: shallow },
211
+ );
212
+ ```
213
+
214
+ ---
215
+
216
+ ### Pattern 2: List + Detail
217
+
218
+ 리스트와 선택된 항목의 상세를 표시하는 패턴입니다.
219
+
220
+ ```typescript
221
+ // ========== interface MetaData ==========
222
+ interface MetaData {
223
+ programFn?: ProgramFn;
224
+ listRequestValue: ListRequest;
225
+ listColWidths: number[];
226
+ listSortParams: AXDGSortParam[];
227
+ saveRequestValue: SaveRequest;
228
+ }
229
+
230
+ // ========== interface States ==========
231
+ interface States extends MetaData {
232
+ routePath?: string;
233
+ listSpinning: boolean;
234
+ listData: AXDGDataItem<DtoItem>[];
235
+ listPage: AXDGPage;
236
+ selectedItem?: DtoItem;
237
+ detailLoading: boolean;
238
+ checkedRowKeys: React.Key[];
239
+ }
240
+
241
+ // ========== const createState ==========
242
+ const createState: States = {
243
+ listRequestValue: { pageNumber: 1, pageSize: 100 },
244
+ listColWidths: [],
245
+ listSortParams: [],
246
+ listSpinning: false,
247
+ listData: [],
248
+ listPage: { currentPage: 0, totalPages: 0 },
249
+ saveRequestValue: {},
250
+ detailLoading: false,
251
+ checkedRowKeys: [],
252
+ };
253
+
254
+ // ========== interface Actions ==========
255
+ interface Actions extends PageStoreActions<States> {
256
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
257
+ setListColWidths: (colWidths: number[]) => void;
258
+ setListSpinning: (spinning: boolean) => void;
259
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
260
+ callListApi: (request?: ListRequest) => Promise<void>;
261
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
262
+ setSelectedItem: (selectedItem?: DtoItem) => Promise<void>;
263
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void;
264
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void;
265
+ }
266
+
267
+ // ========== const createActions ==========
268
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
269
+ ...pageStoreActions(set, get, { createState }),
270
+ syncMetadata: (s = createState) => set(s),
271
+ onMountApp: async () => {},
272
+
273
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
274
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
275
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
276
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
277
+
278
+ callListApi: async (request) => {
279
+ if (get().listSpinning) return;
280
+ set({ listSpinning: true });
281
+ try {
282
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
283
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
284
+ set({
285
+ listRequestValue: apiParam,
286
+ listData: response.ds.map((values) => ({ values })),
287
+ listPage: {
288
+ currentPage: response.page.pageNumber ?? 1,
289
+ pageSize: response.page.pageSize ?? 0,
290
+ totalPages: response.page.pageCount ?? 0,
291
+ totalElements: response.page?.totalCount,
292
+ },
293
+ });
294
+ } finally {
295
+ set({ listSpinning: false });
296
+ }
297
+ },
298
+
299
+ changeListPage: async (pageNumber, pageSize) => {
300
+ await get().callListApi({ pageNumber, pageSize });
301
+ },
302
+
303
+ setSelectedItem: async (selectedItem) => {
304
+ set({ selectedItem, detailLoading: true });
305
+ if (!selectedItem) {
306
+ set({ saveRequestValue: {} });
307
+ }
308
+ await delay(300);
309
+ set({ detailLoading: false });
310
+ },
311
+
312
+ setSaveRequestValue: (saveRequestValue) => set({ saveRequestValue }),
313
+ setCheckedRowKeys: (checkedRowKeys) => set({ checkedRowKeys }),
314
+ });
315
+
316
+ // ========== Store.subscribe ==========
317
+ useXxxStore.subscribe(
318
+ (s): Record<keyof MetaData, any> => ({
319
+ programFn: s.programFn,
320
+ listRequestValue: s.listRequestValue,
321
+ listColWidths: s.listColWidths,
322
+ listSortParams: s.listSortParams,
323
+ saveRequestValue: s.saveRequestValue,
324
+ }),
325
+ getTabStoreListener<MetaData>(createState.routePath),
326
+ { equalityFn: shallow },
327
+ );
328
+ ```
329
+
330
+ ---
331
+
332
+ ### Pattern 3: List + Delete
333
+
334
+ 리스트와 삭제 기능이 있는 패턴입니다.
335
+
336
+ ```typescript
337
+ // ========== interface MetaData ==========
338
+ interface MetaData {
339
+ programFn?: ProgramFn;
340
+ listRequestValue: ListRequest;
341
+ listColWidths: number[];
342
+ listSortParams: AXDGSortParam[];
343
+ }
344
+
345
+ // ========== interface States ==========
346
+ interface States extends MetaData {
347
+ routePath?: string;
348
+ listSpinning: boolean;
349
+ deleteSpinning: boolean;
350
+ listData: AXDGDataItem<DtoItem>[];
351
+ listPage: AXDGPage;
352
+ }
353
+
354
+ // ========== const createState ==========
355
+ const createState: States = {
356
+ listRequestValue: { pageNumber: 1, pageSize: 100 },
357
+ listColWidths: [],
358
+ listSortParams: [],
359
+ listSpinning: false,
360
+ deleteSpinning: false,
361
+ listData: [],
362
+ listPage: { currentPage: 0, totalPages: 0 },
363
+ };
364
+
365
+ // ========== interface Actions ==========
366
+ interface Actions extends PageStoreActions<States> {
367
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
368
+ setListColWidths: (colWidths: number[]) => void;
369
+ setListSpinning: (spinning: boolean) => void;
370
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
371
+ callListApi: (request?: ListRequest) => Promise<void>;
372
+ callDeleteApi: (id: number) => Promise<void>;
373
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
374
+ }
375
+
376
+ // ========== const createActions ==========
377
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
378
+ ...pageStoreActions(set, get, { createState }),
379
+ syncMetadata: (s = createState) => set(s),
380
+ onMountApp: async () => {},
381
+
382
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
383
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
384
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
385
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
386
+
387
+ callListApi: async (request) => {
388
+ if (get().listSpinning) return;
389
+ set({ listSpinning: true });
390
+ try {
391
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
392
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
393
+ set({
394
+ listRequestValue: apiParam,
395
+ listData: response.ds.map((values) => ({ values })),
396
+ listPage: {
397
+ currentPage: response.page.pageNumber ?? 1,
398
+ pageSize: response.page.pageSize ?? 0,
399
+ totalPages: response.page.pageCount ?? 0,
400
+ totalElements: response.page?.totalCount,
401
+ },
402
+ });
403
+ } finally {
404
+ set({ listSpinning: false });
405
+ }
406
+ },
407
+
408
+ callDeleteApi: async (id) => {
409
+ set({ deleteSpinning: true });
410
+ try {
411
+ await XxxService.postXxxDeleteXxx({ id });
412
+ } finally {
413
+ set({ deleteSpinning: false });
414
+ }
415
+ },
416
+
417
+ changeListPage: async (pageNumber, pageSize) => {
418
+ await get().callListApi({ pageNumber, pageSize });
419
+ },
420
+ });
421
+
422
+ // ========== Store.subscribe ==========
423
+ useXxxStore.subscribe(
424
+ (s): Record<keyof MetaData, any> => ({
425
+ programFn: s.programFn,
426
+ listRequestValue: s.listRequestValue,
427
+ listColWidths: s.listColWidths,
428
+ listSortParams: s.listSortParams,
429
+ }),
430
+ getTabStoreListener<MetaData>(createState.routePath),
431
+ { equalityFn: shallow },
432
+ );
433
+ ```
434
+
435
+ ---
436
+
437
+ ### Pattern 4: List + Save
438
+
439
+ 리스트와 저장 기능이 있는 패턴입니다.
440
+
441
+ ```typescript
442
+ // ========== interface MetaData ==========
443
+ interface MetaData {
444
+ programFn?: ProgramFn;
445
+ listRequestValue: ListRequest;
446
+ listColWidths: number[];
447
+ listSortParams: AXDGSortParam[];
448
+ saveRequestValue: SaveRequest;
449
+ modalOpen: boolean;
450
+ }
451
+
452
+ // ========== interface States ==========
453
+ interface States extends MetaData {
454
+ routePath?: string;
455
+ listSpinning: boolean;
456
+ saveSpinning: boolean;
457
+ listData: AXDGDataItem<DtoItem>[];
458
+ listPage: AXDGPage;
459
+ }
460
+
461
+ // ========== const createState ==========
462
+ const createState: States = {
463
+ listRequestValue: { pageNumber: 1, pageSize: 100 },
464
+ listColWidths: [],
465
+ listSortParams: [],
466
+ listSpinning: false,
467
+ saveSpinning: false,
468
+ listData: [],
469
+ listPage: { currentPage: 0, totalPages: 0 },
470
+ saveRequestValue: {},
471
+ modalOpen: false,
472
+ };
473
+
474
+ // ========== interface Actions ==========
475
+ interface Actions extends PageStoreActions<States> {
476
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
477
+ setListColWidths: (colWidths: number[]) => void;
478
+ setListSpinning: (spinning: boolean) => void;
479
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
480
+ callListApi: (request?: ListRequest) => Promise<void>;
481
+ callSaveApi: (apiParam: SaveRequest) => Promise<void>;
482
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
483
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void;
484
+ setModalOpen: (open: boolean) => void;
485
+ }
486
+
487
+ // ========== const createActions ==========
488
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
489
+ ...pageStoreActions(set, get, { createState }),
490
+ syncMetadata: (s = createState) => set(s),
491
+ onMountApp: async () => {},
492
+
493
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
494
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
495
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
496
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
497
+
498
+ callListApi: async (request) => {
499
+ if (get().listSpinning) return;
500
+ set({ listSpinning: true });
501
+ try {
502
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
503
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
504
+ set({
505
+ listRequestValue: apiParam,
506
+ listData: response.ds.map((values) => ({ values })),
507
+ listPage: {
508
+ currentPage: response.page.pageNumber ?? 1,
509
+ pageSize: response.page.pageSize ?? 0,
510
+ totalPages: response.page.pageCount ?? 0,
511
+ totalElements: response.page?.totalCount,
512
+ },
513
+ });
514
+ } finally {
515
+ set({ listSpinning: false });
516
+ }
517
+ },
518
+
519
+ callSaveApi: async (apiParam) => {
520
+ try {
521
+ await XxxService.postXxxSaveXxx(apiParam);
522
+ await get().callListApi();
523
+ } catch (err) {
524
+ await errorHandling(err);
525
+ }
526
+ },
527
+
528
+ changeListPage: async (pageNumber, pageSize) => {
529
+ await get().callListApi({ pageNumber, pageSize });
530
+ },
531
+
532
+ setSaveRequestValue: (saveRequestValue) => set({ saveRequestValue }),
533
+ setModalOpen: (open) => set({ modalOpen: open }),
534
+ });
535
+
536
+ // ========== Store.subscribe ==========
537
+ useXxxStore.subscribe(
538
+ (s): Record<keyof MetaData, any> => ({
539
+ programFn: s.programFn,
540
+ listRequestValue: s.listRequestValue,
541
+ listColWidths: s.listColWidths,
542
+ listSortParams: s.listSortParams,
543
+ saveRequestValue: s.saveRequestValue,
544
+ modalOpen: s.modalOpen,
545
+ }),
546
+ getTabStoreListener<MetaData>(createState.routePath),
547
+ { equalityFn: shallow },
548
+ );
549
+ ```
550
+
551
+ ---
552
+
553
+ ### Pattern 5: List + Excel
554
+
555
+ 리스트와 엑셀 다운로드 기능이 있는 패턴입니다.
556
+
557
+ ```typescript
558
+ // ========== interface MetaData ==========
559
+ interface MetaData {
560
+ programFn?: ProgramFn;
561
+ listRequestValue: ListRequest;
562
+ listColWidths: number[];
563
+ listSortParams: AXDGSortParam[];
564
+ }
565
+
566
+ // ========== interface States ==========
567
+ interface States extends MetaData {
568
+ routePath?: string;
569
+ listSpinning: boolean;
570
+ excelSpinning: boolean;
571
+ listData: AXDGDataItem<DtoItem>[];
572
+ listPage: AXDGPage;
573
+ }
574
+
575
+ // ========== const createState ==========
576
+ const createState: States = {
577
+ listRequestValue: { pageNumber: 1, pageSize: 100 },
578
+ listColWidths: [],
579
+ listSortParams: [],
580
+ listSpinning: false,
581
+ excelSpinning: false,
582
+ listData: [],
583
+ listPage: { currentPage: 0, totalPages: 0 },
584
+ };
585
+
586
+ // ========== interface Actions ==========
587
+ interface Actions extends PageStoreActions<States> {
588
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
589
+ setListColWidths: (colWidths: number[]) => void;
590
+ setListSpinning: (spinning: boolean) => void;
591
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
592
+ callListApi: (request?: ListRequest) => Promise<void>;
593
+ callExcelDownloadApi: (request?: ListRequest) => Promise<void>;
594
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
595
+ }
596
+
597
+ // ========== const createActions ==========
598
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
599
+ ...pageStoreActions(set, get, { createState }),
600
+ syncMetadata: (s = createState) => set(s),
601
+ onMountApp: async () => {},
602
+
603
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
604
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
605
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
606
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
607
+
608
+ callListApi: async (request) => {
609
+ if (get().listSpinning) return;
610
+ set({ listSpinning: true });
611
+ try {
612
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
613
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
614
+ set({
615
+ listRequestValue: apiParam,
616
+ listData: response.ds.map((values) => ({ values })),
617
+ listPage: {
618
+ currentPage: response.page.pageNumber ?? 1,
619
+ pageSize: response.page.pageSize ?? 0,
620
+ totalPages: response.page.pageCount ?? 0,
621
+ totalElements: response.page?.totalCount,
622
+ },
623
+ });
624
+ } finally {
625
+ set({ listSpinning: false });
626
+ }
627
+ },
628
+
629
+ callExcelDownloadApi: async (request) => {
630
+ if (get().excelSpinning) return;
631
+ set({ excelSpinning: true });
632
+ try {
633
+ const { pageNumber, pageSize, ...restParams }: ListRequest = {
634
+ ...get().listRequestValue,
635
+ ...request,
636
+ };
637
+ await XxxService.postXxxListXxxExcel(deleteEmptyValue(restParams));
638
+ } finally {
639
+ set({ excelSpinning: false });
640
+ }
641
+ },
642
+
643
+ changeListPage: async (pageNumber, pageSize) => {
644
+ await get().callListApi({ pageNumber, pageSize });
645
+ },
646
+ });
647
+
648
+ // ========== Store.subscribe ==========
649
+ useXxxStore.subscribe(
650
+ (s): Record<keyof MetaData, any> => ({
651
+ programFn: s.programFn,
652
+ listRequestValue: s.listRequestValue,
653
+ listColWidths: s.listColWidths,
654
+ listSortParams: s.listSortParams,
655
+ }),
656
+ getTabStoreListener<MetaData>(createState.routePath),
657
+ { equalityFn: shallow },
658
+ );
659
+ ```
660
+
661
+ ---
662
+
663
+ ### Pattern 6: Tree
664
+
665
+ 트리 구조 데이터를 다루는 패턴입니다.
666
+
667
+ ```typescript
668
+ // ========== interface MetaData ==========
669
+ interface MetaData {
670
+ programFn?: ProgramFn;
671
+ mdiaClcd: string;
672
+ siteTpcd: string;
673
+ flexGrow: number;
674
+ selectedItem?: DtoItem;
675
+ saveRequestValues?: DtoItem;
676
+ expandedKeys: string[];
677
+ }
678
+
679
+ // ========== interface States ==========
680
+ interface States extends MetaData {
681
+ routePath?: string;
682
+ treeSpinning: boolean;
683
+ saveSpinning: boolean;
684
+ treeData: DtoItem[];
685
+ isDirty: boolean;
686
+ isFormDirty: boolean;
687
+ }
688
+
689
+ // ========== const createState ==========
690
+ const createState: States = {
691
+ flexGrow: 450,
692
+ siteTpcd: "10",
693
+ mdiaClcd: "10",
694
+ treeSpinning: false,
695
+ saveSpinning: false,
696
+ treeData: [],
697
+ expandedKeys: [],
698
+ selectedItem: undefined,
699
+ saveRequestValues: undefined,
700
+ isDirty: false,
701
+ isFormDirty: false,
702
+ };
703
+
704
+ // ========== interface Actions ==========
705
+ interface Actions extends PageStoreActions<States> {
706
+ setFlexGrow: (flexGlow: number) => void;
707
+ callTreeApi: (mdiaClcd?: string) => Promise<void>;
708
+ callTreeSaveApi: (treeData?: DtoItem[]) => Promise<void>;
709
+ callFormSaveApi: () => Promise<void>;
710
+ setSelectedItem: (item?: DtoItem) => void;
711
+ setMdiaClcd: (mdiaClcd: string) => void;
712
+ setTreeData: (treeData: DtoItem[]) => void;
713
+ setExpandedKeys: (expandedKeys: string[]) => void;
714
+ setSaveRequestValues: (saveRequestValues: DtoItem) => void;
715
+ setIsDirty: (dirty: boolean) => void;
716
+ setIsFormDirty: (dirty: boolean) => void;
717
+ }
718
+
719
+ // ========== const createActions ==========
720
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
721
+ onMountApp: async () => {},
722
+ syncMetadata: (s = createState) => set(s),
723
+ setFlexGrow: (flexGrow) => set({ flexGrow }),
724
+
725
+ callTreeApi: async (mdiaClcd) => {
726
+ set({ treeSpinning: true });
727
+ try {
728
+ const { ds } = await XxxService.postXxxGetXxxTree({
729
+ siteTpcd: get().siteTpcd,
730
+ mdiaClcd: mdiaClcd ?? get().mdiaClcd,
731
+ });
732
+ set({ treeData: ds });
733
+ } finally {
734
+ set({ treeSpinning: false });
735
+ }
736
+ },
737
+
738
+ callTreeSaveApi: async (treeData) => {
739
+ set({ saveSpinning: true });
740
+ try {
741
+ const data = treeData ?? get().treeData;
742
+ await XxxService.postXxxSaveXxxTree(getMenuSortedChildren(data, 0, "0"));
743
+ set({ isDirty: false });
744
+ } finally {
745
+ set({ saveSpinning: false });
746
+ }
747
+ },
748
+
749
+ callFormSaveApi: async () => {
750
+ const { saveRequestValues } = get();
751
+ if (!saveRequestValues?.id) return;
752
+
753
+ set({ saveSpinning: true });
754
+ try {
755
+ await XxxService.postXxxSaveXxx(saveRequestValues);
756
+ set({ isFormDirty: false });
757
+ } finally {
758
+ set({ saveSpinning: false });
759
+ }
760
+ },
761
+
762
+ setSelectedItem: (item) => {
763
+ set({ selectedItem: item, saveRequestValues: { ...item } });
764
+ },
765
+ setMdiaClcd: (mdiaClcd) => {
766
+ set({ mdiaClcd, selectedItem: undefined, saveRequestValues: undefined });
767
+ },
768
+ setTreeData: (treeData) => set({ treeData }),
769
+ setExpandedKeys: (expandedKeys) => set({ expandedKeys }),
770
+ setSaveRequestValues: (saveRequestValues) => set({ saveRequestValues }),
771
+ setIsDirty: (dirty) => set({ isDirty: dirty }),
772
+ setIsFormDirty: (dirty) => set({ isFormDirty: dirty }),
773
+
774
+ ...pageStoreActions(set, get, { createState }),
775
+ });
776
+
777
+ // ========== Store.subscribe ==========
778
+ useXxxStore.subscribe(
779
+ (s): Record<keyof MetaData, any> => ({
780
+ programFn: s.programFn,
781
+ flexGrow: s.flexGrow,
782
+ mdiaClcd: s.mdiaClcd,
783
+ siteTpcd: s.siteTpcd,
784
+ selectedItem: s.selectedItem,
785
+ saveRequestValues: s.saveRequestValues,
786
+ expandedKeys: s.expandedKeys,
787
+ }),
788
+ getTabStoreListener<MetaData>(createState.routePath),
789
+ { equalityFn: shallow },
790
+ );
791
+ ```
792
+
793
+ ---
794
+
795
+ ### Pattern 7: Master-Detail + SubList
796
+
797
+ 마스터 리스트와 디테일 서브 리스트가 있는 패턴입니다.
798
+
799
+ ```typescript
800
+ // ========== interface MetaData ==========
801
+ interface MetaData {
802
+ listRequestValue: ListRequest;
803
+ subListRequestValue: SubListRequest;
804
+ listColWidths: number[];
805
+ listSortParams: AXDGSortParam[];
806
+ listSelectedRowKey?: React.Key;
807
+ flexGrow: number;
808
+ saveRequestValue: SaveRequest;
809
+ detail?: DetailResponse;
810
+ formActive: boolean;
811
+ subListColWidths: number[];
812
+ subListSelectedRowKey?: React.Key;
813
+ }
814
+
815
+ // ========== interface States ==========
816
+ interface States extends MetaData {
817
+ routePath?: string;
818
+ listSpinning: boolean;
819
+ saveSpinning: boolean;
820
+ detailSpinning: boolean;
821
+ subListSpinning: boolean;
822
+ listData: AXDGDataItem<DtoItem>[];
823
+ subListData: AXDGDataItem<SubDtoItem>[];
824
+ }
825
+
826
+ // ========== const createState ==========
827
+ const createState: States = {
828
+ listRequestValue: {},
829
+ subListRequestValue: {},
830
+ listColWidths: [],
831
+ listSortParams: [],
832
+ listSelectedRowKey: undefined,
833
+ flexGrow: 40,
834
+ saveRequestValue: { cd: '', nm: '' },
835
+ detail: { cd: '', nm: '' },
836
+ formActive: true,
837
+ listSpinning: false,
838
+ saveSpinning: false,
839
+ detailSpinning: false,
840
+ subListSpinning: false,
841
+ listData: [],
842
+ subListColWidths: [],
843
+ subListSelectedRowKey: undefined,
844
+ subListData: [],
845
+ };
846
+
847
+ // ========== interface Actions ==========
848
+ interface Actions extends PageStoreActions<States> {
849
+ callListApi: (request?: ListRequest, pageNumber?: number) => Promise<void>;
850
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
851
+ callSubListApi: (request?: SubListRequest) => Promise<void>;
852
+ callSaveApi: () => Promise<void>;
853
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
854
+ setSubListRequestValue: (requestValue: SubListRequest) => void;
855
+ setListColWidths: (colWidths: number[]) => void;
856
+ setListSpinning: (spinning: boolean) => void;
857
+ setSubListSpinning: (spinning: boolean) => void;
858
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
859
+ setListSelectedRowKey: (key: React.Key, detail?: DetailResponse) => void;
860
+ setFlexGrow: (flexGlow: number) => void;
861
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void;
862
+ setSubListColWidths: (colWidths: number[]) => void;
863
+ setSubListSelectedRowKey: (key?: React.Key) => void;
864
+ cancelFormActive: () => void;
865
+ setFormActive: () => void;
866
+ }
867
+
868
+ // ========== const createActions ==========
869
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
870
+ ...pageStoreActions(set, get, { createState }),
871
+ onMountApp: async () => {},
872
+ syncMetadata: (s = createState) => set(s),
873
+
874
+ callListApi: async (request) => {
875
+ if (get().listSpinning) return;
876
+ set({ listSpinning: true });
877
+ try {
878
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
879
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
880
+ set({
881
+ listRequestValue: apiParam,
882
+ listData: (response.ds ?? []).map((values) => ({ values })),
883
+ });
884
+ } catch (err) {
885
+ await errorHandling(err);
886
+ } finally {
887
+ set({ listSpinning: false });
888
+ }
889
+ },
890
+
891
+ changeListPage: async (currentPage, pageSize) => {
892
+ await get().callListApi({
893
+ pageNumber: currentPage,
894
+ pageSize: pageSize ?? get().listRequestValue.pageSize,
895
+ });
896
+ },
897
+
898
+ callSubListApi: async (request) => {
899
+ set({ subListSpinning: true, subListData: [], subListSelectedRowKey: "" });
900
+ try {
901
+ if (!get().listSelectedRowKey) return;
902
+ const apiParam = request ?? { id: get().listSelectedRowKey };
903
+ const response = await XxxService.postXxxSubList(deleteEmptyValue(apiParam));
904
+ set({
905
+ subListData: (response.ds ?? []).map((values) => ({ values })),
906
+ });
907
+ } catch (err) {
908
+ await errorHandling(err);
909
+ } finally {
910
+ set({ subListSpinning: false });
911
+ }
912
+ },
913
+
914
+ callSaveApi: async () => {
915
+ set({ saveSpinning: true });
916
+ try {
917
+ await get().callSubListApi();
918
+ } catch (err) {
919
+ await errorHandling(err);
920
+ } finally {
921
+ set({ saveSpinning: false });
922
+ }
923
+ },
924
+
925
+ setListRequestValue: (requestValues, changedValues) =>
926
+ set({ listRequestValue: { ...requestValues, ...changedValues } }),
927
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
928
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
929
+ setListSelectedRowKey: (key, detail) => {
930
+ set({
931
+ listSelectedRowKey: key,
932
+ subListSelectedRowKey: "",
933
+ detail,
934
+ saveRequestValue: detail ?? { cd: '', nm: '' },
935
+ });
936
+ },
937
+ setFlexGrow: (flexGlow) => set({ flexGrow: flexGlow }),
938
+ setSaveRequestValue: (saveRequestValue) => set({ saveRequestValue }),
939
+ setSubListRequestValue: (requestValues) => set({ subListRequestValue: requestValues }),
940
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
941
+ setSubListSpinning: (spinning) => set({ subListSpinning: spinning }),
942
+ setSubListColWidths: (colWidths) => set({ subListColWidths: colWidths }),
943
+ setSubListSelectedRowKey: (key) => set({ subListSelectedRowKey: key }),
944
+ cancelFormActive: () => set({ formActive: false, listSelectedRowKey: undefined }),
945
+ setFormActive: () => set({ formActive: true, detail: undefined, saveRequestValue: undefined }),
946
+ });
947
+
948
+ // ========== Store.subscribe ==========
949
+ useXxxStore.subscribe(
950
+ (s): Record<keyof MetaData, any> => ({
951
+ listRequestValue: s.listRequestValue,
952
+ subListRequestValue: s.subListRequestValue,
953
+ listColWidths: s.listColWidths,
954
+ listSortParams: s.listSortParams,
955
+ listSelectedRowKey: s.listSelectedRowKey,
956
+ flexGrow: s.flexGrow,
957
+ saveRequestValue: s.saveRequestValue,
958
+ detail: s.detail,
959
+ formActive: s.formActive,
960
+ subListColWidths: s.subListColWidths,
961
+ subListSelectedRowKey: s.subListSelectedRowKey,
962
+ subListData: s.subListData,
963
+ }),
964
+ getTabStoreListener<MetaData>(createState.routePath),
965
+ { equalityFn: shallow },
966
+ );
967
+ ```
968
+
969
+ ---
970
+
971
+ ### Pattern 8: List + Modal
972
+
973
+ 리스트와 모달을 사용하는 패턴입니다.
974
+
975
+ ```typescript
976
+ // ========== interface MetaData ==========
977
+ interface MetaData {
978
+ programFn?: ProgramFn;
979
+ listRequestValue: ListRequest;
980
+ listColWidths: number[];
981
+ listSortParams: AXDGSortParam[];
982
+ modalOpen: boolean;
983
+ modalData?: DtoItem;
984
+ }
985
+
986
+ // ========== interface States ==========
987
+ interface States extends MetaData {
988
+ routePath?: string;
989
+ listSpinning: boolean;
990
+ saveSpinning: boolean;
991
+ listData: AXDGDataItem<DtoItem>[];
992
+ listPage: AXDGPage;
993
+ }
994
+
995
+ // ========== const createState ==========
996
+ const createState: States = {
997
+ listRequestValue: { pageNumber: 1, pageSize: 100 },
998
+ listColWidths: [],
999
+ listSortParams: [],
1000
+ modalOpen: false,
1001
+ listSpinning: false,
1002
+ saveSpinning: false,
1003
+ listData: [],
1004
+ listPage: { currentPage: 0, totalPages: 0 },
1005
+ };
1006
+
1007
+ // ========== interface Actions ==========
1008
+ interface Actions extends PageStoreActions<States> {
1009
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
1010
+ setListColWidths: (colWidths: number[]) => void;
1011
+ setListSpinning: (spinning: boolean) => void;
1012
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
1013
+ callListApi: (request?: ListRequest) => Promise<void>;
1014
+ callSaveApi: (apiParam: SaveRequest) => Promise<void>;
1015
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
1016
+ setModalOpen: (open: boolean, data?: DtoItem) => void;
1017
+ setModalData: (data?: DtoItem) => void;
1018
+ }
1019
+
1020
+ // ========== const createActions ==========
1021
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1022
+ ...pageStoreActions(set, get, { createState }),
1023
+ syncMetadata: (s = createState) => set(s),
1024
+ onMountApp: async () => {},
1025
+
1026
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
1027
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
1028
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
1029
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
1030
+
1031
+ callListApi: async (request) => {
1032
+ if (get().listSpinning) return;
1033
+ set({ listSpinning: true });
1034
+ try {
1035
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
1036
+ const response = await XxxService.postXxxListXxx(deleteEmptyValue(apiParam));
1037
+ set({
1038
+ listRequestValue: apiParam,
1039
+ listData: response.ds.map((values) => ({ values })),
1040
+ listPage: {
1041
+ currentPage: response.page.pageNumber ?? 1,
1042
+ pageSize: response.page.pageSize ?? 0,
1043
+ totalPages: response.page.pageCount ?? 0,
1044
+ totalElements: response.page?.totalCount,
1045
+ },
1046
+ });
1047
+ } finally {
1048
+ set({ listSpinning: false });
1049
+ }
1050
+ },
1051
+
1052
+ callSaveApi: async (apiParam) => {
1053
+ try {
1054
+ await XxxService.postXxxSaveXxx(apiParam);
1055
+ await get().callListApi();
1056
+ } catch (err) {
1057
+ await errorHandling(err);
1058
+ }
1059
+ },
1060
+
1061
+ changeListPage: async (pageNumber, pageSize) => {
1062
+ await get().callListApi({ pageNumber, pageSize });
1063
+ },
1064
+
1065
+ setModalOpen: (open, data) => set({ modalOpen: open, modalData: data }),
1066
+ setModalData: (data) => set({ modalData: data }),
1067
+ });
1068
+
1069
+ // ========== Store.subscribe ==========
1070
+ useXxxStore.subscribe(
1071
+ (s): Record<keyof MetaData, any> => ({
1072
+ programFn: s.programFn,
1073
+ listRequestValue: s.listRequestValue,
1074
+ listColWidths: s.listColWidths,
1075
+ listSortParams: s.listSortParams,
1076
+ modalOpen: s.modalOpen,
1077
+ modalData: s.modalData,
1078
+ }),
1079
+ getTabStoreListener<MetaData>(createState.routePath),
1080
+ { equalityFn: shallow },
1081
+ );
1082
+ ```
1083
+
1084
+ ---
1085
+
1086
+ ## 공통 헬퍼 함수
1087
+
1088
+ | 함수 | 용도 | 사용 예시 |
1089
+ |------|------|----------|
1090
+ | `deleteEmptyValue()` | 빈 값 제거 | `deleteEmptyValue({ ...apiParam })` |
1091
+ | `dateRangeToDts()` | 날짜 범위 변환 | `dateRangeToDts(dateRange, { startDt: "YYYY-MM-DD", endDt: "YYYY-MM-DD" })` |
1092
+ | `errorHandling()` | 공통 에러 처리 | `await errorHandling(err)` |
1093
+ | `delay()` | 지연 | `await delay(300)` |
1094
+ | `isEmpty()` | 빈 값 체크 | `isEmpty(value)` |
1095
+
1096
+ ---
1097
+
1098
+ ## 공통 Setter 메서드
1099
+
1100
+ ### 리스트 관련
1101
+ ```typescript
1102
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
1103
+ setListColWidths: (colWidths: number[]) => void;
1104
+ setListSpinning: (spinning: boolean) => void;
1105
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
1106
+ setListSelectedRowKey: (key?: React.Key, detail?: DtoItem) => void;
1107
+ ```
1108
+
1109
+ ### 페이지네이션 관련
1110
+ ```typescript
1111
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
1112
+ ```
1113
+
1114
+ ### 선택/상세 관련
1115
+ ```typescript
1116
+ setSelectedItem: (selectedItem?: DtoItem) => Promise<void>;
1117
+ setDetail: (detail: DtoItem) => void;
1118
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void;
1119
+ ```
1120
+
1121
+ ### 모달 관련
1122
+ ```typescript
1123
+ setModalOpen: (open: boolean) => void;
1124
+ setModalData: (data?: DtoItem) => void;
1125
+ ```
1126
+
1127
+ ### 체크박스 관련
1128
+ ```typescript
1129
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void;
1130
+ ```
1131
+
1132
+ ### 트리 관련
1133
+ ```typescript
1134
+ setExpandedKeys: (expandedKeys: string[]) => void;
1135
+ setTreeData: (treeData: DtoItem[]) => void;
1136
+ ```
1137
+
1138
+ ### 서브 리스트 관련
1139
+ ```typescript
1140
+ setSubListRequestValue: (requestValue: SubListRequest) => void;
1141
+ setSubListColWidths: (colWidths: number[]) => void;
1142
+ setSubListSpinning: (spinning: boolean) => void;
1143
+ setSubListSelectedRowKey: (key?: React.Key) => void;
1144
+ ```
1145
+
1146
+ ### Form 관련
1147
+ ```typescript
1148
+ setFormActive: () => void;
1149
+ cancelFormActive: () => void;
1150
+ setIsDirty: (dirty: boolean) => void;
1151
+ setIsFormDirty: (dirty: boolean) => void;
1152
+ ```
1153
+
1154
+ ---
1155
+
1156
+ ## pageStoreActions 위치 규칙
1157
+
1158
+ `pageStoreActions`는 다음 두 위치 중 하나에 배치할 수 있습니다:
1159
+
1160
+ **옵션 1: 맨 앞에 배치**
1161
+ ```typescript
1162
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1163
+ ...pageStoreActions(set, get, { createState }),
1164
+ syncMetadata: (s = createState) => set(s),
1165
+ onMountApp: async () => {},
1166
+ // 나머지 액션...
1167
+ });
1168
+ ```
1169
+
1170
+ **옵션 2: 맨 뒤에 배치**
1171
+ ```typescript
1172
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1173
+ onMountApp: async () => {},
1174
+ syncMetadata: (s = createState) => set(s),
1175
+ // 나머지 액션...
1176
+ ...pageStoreActions(set, get, { createState }),
1177
+ });
1178
+ ```