@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,1835 @@
1
+ # NH-FE-B Store 패턴별 사용법 가이드
2
+
3
+ > MCP-AXBoot로 Store 생성 후 참고하여 수정할 수 있는 가이드입니다.
4
+
5
+ ---
6
+
7
+ ## 목차
8
+
9
+ 1. [패턴 분류 기준](#1-패턴-분류-기준)
10
+ 2. [Store 타입별 패턴](#2-store-타입별-패턴)
11
+ 3. [MetaData 필드 조합표](#3-metadata-필드-조합표)
12
+ 4. [States 필드별 초기값 패턴](#4-states-필드별-초기값-패턴)
13
+ 5. [createActions 패턴별 구현](#5-createactions-패턴별-구현)
14
+ 6. [Store.subscribe 패턴별 구현](#6-storesubscribe-패턴별-구현)
15
+ 7. [MCP-AXBoot 사용 후 수정 가이드](#7-mcp-axboot-사용-후-수정-가이드)
16
+
17
+ ---
18
+
19
+ ## 1. 패턴 분류 기준
20
+
21
+ Store 패턴은 다음 기준으로 분류됩니다:
22
+
23
+ 1. **데이터 구조**: 단일 리스트 vs 다중 리스트 vs 트리 vs 대시보드
24
+ 2. **기능**: 조회만 vs CRUD vs 상세보기 vs 엑셀 다운로드
25
+ 3. **선택 방식**: 단일 선택 vs 다중 선택 vs 미사용
26
+ 4. **UI 패턴**: 트리+폼, 마스터-디테일, 탭 기반 등
27
+
28
+ ---
29
+
30
+ ## 2. Store 타입별 패턴
31
+
32
+ ### Type 1: 기본 리스트형 (Basic List)
33
+
34
+ **사용처**: 단순 조회 페이지
35
+
36
+ **예시 파일**: `useDepartmentStore.ts`, `useKdhFinancialHighlightsStore.ts`
37
+
38
+ #### MetaData
39
+ ```typescript
40
+ interface MetaData {
41
+ programFn?: ProgramFn;
42
+ listRequestValue: ListRequest;
43
+ listColWidths: number[];
44
+ listSortParams: AXDGSortParam[];
45
+ }
46
+ ```
47
+
48
+ #### States
49
+ ```typescript
50
+ interface States extends MetaData {
51
+ routePath?: string;
52
+ listSpinning: boolean;
53
+ listData: AXDGDataItem<DtoItem>[];
54
+ listPage: AXDGPage;
55
+ }
56
+ ```
57
+
58
+ #### Actions
59
+ ```typescript
60
+ interface Actions extends PageStoreActions<States> {
61
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
62
+ setListColWidths: (colWidths: number[]) => void;
63
+ setListSpinning: (spinning: boolean) => void;
64
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
65
+ callListApi: (request?: ListRequest) => Promise<void>;
66
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
67
+ }
68
+ ```
69
+
70
+ #### Store.subscribe
71
+ ```typescript
72
+ useXXXStore.subscribe(
73
+ (s): Record<keyof MetaData, any> => ({
74
+ programFn: s.programFn,
75
+ listSortParams: s.listSortParams,
76
+ listRequestValue: s.listRequestValue,
77
+ listColWidths: s.listColWidths,
78
+ }),
79
+ getTabStoreListener<MetaData>(createState.routePath),
80
+ { equalityFn: shallow },
81
+ );
82
+ ```
83
+
84
+ ---
85
+
86
+ ### Type 2: 리스트 + 엑셀 다운로드 (List + Excel)
87
+
88
+ **사용처**: 조회 + 엑셀 다운로드
89
+
90
+ **예시 파일**: `useProductReviewStore.ts`, `useProductQnaAnswerTemplateStore.ts`, `useBenefitMileageHistoryStore.ts`
91
+
92
+ #### MetaData
93
+ ```typescript
94
+ interface MetaData {
95
+ programFn?: ProgramFn;
96
+ listRequestValue: ListRequest;
97
+ listColWidths: number[];
98
+ listSortParams: AXDGSortParam[];
99
+ }
100
+ ```
101
+
102
+ #### States (excelSpinning 추가)
103
+ ```typescript
104
+ interface States extends MetaData {
105
+ routePath?: string;
106
+ listSpinning: boolean;
107
+ excelSpinning: boolean; // 추가
108
+ listData: AXDGDataItem<DtoItem>[];
109
+ listPage: AXDGPage;
110
+ }
111
+ ```
112
+
113
+ #### Actions (callExcelDownloadApi 추가)
114
+ ```typescript
115
+ interface Actions extends PageStoreActions<States> {
116
+ // ... 기본 액션들
117
+ setExcelSpinning: (spinning: boolean) => void; // 추가
118
+ callExcelDownloadApi: (request?: ListRequest) => Promise<void>; // 추가
119
+ }
120
+ ```
121
+
122
+ #### Store.subscribe
123
+ ```typescript
124
+ // Type 1과 동일
125
+ ```
126
+
127
+ ---
128
+
129
+ ### Type 3: 리스트 + 상세/폼 (List + Detail)
130
+
131
+ **사용처**: 조회 + 상세보기/편집
132
+
133
+ **예시 파일**: `useMemberListStore.ts`, `useBenefitCouponStore.ts`, `useBenefitEventStore.ts`
134
+
135
+ #### MetaData
136
+ ```typescript
137
+ interface MetaData {
138
+ programFn?: ProgramFn;
139
+ listRequestValue: ListRequest;
140
+ listColWidths: number[];
141
+ listSortParams: AXDGSortParam[];
142
+ saveRequestValue: SaveRequest; // 추가
143
+ }
144
+ ```
145
+
146
+ #### States
147
+ ```typescript
148
+ interface States extends MetaData {
149
+ routePath?: string;
150
+ listSpinning: boolean;
151
+ listData: AXDGDataItem<DtoItem>[];
152
+ listPage: AXDGPage;
153
+ selectedItem?: DtoItem; // 추가
154
+ detailLoading: boolean; // 추가
155
+ checkedRowKeys: React.Key[]; // 추가 (선택사항)
156
+ }
157
+ ```
158
+
159
+ #### Actions
160
+ ```typescript
161
+ interface Actions extends PageStoreActions<States> {
162
+ // ... 기본 액션들
163
+ setSelectedItem: (selectedItem?: DtoItem) => Promise<void>; // 추가
164
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void; // 추가
165
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void; // 추가
166
+ }
167
+ ```
168
+
169
+ #### Store.subscribe
170
+ ```typescript
171
+ useXXXStore.subscribe(
172
+ (s): Record<keyof MetaData, any> => ({
173
+ programFn: s.programFn,
174
+ listSortParams: s.listSortParams,
175
+ listRequestValue: s.listRequestValue,
176
+ listColWidths: s.listColWidths,
177
+ saveRequestValue: s.saveRequestValue, // 추가
178
+ }),
179
+ getTabStoreListener<MetaData>(createState.routePath),
180
+ { equalityFn: shallow },
181
+ );
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Type 4: 마스터-디테일 (Master-Detail)
187
+
188
+ **사용처**: 마스터 리스트 선택 시 디테일 표시
189
+
190
+ **예시 파일**: `useProductMasterStore.ts`, `useOrderPaymentAllStore.ts`, `useOrderClaimReturnStore.ts`
191
+
192
+ #### MetaData
193
+ ```typescript
194
+ interface MetaData {
195
+ programFn?: ProgramFn;
196
+ listRequestValue: ListRequest;
197
+ listColWidths: number[];
198
+ listSortParams: AXDGSortParam[];
199
+ listSelectedRowKey?: React.Key; // 추가
200
+ listCheckedIndexes?: number[]; // 추가 (다중 선택시)
201
+ }
202
+ ```
203
+
204
+ #### States
205
+ ```typescript
206
+ interface States extends MetaData {
207
+ routePath?: string;
208
+ listSpinning: boolean;
209
+ listData: AXDGDataItem<DtoItem>[];
210
+ listPage: AXDGPage;
211
+ checkedRowKeys: React.Key[]; // 추가
212
+ }
213
+ ```
214
+
215
+ #### Actions
216
+ ```typescript
217
+ interface Actions extends PageStoreActions<States> {
218
+ // ... 기본 액션들
219
+ setListSelectedRowKey: (key?: React.Key) => void; // 추가
220
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void; // 추가
221
+ setListCheckedIndexes: (indexes?: number[]) => void; // 추가
222
+ }
223
+ ```
224
+
225
+ #### Store.subscribe
226
+ ```typescript
227
+ useXXXStore.subscribe(
228
+ (s): Record<keyof MetaData, any> => ({
229
+ programFn: s.programFn,
230
+ listSortParams: s.listSortParams,
231
+ listRequestValue: s.listRequestValue,
232
+ listColWidths: s.listColWidths,
233
+ listSelectedRowKey: s.listSelectedRowKey, // 추가
234
+ listCheckedIndexes: s.listCheckedIndexes, // 추가 (사용시)
235
+ }),
236
+ getTabStoreListener<MetaData>(createState.routePath),
237
+ { equalityFn: shallow },
238
+ );
239
+ ```
240
+
241
+ ---
242
+
243
+ ### Type 5: CRUD (Create + Read + Update + Delete)
244
+
245
+ **사용처**: 전체 기능 (생성, 조회, 수정, 삭제)
246
+
247
+ **예시 파일**: `useSystemBbsStore.ts`
248
+
249
+ #### MetaData
250
+ ```typescript
251
+ interface MetaData {
252
+ programFn?: ProgramFn;
253
+ listRequestValue: ListRequest;
254
+ listColWidths: number[];
255
+ listSortParams: AXDGSortParam[];
256
+ listSelectedRowKey?: React.Key;
257
+ flexGrow: number; // 추가
258
+ saveRequestValue: SaveRequest; // 추가
259
+ detail?: SaveRequest; // 추가
260
+ formActive: boolean; // 추가
261
+ listCheckedKeys: React.Key[]; // 추가
262
+ }
263
+ ```
264
+
265
+ #### States
266
+ ```typescript
267
+ interface States extends MetaData {
268
+ routePath?: string;
269
+ listSpinning: boolean;
270
+ listData: AXDGDataItem<DtoItem>[];
271
+ listPage: AXDGPage;
272
+ saveSpinning: boolean; // 추가
273
+ detailSpinning: boolean; // 추가
274
+ deleteSpinning: boolean; // 추가
275
+ }
276
+ ```
277
+
278
+ #### Actions
279
+ ```typescript
280
+ interface Actions extends PageStoreActions<States> {
281
+ // ... 기본 액션들
282
+ setListSelectedRowKey: (key?: React.Key, detail?: DtoItem) => void; // 추가
283
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void; // 추가
284
+ setSaveSpinning: (spinning: boolean) => void; // 추가
285
+ callSaveApi: (request?: SaveRequest) => Promise<void>; // 추가
286
+ callDeleteApi: (request?: RemoveRequest) => Promise<void>; // 추가
287
+ cancelFormActive: () => void; // 추가
288
+ setFormActive: () => void; // 추가
289
+ setListCheckedKeys: (checkedKeys: React.Key[]) => void; // 추가
290
+ setFlexGrow: (flexGlow: number) => void; // 추가
291
+ }
292
+ ```
293
+
294
+ #### Store.subscribe
295
+ ```typescript
296
+ useXXXStore.subscribe(
297
+ (s): Record<keyof MetaData, any> => ({
298
+ programFn: s.programFn,
299
+ listSortParams: s.listSortParams,
300
+ listRequestValue: s.listRequestValue,
301
+ listColWidths: s.listColWidths,
302
+ listSelectedRowKey: s.listSelectedRowKey,
303
+ flexGrow: s.flexGrow,
304
+ saveRequestValue: s.saveRequestValue,
305
+ detail: s.detail,
306
+ formActive: s.formActive,
307
+ listCheckedKeys: s.listCheckedKeys,
308
+ }),
309
+ getTabStoreListener<MetaData>(createState.routePath),
310
+ { equalityFn: shallow },
311
+ );
312
+ ```
313
+
314
+ ---
315
+
316
+ ### Type 6: 트리 + 폼 (Tree + Form)
317
+
318
+ **사용처**: 트리 구조 + 선택된 항목 편집
319
+
320
+ **예시 파일**: `useProductCategoryStore.ts`, `useDisplayGnbMenuStore.ts`
321
+
322
+ #### MetaData
323
+ ```typescript
324
+ interface MetaData {
325
+ programFn?: ProgramFn;
326
+ listRequestValue: ListRequest;
327
+ flexGrow: number; // 추가
328
+ selectedItem?: DtoItem; // 추가
329
+ saveRequestValues?: DtoItem; // 추가
330
+ expandedKeys: number[] | string[]; // 추가
331
+ categorySearch?: boolean; // 추가 (선택사항)
332
+ }
333
+ ```
334
+
335
+ #### States
336
+ ```typescript
337
+ interface States extends MetaData {
338
+ routePath?: string;
339
+ treeSpinning: boolean; // listSpinning 대신
340
+ treeData: DtoItem[]; // listData 대신
341
+ saveSpinning: boolean; // 추가 (저장시)
342
+ isDirty: boolean; // 추가 (수정사항 체크)
343
+ isFormDirty: boolean; // 추가
344
+ }
345
+ ```
346
+
347
+ #### Actions
348
+ ```typescript
349
+ interface Actions extends PageStoreActions<States> {
350
+ setFlexGrow: (flexGlow: number) => void; // 추가
351
+ callTreeApi: (request?: ListRequest) => Promise<void>; // callListApi 대신
352
+ setSelectedItem: (item?: DtoItem) => void; // 추가
353
+ setTreeData: (treeData: DtoItem[]) => void; // 추가
354
+ setExpandedKeys: (expandedKeys: number[]) => void; // 추가
355
+ setSaveRequestValues: (saveRequestValues: DtoItem) => void; // 추가
356
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void; // 추가
357
+ callTreeSaveApi: (treeData?: DtoItem[]) => Promise<void>; // 추가
358
+ setIsDirty: (dirty: boolean) => void; // 추가
359
+ }
360
+ ```
361
+
362
+ #### Store.subscribe
363
+ ```typescript
364
+ useXXXStore.subscribe(
365
+ (s): Record<keyof MetaData, any> => ({
366
+ programFn: s.programFn,
367
+ flexGrow: s.flexGrow,
368
+ selectedItem: s.selectedItem,
369
+ saveRequestValues: s.saveRequestValues,
370
+ expandedKeys: s.expandedKeys,
371
+ listRequestValue: s.listRequestValue,
372
+ categorySearch: s.categorySearch, // 사용시
373
+ }),
374
+ getTabStoreListener<MetaData>(createState.routePath),
375
+ { equalityFn: shallow },
376
+ );
377
+ ```
378
+
379
+ ---
380
+
381
+ ### Type 7: 대시보드 (Dashboard)
382
+
383
+ **사용처**: 여러 데이터 요약 + 여러 리스트
384
+
385
+ **예시 파일**: `useMemberDashboardStore.ts`, `useBenefitProductStore.ts`, `useDisplayMainStore.ts`
386
+
387
+ #### MetaData (다중 데이터)
388
+ ```typescript
389
+ interface MetaData {
390
+ programFn?: ProgramFn;
391
+ summaryRequestValue: SummaryRequest; // 요약 요청
392
+ listRequest01Value: ListRequest01; // 리스트1 요청
393
+ listRequest02Value: ListRequest02; // 리스트2 요청
394
+ activeTabKey: PanelType; // 활성 탭
395
+ list01SortParams: AXDGSortParam[]; // 리스트1 정렬
396
+ list02SortParams: AXDGSortParam[]; // 리스트2 정렬
397
+ }
398
+ ```
399
+
400
+ #### States
401
+ ```typescript
402
+ interface States extends MetaData {
403
+ routePath?: string;
404
+ spinning: boolean; // 또는 summarySpinning
405
+ summaryData: DtoItem; // 요약 데이터
406
+ list01Data: AXDGDataItem<DtoItem01>[]; // 리스트1 데이터
407
+ list01Page: AXDGPage;
408
+ list02Data: AXDGDataItem<DtoItem02>[]; // 리스트2 데이터
409
+ list02Page: AXDGPage;
410
+ summarySpinning: boolean;
411
+ list01Spinning: boolean;
412
+ list02Spinning: boolean;
413
+ }
414
+ ```
415
+
416
+ #### Actions
417
+ ```typescript
418
+ interface Actions extends PageStoreActions<States> {
419
+ callSummaryApi: (requestValue: SummaryRequest) => Promise<void>; // 추가
420
+ callList01Api: (requestValue: ListRequest01) => Promise<void>; // 추가
421
+ callList02Api: (requestValue: ListRequest02) => Promise<void>; // 추가
422
+ setRequestValue: (requestValue: SummaryRequest, changedValues?: SummaryRequest) => void; // 추가
423
+ setRequest01Value: (requestValue: ListRequest01, changedValues?: ListRequest01) => void; // 추가
424
+ setRequest02Value: (requestValue: ListRequest02, changedValues?: ListRequest02) => void; // 추가
425
+ setActiveTabKey: (key: PanelType) => void; // 추가
426
+ setList01SortParams: (sortParams: AXDGSortParam[]) => void; // 추가
427
+ setList02SortParams: (sortParams: AXDGSortParam[]) => void; // 추가
428
+ changeList01Page: (currentPage: number, pageSize?: number) => Promise<void>; // 추가
429
+ changeList02Page: (currentPage: number, pageSize?: number) => Promise<void>; // 추가
430
+ setListSpinning: (spinning: boolean, target?: string) => void; // 동적 스피닝
431
+ }
432
+ ```
433
+
434
+ #### Store.subscribe
435
+ ```typescript
436
+ useXXXStore.subscribe(
437
+ (s): Record<keyof MetaData, any> => ({
438
+ programFn: s.programFn,
439
+ summaryRequestValue: s.summaryRequestValue,
440
+ listRequest01Value: s.listRequest01Value,
441
+ listRequest02Value: s.listRequest02Value,
442
+ activeTabKey: s.activeTabKey,
443
+ list01SortParams: s.list01SortParams,
444
+ list02SortParams: s.list02SortParams,
445
+ }),
446
+ getTabStoreListener<MetaData>(createState.routePath),
447
+ { equalityFn: shallow },
448
+ );
449
+ ```
450
+
451
+ ---
452
+
453
+ ### Type 8: 모달 기반 (Modal Based)
454
+
455
+ **사용처**: 리스트 + 모달로 상세/작업
456
+
457
+ **예시 파일**: `useB2BProductStore.ts`, `useB2BEstimateConsultingStore.ts`
458
+
459
+ #### MetaData
460
+ ```typescript
461
+ interface MetaData {
462
+ programFn?: ProgramFn;
463
+ listRequestValue: ListRequest;
464
+ listColWidths: number[];
465
+ listSortParams: AXDGSortParam[];
466
+ listSelectedRowKey?: React.Key;
467
+ listCheckedIndexes?: number[];
468
+ }
469
+ ```
470
+
471
+ #### States
472
+ ```typescript
473
+ interface States extends MetaData {
474
+ routePath?: string;
475
+ listSpinning: boolean;
476
+ listData: AXDGDataItem<DtoItem>[];
477
+ listPage: AXDGPage;
478
+ selectedItem?: DtoItem;
479
+ detail?: DtoItem;
480
+ checkedRowKeys: React.Key[];
481
+ detailSpinning: boolean;
482
+ deleteSpinning: boolean;
483
+ modalOpen: boolean; // 추가
484
+ excelSpinning: boolean; // 추가 (엑셀시)
485
+ }
486
+ ```
487
+
488
+ #### Actions
489
+ ```typescript
490
+ interface Actions extends PageStoreActions<States> {
491
+ // ... 기본 액션들
492
+ setDetail: (detail?: DtoItem) => void; // 추가
493
+ setModalOpen: (open: boolean) => void; // 추가
494
+ callDeleteApi: (id: number) => Promise<void>; // 추가
495
+ callExcelDownloadApi: (request?: ListRequest) => Promise<void>; // 추가
496
+ }
497
+ ```
498
+
499
+ ---
500
+
501
+ ### Type 9: 상태 변경 (Status Change)
502
+
503
+ **사용처**: 다중 선택 후 일괄 상태 변경
504
+
505
+ **예시 파일**: `useProductSingleStore.ts`, `useProductPackageStore.ts`
506
+
507
+ #### MetaData
508
+ ```typescript
509
+ interface MetaData {
510
+ programFn?: ProgramFn;
511
+ listRequestValue: ListRequest;
512
+ listColWidths: number[];
513
+ listSortParams: AXDGSortParam[];
514
+ listSelectedRowKey?: React.Key;
515
+ listCheckedIndexes?: number[];
516
+ }
517
+ ```
518
+
519
+ #### States
520
+ ```typescript
521
+ interface States extends MetaData {
522
+ routePath?: string;
523
+ listSpinning: boolean;
524
+ updateSpinning: boolean; // 추가 (상태 변경 스피닝)
525
+ deleteSpinning: boolean; // 추가 (삭제 스피닝)
526
+ copySpinning: boolean; // 추가 (복사 스피닝)
527
+ listData: AXDGDataItem<DtoItem>[];
528
+ listPage: AXDGPage;
529
+ selectedItem?: DtoItem;
530
+ checkedRowKeys: React.Key[];
531
+ }
532
+ ```
533
+
534
+ #### Actions
535
+ ```typescript
536
+ interface Actions extends PageStoreActions<States> {
537
+ // ... 기본 액션들
538
+ callChangeStatusApi: (changeStatus: string) => void; // 추가
539
+ callDeleteApi: (selectedItems: string | React.Key[]) => void; // 추가
540
+ callDataCopyApi: (prdCd: string) => Promise<void>; // 추가
541
+ }
542
+ ```
543
+
544
+ ---
545
+
546
+ ### Type 10: 고정값 기반 (Fixed Value Based)
547
+
548
+ **사용처**: bbsNo, cntntsBbsKncd 등 고정값이 필요한 페이지
549
+
550
+ **예시 파일**: `useKdhFinancialHighlightsStore.ts` (bbsNo), `useCsManualStore.ts` (cntntsBbsKncd)
551
+
552
+ #### MetaData
553
+ ```typescript
554
+ interface MetaData {
555
+ programFn?: ProgramFn;
556
+ listRequestValue: ListRequest;
557
+ listColWidths: number[];
558
+ listSortParams: AXDGSortParam[];
559
+ }
560
+ ```
561
+
562
+ #### States (고정값 추가)
563
+ ```typescript
564
+ interface States extends MetaData {
565
+ routePath?: string;
566
+ listSpinning: boolean;
567
+ listData: AXDGDataItem<DtoItem>[];
568
+ listPage: AXDGPage;
569
+ bbsNo: number; // 추가 (고정값)
570
+ // 또는
571
+ cntntsBbsKncd: string; // 추가 (고정값)
572
+ // 또는
573
+ reprtTpcd: string; // 추가 (고정값)
574
+ // 또는
575
+ langKncd: string; // 추가 (고정값)
576
+ }
577
+ ```
578
+
579
+ #### createState (고정값 초기화)
580
+ ```typescript
581
+ const bbsNo = 26; // 고정값 상수 선언
582
+ const createState: States = {
583
+ bbsNo,
584
+ listRequestValue: {
585
+ bbsNo, // listRequestValue에도 포함
586
+ pageNumber: 1,
587
+ pageSize: 100,
588
+ },
589
+ // ...
590
+ };
591
+ ```
592
+
593
+ #### Actions (고정값 사용)
594
+ ```typescript
595
+ callListApi: async (request) => {
596
+ // ...
597
+ const response = await XXXService.postXXXList({
598
+ ...apiParam,
599
+ bbsNo: get().bbsNo, // 고정값 사용
600
+ // 또는
601
+ cntntsBbsKncd: get().cntntsBbsKncd,
602
+ // 또는
603
+ reprtTpcd: get().reprtTpcd,
604
+ });
605
+ // ...
606
+ }
607
+ ```
608
+
609
+ ---
610
+
611
+ ### Type 11: 전시 폼 (Display Form)
612
+
613
+ **사용처**: 메인 전시 관리 등 폼 기반 구성
614
+
615
+ **예시 파일**: `useDisplayMainStore.ts`
616
+
617
+ #### MetaData
618
+ ```typescript
619
+ interface MetaData {
620
+ programFn?: ProgramFn;
621
+ listColWidths: number[];
622
+ listSortParams: AXDGSortParam[];
623
+ flexGrow: number;
624
+ activeTabId: ActiveTabId; // 추가
625
+ mdiaClcd: string; // 추가 (미디어 코드)
626
+ formValues: DtoItem; // 추가 (폼 값)
627
+ formValueChanged: boolean; // 추가 (폼 변경 여부)
628
+ selectedSctnTpcd?: MainDspySctnType; // 추가
629
+ }
630
+ ```
631
+
632
+ #### States
633
+ ```typescript
634
+ interface States extends MetaData {
635
+ routePath?: string;
636
+ spinning: boolean; // listSpinning 대신
637
+ }
638
+ ```
639
+
640
+ #### Actions
641
+ ```typescript
642
+ interface Actions extends PageStoreActions<States> {
643
+ callDetailApi: () => Promise<void>; // 추가
644
+ callConfirmApi: () => Promise<void>; // 추가
645
+ setFormValues: (formValues: DtoItem) => void; // 추가
646
+ setActiveTabId: (activeTabId: ActiveTabId) => void; // 추가
647
+ setMdiaClcd: (mdiaClcd: string) => void; // 추가
648
+ setListColWidths: (colWidths: number[]) => void;
649
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
650
+ setFlexGrow: (flexGlow: number) => void;
651
+ setDspySortArray: (arr: MainDspySctnDspySort[]) => void; // 추가
652
+ deleteSctnBanner: (sctnBanner: MainDspyBanner) => Promise<void>; // 추가
653
+ setSelectedSctnTpcd: (selectedSctnTpcd?: MainDspySctnType) => void; // 추가
654
+ }
655
+ ```
656
+
657
+ ---
658
+
659
+ ### Type 12: 약관/정책 (Terms/Policy)
660
+
661
+ **사용처**: 약관, 정책 등 상세 내용 저장
662
+
663
+ **예시 파일**: `useSystemTermsConditionsStore.ts`
664
+
665
+ #### MetaData
666
+ ```typescript
667
+ interface MetaData {
668
+ programFn?: ProgramFn;
669
+ listRequestValue: ListRequest;
670
+ listColWidths: number[];
671
+ listSortParams: AXDGSortParam[];
672
+ detail?: SaveRequest; // 추가
673
+ }
674
+ ```
675
+
676
+ #### States
677
+ ```typescript
678
+ interface States extends MetaData {
679
+ routePath?: string;
680
+ listSpinning: boolean;
681
+ listData: AXDGDataItem<DtoItem>[];
682
+ listPage: AXDGPage;
683
+ }
684
+ ```
685
+
686
+ #### createState (detail 초기값 포함)
687
+ ```typescript
688
+ const createState: States = {
689
+ listRequestValue: {
690
+ // ...
691
+ },
692
+ listColWidths: [],
693
+ listSpinning: false,
694
+ listData: [],
695
+ listPage: { currentPage: 0, totalPages: 0 },
696
+ listSortParams: [],
697
+ detail: { // 상세 초기값
698
+ stplatDescCtnts: "",
699
+ stplatVerNo: "",
700
+ stplatPolicyTpcd: "",
701
+ dspyPdBgnDtm: "",
702
+ stplatSjNm: "",
703
+ dspyPdEndDtm: "",
704
+ useYn: TypeYn.Y,
705
+ },
706
+ };
707
+ ```
708
+
709
+ ---
710
+
711
+ ## 3. MetaData 필드 조합표
712
+
713
+ | 필드명 | 타입 | 설명 | 사용 패턴 |
714
+ |--------|------|------|----------|
715
+ | `programFn` | `ProgramFn \| undefined` | 프로그램 함수 참조 | **모든 패턴 필수** |
716
+ | `listRequestValue` | `ListRequest` | 리스트 요청값 | 리스트형 패턴 (1-5, 8-10, 12) |
717
+ | `listColWidths` | `number[]` | 컬럼 너비 배열 | 리스트형 패턴 (1-5, 8-10, 12) |
718
+ | `listSortParams` | `AXDGSortParam[]` | 정렬 파라미터 | 리스트형 패턴 (1-5, 8-10, 12) |
719
+ | `saveRequestValue` | `SaveRequest` | 저장/상세 요청값 | 3, 5, 12 |
720
+ | `selectedItem` | `DtoItem \| undefined` | 선택된 아이템 | 3, 6 |
721
+ | `listSelectedRowKey` | `React.Key \| undefined` | 선택된 행 키 | 4, 5, 8 |
722
+ | `listCheckedIndexes` | `number[] \| undefined` | 체크된 인덱스 | 4, 8, 9 |
723
+ | `checkedRowKeys` | `React.Key[]` | 체크된 행 키 | 3, 4, 8, 9 |
724
+ | `listCheckedKeys` | `React.Key[]` | 체크된 키 | 5 |
725
+ | `flexGrow` | `number` | 플렉스 성장 비율 | 5, 6, 11 |
726
+ | `detail` | `SaveRequest \| undefined` | 상세 데이터 | 5, 12 |
727
+ | `formActive` | `boolean` | 폼 활성화 상태 | 5 |
728
+ | `activeTabKey` | `string` | 활성 탭 키 | 7, 11 |
729
+ | `summaryRequestValue` | `SummaryRequest` | 요약 요청값 | 7 |
730
+ | `listRequest01Value` | `ListRequest01` | 리스트1 요청값 | 7 |
731
+ | `listRequest02Value` | `ListRequest02` | 리스트2 요청값 | 7 |
732
+ | `list01SortParams` | `AXDGSortParam[]` | 리스트1 정렬 | 7 |
733
+ | `list02SortParams` | `AXDGSortParam[]` | 리스트2 정렬 | 7 |
734
+ | `expandedKeys` | `number[] \| string[]` | 트리 확장 키 | 6 |
735
+ | `saveRequestValues` | `DtoItem` | 저장 요청값 (트리) | 6 |
736
+ | `categorySearch` | `boolean` | 카테고리 검색 여부 | 6 |
737
+ | `mdiaClcd` | `string` | 미디어 코드 | 11 |
738
+ | `formValues` | `DtoItem` | 폼 값 | 11 |
739
+ | `formValueChanged` | `boolean` | 폼 변경 여부 | 11 |
740
+ | `selectedSctnTpcd` | `string` | 선택된 섹션 타입 | 11 |
741
+ | `langKncd` | `string` | 언어 코드 | 10 |
742
+
743
+ ---
744
+
745
+ ## 4. States 필드별 초기값 패턴
746
+
747
+ ### 기본 States 필드
748
+ ```typescript
749
+ interface States {
750
+ routePath?: string; // 초기값: undefined
751
+ listSpinning: boolean; // 초기값: false
752
+ listData: AXDGDataItem<DtoItem>[]; // 초기값: []
753
+ listPage: AXDGPage; // 초기값: { currentPage: 0, totalPages: 0 }
754
+ }
755
+ ```
756
+
757
+ ### 추가 States 필드별 초기값
758
+
759
+ | 필드명 | 초기값 | 설명 |
760
+ |--------|--------|------|
761
+ | `listColWidths` | `[]` | 빈 배열 |
762
+ | `listSortParams` | `[]` | 빈 배열 |
763
+ | `listSelectedRowKey` | `""` | 빈 문자열 |
764
+ | `checkedRowKeys` | `[]` | 빈 배열 |
765
+ | `listCheckedIndexes` | `undefined` |
766
+ | `listCheckedKeys` | `[]` | 빈 배열 |
767
+ | `selectedItem` | `undefined` |
768
+ | `detail` | `undefined` |
769
+ | `detailLoading` | `false` |
770
+ | `saveRequestValue` | `{}` 또는 상세 초기값 |
771
+ | `formActive` | `false` |
772
+ | `flexGrow` | `40`, `400`, `450` 등 (레이아웃에 따라) |
773
+ | `expandedKeys` | `[]` |
774
+ | `saveRequestValues` | `undefined` |
775
+ | `categorySearch` | `false` |
776
+ | `modalOpen` | `false` |
777
+ | `excelSpinning` | `false` |
778
+ | `saveSpinning` | `false` |
779
+ | `deleteSpinning` | `false` |
780
+ | `updateSpinning` | `false` |
781
+ | `copySpinning` | `false` |
782
+ | `treeSpinning` | `false` |
783
+ | `activeTabId` | `"banner"`, `"pg1"` 등 |
784
+ | `mdiaClcd` | `"10"` (PC) |
785
+ | `isDirty` | `false` |
786
+ | `isFormDirty` | `false` |
787
+ | `formValueChanged` | `false` |
788
+ | `statData` | `[]` |
789
+ | `statSpinning` | `false` |
790
+ | `bbsNo` | `26` 등 (고정값) |
791
+ | `cntntsBbsKncd` | `"MAN"` 등 (고정값) |
792
+ | `reprtTpcd` | `"20"` 등 (고정값) |
793
+ | `langKncd` | `"KR"` (기본값) |
794
+
795
+ ### createState 초기화 패턴 예시
796
+
797
+ ```typescript
798
+ const createState: States = {
799
+ // 1) 고정값이 있는 경우 (Type 10)
800
+ bbsNo: 26,
801
+ cntntsBbsKncd: "MAN",
802
+ reprtTpcd: "20",
803
+ langKncd: "KR",
804
+
805
+ // 2) 리스트 요청값 초기화
806
+ listRequestValue: {
807
+ pageNumber: 1,
808
+ pageSize: 100,
809
+ // 추가 검색조건...
810
+ },
811
+
812
+ // 3) UI 상태 초기화
813
+ listColWidths: [],
814
+ listSpinning: false,
815
+ listData: [],
816
+ listPage: { currentPage: 0, totalPages: 0 },
817
+ listSortParams: [],
818
+
819
+ // 4) 선택 관련 초기화
820
+ listSelectedRowKey: "",
821
+ checkedRowKeys: [],
822
+ listCheckedIndexes: undefined,
823
+
824
+ // 5) 스피닝 초기화
825
+ excelSpinning: false,
826
+ saveSpinning: false,
827
+ deleteSpinning: false,
828
+ updateSpinning: false,
829
+ copySpinning: false,
830
+ detailSpinning: false,
831
+ detailLoading: false,
832
+
833
+ // 6) 폼/상세 초기화
834
+ saveRequestValue: {},
835
+ detail: undefined,
836
+ selectedItem: undefined,
837
+ formActive: false,
838
+ modalOpen: false,
839
+
840
+ // 7) 트리 관련 초기화
841
+ treeData: [],
842
+ treeSpinning: false,
843
+ expandedKeys: [],
844
+
845
+ // 8) 대시보드 관련 초기화
846
+ activeTabKey: "pg1",
847
+ summaryData: {},
848
+ list01Data: [],
849
+ list01Page: { currentPage: 0, totalPages: 0 },
850
+ list02Data: [],
851
+ list02Page: { currentPage: 0, totalPages: 0 },
852
+ summarySpinning: false,
853
+ list01Spinning: false,
854
+ list02Spinning: false,
855
+
856
+ // 9) 기타 초기화
857
+ flexGrow: 400,
858
+ isDirty: false,
859
+ isFormDirty: false,
860
+ formValueChanged: false,
861
+ };
862
+ ```
863
+
864
+ ---
865
+
866
+ ## 5. createActions 패턴별 구현
867
+
868
+ ### 공통 필수 액션 (모든 패턴)
869
+ ```typescript
870
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
871
+ // 1) 메타데이터 동기화
872
+ syncMetadata: (s = createState) => set(s),
873
+
874
+ // 2) 앱 마운트 시 실행
875
+ onMountApp: async () => {
876
+ // onDidMount and store initialized
877
+ },
878
+
879
+ // 3) Setter들
880
+ setListRequestValue: (requestValues) => {
881
+ set({ listRequestValue: requestValues });
882
+ // 또는 병합 방식
883
+ set({
884
+ listRequestValue: {
885
+ ...get().listRequestValue,
886
+ ...requestValues,
887
+ },
888
+ });
889
+ },
890
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
891
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
892
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
893
+
894
+ // 4) pageStoreActions 확장 (항상 마지막에)
895
+ ...pageStoreActions(set, get, { createState }),
896
+ });
897
+ ```
898
+
899
+ ### Type 1: 기본 리스트형 전체 구현
900
+ ```typescript
901
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
902
+ ...pageStoreActions(set, get, { createState }),
903
+ syncMetadata: (s = createState) => set(s),
904
+ onMountApp: async () => {},
905
+ setListRequestValue: (requestValues) => {
906
+ set({ listRequestValue: requestValues });
907
+ },
908
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
909
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
910
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
911
+
912
+ callListApi: async (request) => {
913
+ if (get().listSpinning) return;
914
+ set({ listSpinning: true });
915
+
916
+ try {
917
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
918
+ const response = await XXXService.postXXXList(
919
+ deleteEmptyValue({ ...apiParam })
920
+ );
921
+
922
+ set({
923
+ listRequestValue: apiParam,
924
+ listData: response.ds.map((values) => ({ values })),
925
+ listPage: {
926
+ currentPage: response.page.pageNumber ?? 1,
927
+ pageSize: response.page.pageSize ?? 0,
928
+ totalPages: response.page.pageCount ?? 0,
929
+ totalElements: response.page?.totalCount,
930
+ },
931
+ });
932
+ } finally {
933
+ set({ listSpinning: false });
934
+ }
935
+ },
936
+
937
+ changeListPage: async (pageNumber, pageSize) => {
938
+ await get().callListApi({ pageNumber, pageSize });
939
+ },
940
+ });
941
+ ```
942
+
943
+ ### Type 2: 엑셀 다운로드 추가
944
+ ```typescript
945
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
946
+ // ... 기본 액션들
947
+
948
+ setExcelSpinning: (spinning) => set({ excelSpinning: spinning }),
949
+
950
+ callExcelDownloadApi: async (request) => {
951
+ if (get().excelSpinning) return;
952
+ set({ excelSpinning: true });
953
+
954
+ try {
955
+ const { pageNumber, pageSize, ...restParams }: ListRequest = {
956
+ ...get().listRequestValue,
957
+ ...request,
958
+ };
959
+
960
+ await XXXService.postXXXListExcel(
961
+ deleteEmptyValue({ ...restParams })
962
+ );
963
+ } finally {
964
+ set({ excelSpinning: false });
965
+ }
966
+ },
967
+ });
968
+ ```
969
+
970
+ ### Type 3: 상세/폼 관련 액션
971
+ ```typescript
972
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
973
+ // ... 기본 액션들
974
+
975
+ setSelectedItem: async (selectedItem) => {
976
+ set({ selectedItem, detailLoading: true });
977
+
978
+ if (!selectedItem || selectedItem["__status__"] === "C") {
979
+ set({ saveRequestValue: {} });
980
+ }
981
+
982
+ await delay(300); // 선택사항
983
+
984
+ set({ detailLoading: false });
985
+ },
986
+
987
+ setSaveRequestValue: (saveRequestValue) => set({ saveRequestValue }),
988
+
989
+ setCheckedRowKeys: (checkedRowKeys) => set({ checkedRowKeys }),
990
+ });
991
+ ```
992
+
993
+ ### Type 4: 마스터-디테일 액션
994
+ ```typescript
995
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
996
+ // ... 기본 액션들
997
+
998
+ setListSelectedRowKey: (key) => {
999
+ set({ listSelectedRowKey: key });
1000
+ },
1001
+
1002
+ setCheckedRowKeys: (checkedRowKeys) => set({ checkedRowKeys }),
1003
+
1004
+ setListCheckedIndexes: (indexes) => set({ listCheckedIndexes: indexes }),
1005
+ });
1006
+ ```
1007
+
1008
+ ### Type 5: CRUD 액션
1009
+ ```typescript
1010
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1011
+ // ... 기본 액션들
1012
+
1013
+ setListSelectedRowKey: async (key, detail) => {
1014
+ if (detail) {
1015
+ set({ listSelectedRowKey: key, detail, saveRequestValue: { ...detail } });
1016
+ } else {
1017
+ set({ listSelectedRowKey: undefined, detail: undefined, saveRequestValue: undefined });
1018
+ }
1019
+ },
1020
+
1021
+ setSaveRequestValue: (saveRequestValue) => {
1022
+ set({ saveRequestValue });
1023
+ },
1024
+
1025
+ setSaveSpinning: (saveSpinning) => set({ saveSpinning: saveSpinning }),
1026
+
1027
+ callSaveApi: async (request) => {
1028
+ if (get().saveSpinning) return;
1029
+ set({ saveSpinning: true });
1030
+
1031
+ try {
1032
+ const apiParam: SaveRequest = {
1033
+ ...get().saveRequestValue,
1034
+ ...request,
1035
+ };
1036
+
1037
+ apiParam.__status__ = get().listSelectedRowKey ? "U" : "C";
1038
+
1039
+ const { rs } = await XXXService.postXXXSave(apiParam);
1040
+ set({ saveRequestValue: rs, detail: rs });
1041
+ } finally {
1042
+ set({ saveSpinning: false });
1043
+ }
1044
+ },
1045
+
1046
+ cancelFormActive: () => {
1047
+ set({ formActive: false, listSelectedRowKey: undefined });
1048
+ },
1049
+
1050
+ setFormActive: () => {
1051
+ set({ formActive: true, detail: undefined, saveRequestValue: undefined });
1052
+ },
1053
+
1054
+ callDeleteApi: async (request) => {
1055
+ if (get().deleteSpinning) return;
1056
+ set({ deleteSpinning: true });
1057
+
1058
+ try {
1059
+ const apiParam: RemoveRequest = {
1060
+ ...get().saveRequestValue,
1061
+ ...request,
1062
+ __status__: "D",
1063
+ };
1064
+
1065
+ await XXXService.postXXXDelete(apiParam);
1066
+ } finally {
1067
+ set({ deleteSpinning: false });
1068
+ }
1069
+ },
1070
+
1071
+ setListCheckedKeys: (checkedKeys) => {
1072
+ set({ listCheckedKeys: checkedKeys });
1073
+ },
1074
+
1075
+ setFlexGrow: (flexGlow) => {
1076
+ set({ flexGrow: flexGlow });
1077
+ },
1078
+ });
1079
+ ```
1080
+
1081
+ ### Type 6: 트리 관련 액션
1082
+ ```typescript
1083
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1084
+ onMountApp: async () => {},
1085
+ syncMetadata: (s = createState) => set(s),
1086
+
1087
+ setFlexGrow: (flexGrow) => set({ flexGrow }),
1088
+
1089
+ callTreeApi: async (request) => {
1090
+ set({ treeSpinning: true });
1091
+ try {
1092
+ // 검색어가 있으면 리스트 조회
1093
+ if (request?.ctgrNm && request.ctgrNm !== "") {
1094
+ const { ds: list } = await CategoryService.postCategoryListCtgr({
1095
+ ctgrNm: request.ctgrNm,
1096
+ pathDelim: " > ",
1097
+ });
1098
+ set({ listData: list, categorySearch: true });
1099
+ } else {
1100
+ // 없으면 트리 조회
1101
+ const { ds: tree } = await CategoryService.postCategoryListCtgrTree({});
1102
+ set({ treeData: tree, categorySearch: false });
1103
+ }
1104
+ } finally {
1105
+ set({ treeSpinning: false });
1106
+ }
1107
+ },
1108
+
1109
+ setSelectedItem: (item) => {
1110
+ set({ selectedItem: item, saveRequestValues: { ...item } });
1111
+ },
1112
+
1113
+ setTreeData: (treeData) => {
1114
+ set({ treeData });
1115
+ },
1116
+
1117
+ setExpandedKeys: (expandedKeys) => {
1118
+ set({ expandedKeys });
1119
+ },
1120
+
1121
+ setSaveRequestValues: (saveRequestValues) => {
1122
+ set({ saveRequestValues });
1123
+ },
1124
+
1125
+ setListRequestValue: (requestValues) => {
1126
+ set({ listRequestValue: requestValues });
1127
+ },
1128
+
1129
+ setCategorySearch: (search) => set({ categorySearch: search }),
1130
+
1131
+ ...pageStoreActions(set, get, { createState }),
1132
+ });
1133
+ ```
1134
+
1135
+ ### Type 7: 대시보드 액션
1136
+ ```typescript
1137
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1138
+ syncMetadata: (s = createState) => set(s),
1139
+ onMountApp: async () => {},
1140
+
1141
+ callSummaryApi: async (requestValue) => {
1142
+ if (get().summarySpinning) return;
1143
+ set({ summarySpinning: true });
1144
+
1145
+ try {
1146
+ const apiParam: SummaryRequest = { ...get().summaryRequestValue, ...requestValue };
1147
+ const response = await MemberService.postMemberStatsDaBdMember(
1148
+ deleteEmptyValue({ ...apiParam })
1149
+ );
1150
+
1151
+ set({ summaryData: response.rs });
1152
+ } finally {
1153
+ set({ summarySpinning: false });
1154
+ }
1155
+ },
1156
+
1157
+ callList01Api: async (requestValue) => {
1158
+ if (get().list01Spinning) return;
1159
+ set({ list01Spinning: true });
1160
+
1161
+ try {
1162
+ const apiParam: ListRequest01 = { ...get().listRequest01Value, ...requestValue };
1163
+ const response = await MemberService.postMemberListDaBdMember(
1164
+ deleteEmptyValue({ ...apiParam })
1165
+ );
1166
+
1167
+ set({
1168
+ listRequest01Value: apiParam,
1169
+ list01Data: response.ds.map((values) => ({ values })),
1170
+ list01Page: {
1171
+ currentPage: response.page.pageNumber ?? 1,
1172
+ pageSize: response.page.pageSize ?? 0,
1173
+ totalPages: response.page.pageCount ?? 0,
1174
+ totalElements: response.page?.totalCount,
1175
+ },
1176
+ });
1177
+ } finally {
1178
+ set({ list01Spinning: false });
1179
+ }
1180
+ },
1181
+
1182
+ callList02Api: async (requestValue) => {
1183
+ // callList01Api와 유사
1184
+ },
1185
+
1186
+ setRequestValue: (requestValues) => set({ summaryRequestValue: requestValues }),
1187
+ setRequest01Value: (requestValues) => set({ listRequest01Value: requestValues }),
1188
+ setRequest02Value: (requestValues) => set({ listRequest02Value: requestValues }),
1189
+ setActiveTabKey: (key) => set({ activeTabKey: key }),
1190
+ setList01SortParams: (sortParams) => set({ list01SortParams: sortParams }),
1191
+ setList02SortParams: (sortParams) => set({ list02SortParams: sortParams }),
1192
+
1193
+ changeList01Page: async (pageNumber, pageSize) => {
1194
+ await get().callList01Api({ pageNumber, pageSize });
1195
+ },
1196
+
1197
+ changeList02Page: async (pageNumber, pageSize) => {
1198
+ await get().callList02Api({ pageNumber, pageSize });
1199
+ },
1200
+
1201
+ setListSpinning: (spinning, target = "01") =>
1202
+ set({ [`list${target}Spinning`]: spinning }),
1203
+
1204
+ ...pageStoreActions(set, get, { createState }),
1205
+ });
1206
+ ```
1207
+
1208
+ ### Type 9: 상태 변경 액션
1209
+ ```typescript
1210
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1211
+ // ... 기본 액션들
1212
+
1213
+ callChangeStatusApi: async (changeStatus) => {
1214
+ const selectedPrdCds = get().checkedRowKeys as string[];
1215
+ if (!selectedPrdCds || selectedPrdCds.length === 0) return;
1216
+
1217
+ if (get().updateSpinning) return;
1218
+ set({ updateSpinning: true });
1219
+
1220
+ try {
1221
+ switch (changeStatus) {
1222
+ case "전시":
1223
+ await ProductService.postProductSavePrdDspyY({ prdCds: selectedPrdCds });
1224
+ break;
1225
+ case "비전시":
1226
+ await ProductService.postProductSavePrdDspN({ prdCds: selectedPrdCds });
1227
+ break;
1228
+ case "판매중":
1229
+ await ProductService.postProductSavePrdSleSttus10({ prdCds: selectedPrdCds });
1230
+ break;
1231
+ case "품절":
1232
+ await ProductService.postProductSavePrdSleSttus20({ prdCds: selectedPrdCds });
1233
+ break;
1234
+ case "판매중지":
1235
+ await ProductService.postProductSavePrdSleSttus50({ prdCds: selectedPrdCds });
1236
+ break;
1237
+ default:
1238
+ console.warn("알 수 없는 상태 변경 요청:", changeStatus);
1239
+ }
1240
+ } finally {
1241
+ set({ updateSpinning: false });
1242
+ }
1243
+ },
1244
+
1245
+ callDeleteApi: async (selectedItems) => {
1246
+ if (get().deleteSpinning) return;
1247
+ set({ deleteSpinning: true });
1248
+
1249
+ try {
1250
+ const apiParam = Array.isArray(selectedItems)
1251
+ ? { prdCds: selectedItems as string[] }
1252
+ : { prdCd: selectedItems };
1253
+
1254
+ await ProductService.postProductDeletePrd(apiParam);
1255
+ get().setCheckedRowKeys([]);
1256
+ } finally {
1257
+ set({ deleteSpinning: false });
1258
+ }
1259
+ },
1260
+
1261
+ callDataCopyApi: async (prdCd) => {
1262
+ if (get().copySpinning) return;
1263
+ set({ copySpinning: true });
1264
+
1265
+ try {
1266
+ const response = await ProductService.postProductCopyPrd({ prdCd });
1267
+ const copiedData = response.rs;
1268
+
1269
+ set({ selectedItem: { ...copiedData, __status__: "D" } });
1270
+ } finally {
1271
+ set({ copySpinning: false });
1272
+ }
1273
+ },
1274
+ });
1275
+ ```
1276
+
1277
+ ### Type 10: 고정값 사용 액션
1278
+ ```typescript
1279
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1280
+ syncMetadata: (s = createState) => set(s),
1281
+ onMountApp: async () => {},
1282
+ setListRequestValue: (requestValues) => {
1283
+ set({
1284
+ listRequestValue: {
1285
+ ...get().listRequestValue,
1286
+ ...requestValues,
1287
+ },
1288
+ });
1289
+ },
1290
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
1291
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
1292
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
1293
+
1294
+ callListApi: async (request) => {
1295
+ if (get().listSpinning) return;
1296
+ set({ listSpinning: true });
1297
+
1298
+ try {
1299
+ const apiParam: ListRequest = {
1300
+ ...get().listRequestValue,
1301
+ ...request,
1302
+ bbsNo: get().bbsNo, // 고정값 사용
1303
+ };
1304
+
1305
+ const response = await BbsPostService.postBbsPostList(
1306
+ deleteEmptyValue({ ...apiParam })
1307
+ );
1308
+
1309
+ set({
1310
+ listData: response.ds.map((values) => ({ values })),
1311
+ listPage: {
1312
+ currentPage: response.page.pageNumber ?? 1,
1313
+ pageSize: response.page.pageSize ?? 0,
1314
+ totalPages: response.page.pageCount ?? 0,
1315
+ totalElements: response.page?.totalCount,
1316
+ },
1317
+ });
1318
+ } finally {
1319
+ set({ listSpinning: false });
1320
+ }
1321
+ },
1322
+
1323
+ changeListPage: async (pageNumber, pageSize) => {
1324
+ await get().callListApi({ pageNumber, pageSize });
1325
+ },
1326
+
1327
+ ...pageStoreActions(set, get, { createState }),
1328
+ });
1329
+ ```
1330
+
1331
+ ---
1332
+
1333
+ ## 6. Store.subscribe 패턴별 구현
1334
+
1335
+ ### Type 1: 기본 (가장 일반적)
1336
+ ```typescript
1337
+ useXXXStore.subscribe(
1338
+ (s): Record<keyof MetaData, any> => ({
1339
+ programFn: s.programFn,
1340
+ listSortParams: s.listSortParams,
1341
+ listRequestValue: s.listRequestValue,
1342
+ listColWidths: s.listColWidths,
1343
+ }),
1344
+ getTabStoreListener<MetaData>(createState.routePath),
1345
+ { equalityFn: shallow },
1346
+ );
1347
+ ```
1348
+
1349
+ ### Type 2: 엑셀 추가 (Type 1과 동일)
1350
+ ```typescript
1351
+ // Type 1과 동일
1352
+ ```
1353
+
1354
+ ### Type 3: 상세/폼 추가
1355
+ ```typescript
1356
+ useXXXStore.subscribe(
1357
+ (s): Record<keyof MetaData, any> => ({
1358
+ programFn: s.programFn,
1359
+ listSortParams: s.listSortParams,
1360
+ listRequestValue: s.listRequestValue,
1361
+ listColWidths: s.listColWidths,
1362
+ saveRequestValue: s.saveRequestValue, // 추가
1363
+ }),
1364
+ getTabStoreListener<MetaData>(createState.routePath),
1365
+ { equalityFn: shallow },
1366
+ );
1367
+ ```
1368
+
1369
+ ### Type 4: 마스터-디테일
1370
+ ```typescript
1371
+ useXXXStore.subscribe(
1372
+ (s): Record<keyof MetaData, any> => ({
1373
+ programFn: s.programFn,
1374
+ listSortParams: s.listSortParams,
1375
+ listRequestValue: s.listRequestValue,
1376
+ listColWidths: s.listColWidths,
1377
+ listSelectedRowKey: s.listSelectedRowKey, // 추가
1378
+ listCheckedIndexes: s.listCheckedIndexes, // 선택사항
1379
+ }),
1380
+ getTabStoreListener<MetaData>(createState.routePath),
1381
+ { equalityFn: shallow },
1382
+ );
1383
+ ```
1384
+
1385
+ ### Type 5: CRUD
1386
+ ```typescript
1387
+ useXXXStore.subscribe(
1388
+ (s): Record<keyof MetaData, any> => ({
1389
+ programFn: s.programFn,
1390
+ listSortParams: s.listSortParams,
1391
+ listRequestValue: s.listRequestValue,
1392
+ listColWidths: s.listColWidths,
1393
+ listSelectedRowKey: s.listSelectedRowKey,
1394
+ flexGrow: s.flexGrow,
1395
+ saveRequestValue: s.saveRequestValue,
1396
+ detail: s.detail,
1397
+ formActive: s.formActive,
1398
+ listCheckedKeys: s.listCheckedKeys,
1399
+ }),
1400
+ getTabStoreListener<MetaData>(createState.routePath),
1401
+ { equalityFn: shallow },
1402
+ );
1403
+ ```
1404
+
1405
+ ### Type 6: 트리 + 폼
1406
+ ```typescript
1407
+ useXXXStore.subscribe(
1408
+ (s): Record<keyof MetaData, any> => ({
1409
+ programFn: s.programFn,
1410
+ flexGrow: s.flexGrow,
1411
+ selectedItem: s.selectedItem,
1412
+ saveRequestValues: s.saveRequestValues,
1413
+ expandedKeys: s.expandedKeys,
1414
+ listRequestValue: s.listRequestValue,
1415
+ categorySearch: s.categorySearch, // 선택사항
1416
+ }),
1417
+ getTabStoreListener<MetaData>(createState.routePath),
1418
+ { equalityFn: shallow },
1419
+ );
1420
+ ```
1421
+
1422
+ ### Type 7: 대시보드
1423
+ ```typescript
1424
+ useXXXStore.subscribe(
1425
+ (s): Record<keyof MetaData, any> => ({
1426
+ programFn: s.programFn,
1427
+ summaryRequestValue: s.summaryRequestValue,
1428
+ listRequest01Value: s.listRequest01Value,
1429
+ listRequest02Value: s.listRequest02Value,
1430
+ activeTabKey: s.activeTabKey,
1431
+ list01SortParams: s.list01SortParams,
1432
+ list02SortParams: s.list02SortParams,
1433
+ }),
1434
+ getTabStoreListener<MetaData>(createState.routePath),
1435
+ { equalityFn: shallow },
1436
+ );
1437
+ ```
1438
+
1439
+ ### Type 8: 모달 기반
1440
+ ```typescript
1441
+ // Type 4와 동일
1442
+ ```
1443
+
1444
+ ### Type 9: 상태 변경
1445
+ ```typescript
1446
+ // Type 4와 동일
1447
+ ```
1448
+
1449
+ ### Type 10: 고정값
1450
+ ```typescript
1451
+ // MetaData에 고정값이 없는 경우 Type 1과 동일
1452
+ ```
1453
+
1454
+ ### Type 11: 전시 폼
1455
+ ```typescript
1456
+ useXXXStore.subscribe(
1457
+ (s): Record<keyof MetaData, any> => ({
1458
+ programFn: s.programFn,
1459
+ listColWidths: s.listColWidths,
1460
+ listSortParams: s.listSortParams,
1461
+ activeTabId: s.activeTabId,
1462
+ mdiaClcd: s.mdiaClcd,
1463
+ flexGrow: s.flexGrow,
1464
+ formValues: s.formValues,
1465
+ formValueChanged: s.formValueChanged,
1466
+ selectedSctnTpcd: s.selectedSctnTpcd,
1467
+ }),
1468
+ getTabStoreListener<MetaData>(createState.routePath),
1469
+ { equalityFn: shallow },
1470
+ );
1471
+ ```
1472
+
1473
+ ### Type 12: 약관/정책
1474
+ ```typescript
1475
+ useXXXStore.subscribe(
1476
+ (s): Record<keyof MetaData, any> => ({
1477
+ programFn: s.programFn,
1478
+ listSortParams: s.listSortParams,
1479
+ listRequestValue: s.listRequestValue,
1480
+ listColWidths: s.listColWidths,
1481
+ detail: s.detail, // 추가
1482
+ }),
1483
+ getTabStoreListener<MetaData>(createState.routePath),
1484
+ { equalityFn: shallow },
1485
+ );
1486
+ ```
1487
+
1488
+ ---
1489
+
1490
+ ## 7. MCP-AXBoot 사용 후 수정 가이드
1491
+
1492
+ ### Step 1: MCP-AXBoot로 Store 생성
1493
+
1494
+ ```bash
1495
+ # MCP-AXBoot를 사용하여 기본 Store 생성
1496
+ mcp-axboot generate-store --interface-path="/path/to/XXXRepository.ts" --output-path="/path/to/useXXXStore.ts"
1497
+ ```
1498
+
1499
+ ### Step 2: 생성된 Store 확인 및 수정
1500
+
1501
+ #### 2.1 Type 확인 후 필요한 필드 추가
1502
+
1503
+ MCP-AXBoot로 생성된 Store는 기본 패턴(Type 1)으로 생성됩니다. 필요한 타입에 따라 다음 필드를 추가하세요:
1504
+
1505
+ | 추가가 필요한 경우 | 추가할 필드 |
1506
+ |------------------|------------|
1507
+ | 엑셀 다운로드 필요 | `excelSpinning`, `callExcelDownloadApi`, `setExcelSpinning` |
1508
+ | 상세/폼 필요 | `saveRequestValue`, `selectedItem`, `detailLoading`, `setSelectedItem`, `setSaveRequestValue` |
1509
+ | 마스터-디테일 | `listSelectedRowKey`, `setCheckedRowKeys`, `setListSelectedRowKey` |
1510
+ | CRUD | Type 5의 모든 필드 추가 |
1511
+ | 트리 구조 | `treeData`, `treeSpinning`, `expandedKeys`, `selectedItem`, `saveRequestValues` |
1512
+ | 대시보드 | 다중 요청/데이터/스피닝 필드 추가 |
1513
+ | 고정값 필요 | `bbsNo`, `cntntsBbsKncd` 등 추가 |
1514
+
1515
+ #### 2.2 MetaData 수정
1516
+
1517
+ ```typescript
1518
+ // 수정 전 (MCP-AXBoot 생성 기본값)
1519
+ interface MetaData {
1520
+ programFn?: ProgramFn;
1521
+ listRequestValue: ListRequest;
1522
+ listColWidths: number[];
1523
+ listSortParams: AXDGSortParam[];
1524
+ }
1525
+
1526
+ // 수정 후 (예: 상세/폼 추가)
1527
+ interface MetaData {
1528
+ programFn?: ProgramFn;
1529
+ listRequestValue: ListRequest;
1530
+ listColWidths: number[];
1531
+ listSortParams: AXDGSortParam[];
1532
+ saveRequestValue: SaveRequest; // 추가
1533
+ }
1534
+ ```
1535
+
1536
+ #### 2.3 States 수정
1537
+
1538
+ ```typescript
1539
+ // 수정 전
1540
+ interface States extends MetaData {
1541
+ routePath?: string;
1542
+ listSpinning: boolean;
1543
+ listData: AXDGDataItem<DtoItem>[];
1544
+ listPage: AXDGPage;
1545
+ }
1546
+
1547
+ // 수정 후 (예: 상세/폼 추가)
1548
+ interface States extends MetaData {
1549
+ routePath?: string;
1550
+ listSpinning: boolean;
1551
+ listData: AXDGDataItem<DtoItem>[];
1552
+ listPage: AXDGPage;
1553
+ selectedItem?: DtoItem; // 추가
1554
+ detailLoading: boolean; // 추가
1555
+ checkedRowKeys: React.Key[]; // 추가
1556
+ }
1557
+ ```
1558
+
1559
+ #### 2.4 createState 수정
1560
+
1561
+ ```typescript
1562
+ // 수정 전
1563
+ const createState: States = {
1564
+ listRequestValue: {
1565
+ pageNumber: 1,
1566
+ pageSize: 100,
1567
+ },
1568
+ listColWidths: [],
1569
+ listSpinning: false,
1570
+ listData: [],
1571
+ listPage: { currentPage: 0, totalPages: 0 },
1572
+ listSortParams: [],
1573
+ };
1574
+
1575
+ // 수정 후 (예: 상세/폼 추가)
1576
+ const createState: States = {
1577
+ listRequestValue: {
1578
+ pageNumber: 1,
1579
+ pageSize: 100,
1580
+ },
1581
+ listColWidths: [],
1582
+ listSpinning: false,
1583
+ listData: [],
1584
+ listPage: { currentPage: 0, totalPages: 0 },
1585
+ listSortParams: [],
1586
+ saveRequestValue: {}, // 추가
1587
+ selectedItem: undefined, // 추가
1588
+ detailLoading: false, // 추가
1589
+ checkedRowKeys: [], // 추가
1590
+ };
1591
+ ```
1592
+
1593
+ #### 2.5 Actions 인터페이스 수정
1594
+
1595
+ ```typescript
1596
+ // 수정 전
1597
+ interface Actions extends PageStoreActions<States> {
1598
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
1599
+ setListColWidths: (colWidths: number[]) => void;
1600
+ setListSpinning: (spinning: boolean) => void;
1601
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
1602
+ callListApi: (request?: ListRequest) => Promise<void>;
1603
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
1604
+ }
1605
+
1606
+ // 수정 후 (예: 상세/폼 추가)
1607
+ interface Actions extends PageStoreActions<States> {
1608
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
1609
+ setListColWidths: (colWidths: number[]) => void;
1610
+ setListSpinning: (spinning: boolean) => void;
1611
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
1612
+ callListApi: (request?: ListRequest) => Promise<void>;
1613
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
1614
+ setSelectedItem: (selectedItem?: DtoItem) => Promise<void>; // 추가
1615
+ setSaveRequestValue: (saveRequestValue: SaveRequest) => void; // 추가
1616
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void; // 추가
1617
+ }
1618
+ ```
1619
+
1620
+ #### 2.6 createActions 수정
1621
+
1622
+ ```typescript
1623
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1624
+ // ... 기존 액션들
1625
+
1626
+ // 추가할 액션들
1627
+ setSelectedItem: async (selectedItem) => {
1628
+ set({ selectedItem, detailLoading: true });
1629
+
1630
+ if (!selectedItem || selectedItem["__status__"] === "C") {
1631
+ set({ saveRequestValue: {} });
1632
+ }
1633
+
1634
+ await delay(300);
1635
+
1636
+ set({ detailLoading: false });
1637
+ },
1638
+
1639
+ setSaveRequestValue: (saveRequestValue) => set({ saveRequestValue }),
1640
+ setCheckedRowKeys: (checkedRowKeys) => set({ checkedRowKeys }),
1641
+ });
1642
+ ```
1643
+
1644
+ #### 2.7 Store.subscribe 수정
1645
+
1646
+ ```typescript
1647
+ // 수정 전
1648
+ useXXXStore.subscribe(
1649
+ (s): Record<keyof MetaData, any> => ({
1650
+ programFn: s.programFn,
1651
+ listSortParams: s.listSortParams,
1652
+ listRequestValue: s.listRequestValue,
1653
+ listColWidths: s.listColWidths,
1654
+ }),
1655
+ getTabStoreListener<MetaData>(createState.routePath),
1656
+ { equalityFn: shallow },
1657
+ );
1658
+
1659
+ // 수정 후 (예: 상세/폼 추가)
1660
+ useXXXStore.subscribe(
1661
+ (s): Record<keyof MetaData, any> => ({
1662
+ programFn: s.programFn,
1663
+ listSortParams: s.listSortParams,
1664
+ listRequestValue: s.listRequestValue,
1665
+ listColWidths: s.listColWidths,
1666
+ saveRequestValue: s.saveRequestValue, // 추가
1667
+ }),
1668
+ getTabStoreListener<MetaData>(createState.routePath),
1669
+ { equalityFn: shallow },
1670
+ );
1671
+ ```
1672
+
1673
+ ### Step 3: 패턴별 체크리스트
1674
+
1675
+ #### Type 1: 기본 리스트형
1676
+ - [ ] MetaData: 기본 4개 필드 확인
1677
+ - [ ] States: 기본 States 확인
1678
+ - [ ] Actions: 기본 6개 액션 확인
1679
+ - [ ] Store.subscribe: 기본 패턴 확인
1680
+
1681
+ #### Type 2: 리스트 + 엑셀
1682
+ - [ ] States: `excelSpinning` 추가
1683
+ - [ ] Actions: `setExcelSpinning`, `callExcelDownloadApi` 추가
1684
+
1685
+ #### Type 3: 리스트 + 상세/폼
1686
+ - [ ] MetaData: `saveRequestValue` 추가
1687
+ - [ ] States: `selectedItem`, `detailLoading`, `checkedRowKeys` 추가
1688
+ - [ ] Actions: `setSelectedItem`, `setSaveRequestValue`, `setCheckedRowKeys` 추가
1689
+ - [ ] Store.subscribe: `saveRequestValue` 추가
1690
+
1691
+ #### Type 4: 마스터-디테일
1692
+ - [ ] MetaData: `listSelectedRowKey`, `listCheckedIndexes` 추가
1693
+ - [ ] States: `checkedRowKeys` 추가
1694
+ - [ ] Actions: `setListSelectedRowKey`, `setCheckedRowKeys`, `setListCheckedIndexes` 추가
1695
+ - [ ] Store.subscribe: `listSelectedRowKey`, `listCheckedIndexes` 추가
1696
+
1697
+ #### Type 5: CRUD
1698
+ - [ ] MetaData: 모든 CRUD 필드 추가
1699
+ - [ ] States: 관련 스피닝 필드 추가
1700
+ - [ ] Actions: 저장/삭제/폼 액션 추가
1701
+ - [ ] Store.subscribe: 모든 필드 추가
1702
+
1703
+ #### Type 6: 트리 + 폼
1704
+ - [ ] MetaData: 트리 관련 필드로 교체
1705
+ - [ ] States: `treeData`, `treeSpinning` 등으로 교체
1706
+ - [ ] Actions: 트리 액션으로 교체
1707
+ - [ ] Store.subscribe: 트리 필드로 교체
1708
+
1709
+ #### Type 7: 대시보드
1710
+ - [ ] MetaData: 다중 요청/정렬 필드 추가
1711
+ - [ ] States: 다중 데이터/스피닝 필드 추가
1712
+ - [ ] Actions: 다중 API/페이지 액션 추가
1713
+ - [ ] Store.subscribe: 다중 필드 추가
1714
+
1715
+ #### Type 8-12: 해당 패턴별 필드 확인
1716
+
1717
+ ---
1718
+
1719
+ ## 부록: 전체 예시
1720
+
1721
+ ### Type 1 전체 예시 (useDepartmentStore.ts)
1722
+ ```typescript
1723
+ import { AXDGDataItem, AXDGPage, AXDGSortParam } from "@axboot/datagrid";
1724
+ import { pageStoreActions } from "@core/stores/pageStoreActions";
1725
+ import { PageStoreActions, StoreActions } from "@core/stores/types";
1726
+ import { deleteEmptyValue } from "@core/utils/object";
1727
+ import { ProgramFn } from "@types";
1728
+ import { PostTrtmntDeptListTrtmntDeptRequest, TrtmntDept, TrtmntDeptService } from "services";
1729
+ import { getTabStoreListener } from "stores";
1730
+ import { create } from "zustand";
1731
+ import { subscribeWithSelector } from "zustand/middleware";
1732
+ import { shallow } from "zustand/shallow";
1733
+
1734
+ interface ListRequest extends PostTrtmntDeptListTrtmntDeptRequest {}
1735
+ interface DtoItem extends TrtmntDept {}
1736
+
1737
+ interface MetaData {
1738
+ programFn?: ProgramFn;
1739
+ listRequestValue: ListRequest;
1740
+ listColWidths: number[];
1741
+ listSortParams: AXDGSortParam[];
1742
+ }
1743
+
1744
+ interface States extends MetaData {
1745
+ routePath?: string;
1746
+ listSpinning: boolean;
1747
+ listData: AXDGDataItem<DtoItem>[];
1748
+ listPage: AXDGPage;
1749
+ }
1750
+
1751
+ const createState: States = {
1752
+ listRequestValue: {
1753
+ pageNumber: 1,
1754
+ pageSize: 100,
1755
+ srchwrdType: "DEPT_NM",
1756
+ },
1757
+ listColWidths: [],
1758
+ listSpinning: false,
1759
+ listData: [],
1760
+ listPage: { currentPage: 0, totalPages: 0 },
1761
+ listSortParams: [],
1762
+ };
1763
+
1764
+ interface Actions extends PageStoreActions<States> {
1765
+ setListRequestValue: (requestValue: ListRequest, changedValues?: ListRequest) => void;
1766
+ setListColWidths: (colWidths: number[]) => void;
1767
+ setListSpinning: (spinning: boolean) => void;
1768
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
1769
+ callListApi: (request?: ListRequest) => Promise<void>;
1770
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
1771
+ }
1772
+
1773
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
1774
+ ...pageStoreActions(set, get, { createState }),
1775
+ syncMetadata: (s = createState) => set(s),
1776
+ onMountApp: async () => {},
1777
+ setListRequestValue: (requestValues) => {
1778
+ set({ listRequestValue: requestValues });
1779
+ },
1780
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
1781
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
1782
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
1783
+ callListApi: async (request) => {
1784
+ if (get().listSpinning) return;
1785
+ set({ listSpinning: true });
1786
+
1787
+ try {
1788
+ const apiParam: ListRequest = { ...get().listRequestValue, ...request };
1789
+ const response = await TrtmntDeptService.postTrtmntDeptListTrtmntDept(
1790
+ deleteEmptyValue({ ...apiParam })
1791
+ );
1792
+
1793
+ set({
1794
+ listRequestValue: apiParam,
1795
+ listData: response.ds.map((values) => ({ values })),
1796
+ listPage: {
1797
+ currentPage: response.page.pageNumber ?? 1,
1798
+ pageSize: response.page.pageSize ?? 0,
1799
+ totalPages: response.page.pageCount ?? 0,
1800
+ totalElements: response.page?.totalCount,
1801
+ },
1802
+ });
1803
+ } finally {
1804
+ set({ listSpinning: false });
1805
+ }
1806
+ },
1807
+ changeListPage: async (pageNumber, pageSize) => {
1808
+ await get().callListApi({ pageNumber, pageSize });
1809
+ },
1810
+ });
1811
+
1812
+ export interface departmentStore extends States, Actions, PageStoreActions<States> {}
1813
+
1814
+ export const useDepartmentStore = create(
1815
+ subscribeWithSelector<departmentStore>((set, get) => ({
1816
+ ...createState,
1817
+ ...createActions(set, get),
1818
+ })),
1819
+ );
1820
+
1821
+ useDepartmentStore.subscribe(
1822
+ (s): Record<keyof MetaData, any> => ({
1823
+ programFn: s.programFn,
1824
+ listSortParams: s.listSortParams,
1825
+ listRequestValue: s.listRequestValue,
1826
+ listColWidths: s.listColWidths,
1827
+ }),
1828
+ getTabStoreListener<MetaData>(createState.routePath),
1829
+ { equalityFn: shallow },
1830
+ );
1831
+ ```
1832
+
1833
+ ---
1834
+
1835
+ *이 문서는 NH-FE-B 프로젝트의 Store 패턴을 분석하여 MCP-AXBoot로 생성된 Store를 수정할 때 참고할 수 있도록 작성되었습니다.*