@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.
- package/CLAUDE.md +119 -0
- package/MCP_TOOL_PLAN.md +710 -0
- package/MCP_USAGE.md +914 -0
- package/README.md +168 -0
- package/REPOSITORY_CONVENTIONS.md +250 -0
- package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
- package/SEARCH_PARAMS_PLAN.md +2570 -0
- package/STORE_PATTERNS.md +1178 -0
- package/debug-dto.js +72 -0
- package/generate-banner-store.js +62 -0
- package/generation-plan.json +2176 -0
- package/generation-results.json +1817 -0
- package/package.json +45 -0
- package/scripts/batch-generate-all.js +159 -0
- package/scripts/batch-generate-mcp.js +329 -0
- package/scripts/batch-generate-stores-v2.js +272 -0
- package/scripts/batch-generate-stores.js +179 -0
- package/scripts/batch-plan.json +3810 -0
- package/scripts/batch-process.py +90 -0
- package/scripts/batch-regenerate.js +356 -0
- package/scripts/direct-generate.js +227 -0
- package/scripts/execute-batches.js +1911 -0
- package/scripts/generate-all-stores.js +144 -0
- package/scripts/generate-stores-mcp.js +161 -0
- package/scripts/generate-stores-v2.js +450 -0
- package/scripts/generate-stores-v3.js +412 -0
- package/scripts/generate-stores-v4.js +521 -0
- package/scripts/generate-stores.js +382 -0
- package/scripts/repos-to-process.json +1899 -0
- package/src/config/nh-layout-patterns.ts +166 -0
- package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
- package/src/docs/NH_STORE_PATTERNS.md +297 -0
- package/src/docs/README.md +216 -0
- package/src/docs/index.ts +28 -0
- package/src/docs/loader.ts +568 -0
- package/src/docs/patterns.json +419 -0
- package/src/docs/practical-examples.md +732 -0
- package/src/docs/quick-start.md +257 -0
- package/src/docs/requirements-analysis-guide.md +364 -0
- package/src/docs/rules.json +321 -0
- package/src/docs/store-pattern-analysis.md +664 -0
- package/src/docs/store-patterns-rules.md +1168 -0
- package/src/docs/store-patterns-usage-guide.md +1835 -0
- package/src/docs/troubleshooting.md +544 -0
- package/src/docs/type-selection-guide.md +572 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/src/features/store-features.ts +232 -0
- package/src/handlers/analyze-requirements.ts +403 -0
- package/src/handlers/analyze.ts +1373 -0
- package/src/handlers/generate-from-requirements.ts +250 -0
- package/src/handlers/generate-hook.ts +950 -0
- package/src/handlers/generate-interactive.ts +840 -0
- package/src/handlers/generate-listdatagrid.ts +521 -0
- package/src/handlers/generate-multi-stores.ts +577 -0
- package/src/handlers/generate-requirements-from-layout.ts +160 -0
- package/src/handlers/generate-search-params.ts +717 -0
- package/src/handlers/generate.ts +911 -0
- package/src/handlers/list-templates.ts +104 -0
- package/src/handlers/scan-metadata.ts +485 -0
- package/src/handlers/suggest-layout.ts +326 -0
- package/src/index.ts +959 -0
- package/src/prompts/search-params.md +793 -0
- package/src/templates/index.ts +107 -0
- package/src/templates/unified.ts +462 -0
- package/store-generation-error-patterns.md +225 -0
- package/test/useAgentStore.ts +136 -0
- package/test-server.js +78 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
# MCP-AXBoot 실전 예제 모음
|
|
2
|
+
|
|
3
|
+
> 실제 프로젝트에서 자주 발생하는 시나리오별 Store 생성 예제입니다.
|
|
4
|
+
|
|
5
|
+
## 목차
|
|
6
|
+
|
|
7
|
+
1. [예제 1: 회원 관리 (CRUD)](#예제-1-회원-관리-crud)
|
|
8
|
+
2. [예제 2: 상품 카테고리 (트리)](#예제-2-상품-카테고리-트리)
|
|
9
|
+
3. [예제 3: 주문 관리 (마스터-디테일)](#예제-3-주문-관리-마스터-디테일)
|
|
10
|
+
4. [예제 4: 대시보드 (다중 데이터)](#예제-4-대시보드-다중-데이터)
|
|
11
|
+
5. [예제 5: 배치 처리 (체크박스)](#예제-5-배치-처리-체크박스)
|
|
12
|
+
6. [예제 6: 게시판 (고정값)](#예제-6-게시판-고정값)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 예제 1: 회원 관리 (CRUD)
|
|
17
|
+
|
|
18
|
+
### 시나리오
|
|
19
|
+
|
|
20
|
+
회원 목록을 조회하고, 선택한 회원의 상세 정보를 보여주며, 수정과 삭제가 가능한 페이지입니다.
|
|
21
|
+
|
|
22
|
+
### Repository
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// services/MemberRepository.ts
|
|
26
|
+
export interface MemberRepository {
|
|
27
|
+
postMemberListMember(request: PostMemberListMemberRequest): Promise<MemberListResponse>;
|
|
28
|
+
postMemberDetailMember(request: PostMemberDetailMemberRequest): Promise<MemberDetailResponse>;
|
|
29
|
+
postMemberSaveMember(request: PostMemberSaveMemberRequest): Promise<MemberSaveResponse>;
|
|
30
|
+
postMemberDeleteMember(request: PostMemberDeleteMemberRequest): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 요구사항
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
회원 목록을 보여주고, 회원을 선택하면 상세 정보가 표시됩니다.
|
|
38
|
+
상세 정보에서 수정과 삭제가 가능해야 합니다.
|
|
39
|
+
엑셀 다운로드도 필요합니다.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Claude 대화
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
👤 사용자:
|
|
46
|
+
회원 목록 CRUD Store를 만들어줘.
|
|
47
|
+
Repository는 services/MemberRepository.ts야.
|
|
48
|
+
|
|
49
|
+
🤖 Claude:
|
|
50
|
+
먼저 MemberRepository를 분석하겠습니다...
|
|
51
|
+
|
|
52
|
+
분석 결과:
|
|
53
|
+
- List 메서드: postMemberListMember ✓
|
|
54
|
+
- Detail 메서드: postMemberDetailMember ✓
|
|
55
|
+
- Save 메서드: postMemberSaveMember ✓
|
|
56
|
+
- Delete 메서드: postMemberDeleteMember ✓
|
|
57
|
+
|
|
58
|
+
추천 타입: Type 5 (CRUD) - 점수 0.95
|
|
59
|
+
|
|
60
|
+
Store를 생성할까요?
|
|
61
|
+
|
|
62
|
+
👤 사용자:
|
|
63
|
+
응, 생성해줘. 출력은 stores/useMemberListStore.ts로 해줘.
|
|
64
|
+
|
|
65
|
+
🤖 Claude:
|
|
66
|
+
Store를 생성했습니다! 생성된 파일:
|
|
67
|
+
stores/useMemberListStore.ts
|
|
68
|
+
|
|
69
|
+
주요 기능:
|
|
70
|
+
- 리스트 조회 (callListApi)
|
|
71
|
+
- 상세 조회 (setListSelectedRowKey)
|
|
72
|
+
- 저장 (callSaveApi)
|
|
73
|
+
- 삭제 (callDeleteApi)
|
|
74
|
+
- 엑셀 다운로드 (callExcelDownloadApi)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 생성된 Store 코드
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// stores/useMemberListStore.ts
|
|
81
|
+
import { AXDGDataItem, AXDGPage, AXDGSortParam } from "@axboot/datagrid";
|
|
82
|
+
import { pageStoreActions } from "@core/stores/pageStoreActions";
|
|
83
|
+
import { PageStoreActions, StoreActions } from "@core/stores/types";
|
|
84
|
+
import { deleteEmptyValue } from "@core/utils/object";
|
|
85
|
+
import { ProgramFn } from "@types";
|
|
86
|
+
import {
|
|
87
|
+
MemberService,
|
|
88
|
+
PostMemberListMemberRequest,
|
|
89
|
+
MemberRes
|
|
90
|
+
} from "services";
|
|
91
|
+
import { getTabStoreListener } from "stores";
|
|
92
|
+
import { create } from "zustand";
|
|
93
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
|
94
|
+
import { shallow } from "zustand/shallow";
|
|
95
|
+
|
|
96
|
+
interface ListRequest extends PostMemberListMemberRequest {}
|
|
97
|
+
interface DtoItem extends MemberRes {}
|
|
98
|
+
interface SaveRequest {
|
|
99
|
+
memberUuid?: string;
|
|
100
|
+
memberName?: string;
|
|
101
|
+
memberEmail?: string;
|
|
102
|
+
// ... other fields
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface MetaData {
|
|
106
|
+
programFn?: ProgramFn;
|
|
107
|
+
listRequestValue: ListRequest;
|
|
108
|
+
listColWidths: number[];
|
|
109
|
+
listSortParams: AXDGSortParam[];
|
|
110
|
+
listSelectedRowKey?: React.Key;
|
|
111
|
+
flexGrow: number;
|
|
112
|
+
saveRequestValue: SaveRequest;
|
|
113
|
+
detail?: SaveRequest;
|
|
114
|
+
formActive: boolean;
|
|
115
|
+
listCheckedKeys: React.Key[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface States extends MetaData {
|
|
119
|
+
routePath?: string;
|
|
120
|
+
listSpinning: boolean;
|
|
121
|
+
listData: AXDGDataItem<DtoItem>[];
|
|
122
|
+
listPage: AXDGPage;
|
|
123
|
+
saveSpinning: boolean;
|
|
124
|
+
detailSpinning: boolean;
|
|
125
|
+
deleteSpinning: boolean;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const createState: States = {
|
|
129
|
+
listRequestValue: {
|
|
130
|
+
pageNumber: 1,
|
|
131
|
+
pageSize: 100,
|
|
132
|
+
},
|
|
133
|
+
listColWidths: [],
|
|
134
|
+
listSpinning: false,
|
|
135
|
+
listData: [],
|
|
136
|
+
listPage: { currentPage: 0, totalPages: 0 },
|
|
137
|
+
listSortParams: [],
|
|
138
|
+
listSelectedRowKey: "",
|
|
139
|
+
flexGrow: 400,
|
|
140
|
+
saveRequestValue: {},
|
|
141
|
+
detail: undefined,
|
|
142
|
+
formActive: false,
|
|
143
|
+
listCheckedKeys: [],
|
|
144
|
+
saveSpinning: false,
|
|
145
|
+
detailSpinning: false,
|
|
146
|
+
deleteSpinning: false,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
interface Actions extends PageStoreActions<States> {
|
|
150
|
+
setListRequestValue: (requestValue: ListRequest) => void;
|
|
151
|
+
setListColWidths: (colWidths: number[]) => void;
|
|
152
|
+
setListSpinning: (spinning: boolean) => void;
|
|
153
|
+
setListSortParams: (sortParams: AXDGSortParam[]) => void;
|
|
154
|
+
callListApi: (request?: ListRequest) => Promise<void>;
|
|
155
|
+
changeListPage: (pageNumber: number, pageSize?: number) => Promise<void>;
|
|
156
|
+
setListSelectedRowKey: (key?: React.Key, detail?: DtoItem) => void;
|
|
157
|
+
setSaveRequestValue: (saveRequestValue: SaveRequest) => void;
|
|
158
|
+
setSaveSpinning: (spinning: boolean) => void;
|
|
159
|
+
callSaveApi: (request?: SaveRequest) => Promise<void>;
|
|
160
|
+
callDeleteApi: (request?: SaveRequest) => Promise<void>;
|
|
161
|
+
cancelFormActive: () => void;
|
|
162
|
+
setFormActive: () => void;
|
|
163
|
+
setListCheckedKeys: (checkedKeys: React.Key[]) => void;
|
|
164
|
+
setFlexGrow: (flexGlow: number) => void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
|
|
168
|
+
...pageStoreActions(set, get, { createState }),
|
|
169
|
+
syncMetadata: (s = createState) => set(s),
|
|
170
|
+
onMountApp: async () => {},
|
|
171
|
+
setListRequestValue: (requestValues) => {
|
|
172
|
+
set({ listRequestValue: requestValues });
|
|
173
|
+
},
|
|
174
|
+
setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
|
|
175
|
+
setListSpinning: (spinning) => set({ listSpinning: spinning }),
|
|
176
|
+
setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
|
|
177
|
+
|
|
178
|
+
callListApi: async (request) => {
|
|
179
|
+
if (get().listSpinning) return;
|
|
180
|
+
set({ listSpinning: true });
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const apiParam: ListRequest = { ...get().listRequestValue, ...request };
|
|
184
|
+
const response = await MemberService.postMemberList(
|
|
185
|
+
deleteEmptyValue({ ...apiParam })
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
set({
|
|
189
|
+
listRequestValue: apiParam,
|
|
190
|
+
listData: response.ds.map((values) => ({ values })),
|
|
191
|
+
listPage: {
|
|
192
|
+
currentPage: response.page.pageNumber ?? 1,
|
|
193
|
+
pageSize: response.page.pageSize ?? 0,
|
|
194
|
+
totalPages: response.page.pageCount ?? 0,
|
|
195
|
+
totalElements: response.page?.totalCount,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
} finally {
|
|
199
|
+
set({ listSpinning: false });
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
changeListPage: async (pageNumber, pageSize) => {
|
|
204
|
+
await get().callListApi({ pageNumber, pageSize });
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
setListSelectedRowKey: async (key, detail) => {
|
|
208
|
+
if (detail) {
|
|
209
|
+
set({ listSelectedRowKey: key, detail, saveRequestValue: { ...detail } });
|
|
210
|
+
} else {
|
|
211
|
+
set({ listSelectedRowKey: undefined, detail: undefined, saveRequestValue: undefined });
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
setSaveRequestValue: (saveRequestValue) => {
|
|
216
|
+
set({ saveRequestValue });
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
setSaveSpinning: (saveSpinning) => set({ saveSpinning: saveSpinning }),
|
|
220
|
+
|
|
221
|
+
callSaveApi: async (request) => {
|
|
222
|
+
if (get().saveSpinning) return;
|
|
223
|
+
set({ saveSpinning: true });
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const apiParam: SaveRequest = {
|
|
227
|
+
...get().saveRequestValue,
|
|
228
|
+
...request,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
apiParam.__status__ = get().listSelectedRowKey ? "U" : "C";
|
|
232
|
+
|
|
233
|
+
const { rs } = await MemberService.postMemberSave(apiParam);
|
|
234
|
+
set({ saveRequestValue: rs, detail: rs });
|
|
235
|
+
|
|
236
|
+
await get().callListApi();
|
|
237
|
+
} finally {
|
|
238
|
+
set({ saveSpinning: false });
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
callDeleteApi: async (request) => {
|
|
243
|
+
if (get().deleteSpinning) return;
|
|
244
|
+
set({ deleteSpinning: true });
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const apiParam: SaveRequest = {
|
|
248
|
+
...get().saveRequestValue,
|
|
249
|
+
...request,
|
|
250
|
+
__status__: "D",
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
await MemberService.postMemberDelete(apiParam);
|
|
254
|
+
await get().callListApi();
|
|
255
|
+
} finally {
|
|
256
|
+
set({ deleteSpinning: false });
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
cancelFormActive: () => {
|
|
261
|
+
set({ formActive: false, listSelectedRowKey: undefined });
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
setFormActive: () => {
|
|
265
|
+
set({ formActive: true, detail: undefined, saveRequestValue: undefined });
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
setListCheckedKeys: (checkedKeys) => {
|
|
269
|
+
set({ listCheckedKeys: checkedKeys });
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
setFlexGrow: (flexGlow) => {
|
|
273
|
+
set({ flexGrow: flexGlow });
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
export interface memberListStore extends States, Actions, PageStoreActions<States> {}
|
|
278
|
+
|
|
279
|
+
export const useMemberListStore = create(
|
|
280
|
+
subscribeWithSelector<memberListStore>((set, get) => ({
|
|
281
|
+
...createState,
|
|
282
|
+
...createActions(set, get),
|
|
283
|
+
}))
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
useMemberListStore.subscribe(
|
|
287
|
+
(s): Record<keyof MetaData, any> => ({
|
|
288
|
+
programFn: s.programFn,
|
|
289
|
+
listSortParams: s.listSortParams,
|
|
290
|
+
listRequestValue: s.listRequestValue,
|
|
291
|
+
listColWidths: s.listColWidths,
|
|
292
|
+
listSelectedRowKey: s.listSelectedRowKey,
|
|
293
|
+
flexGrow: s.flexGrow,
|
|
294
|
+
saveRequestValue: s.saveRequestValue,
|
|
295
|
+
detail: s.detail,
|
|
296
|
+
formActive: s.formActive,
|
|
297
|
+
listCheckedKeys: s.listCheckedKeys,
|
|
298
|
+
}),
|
|
299
|
+
getTabStoreListener<MetaData>(createState.routePath),
|
|
300
|
+
{ equalityFn: shallow },
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 컴포넌트 사용 예시
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import React, { useEffect } from 'react';
|
|
308
|
+
import { useMemberListStore } from './stores/useMemberListStore';
|
|
309
|
+
|
|
310
|
+
export default function MemberListPage() {
|
|
311
|
+
const {
|
|
312
|
+
listData,
|
|
313
|
+
listPage,
|
|
314
|
+
listSpinning,
|
|
315
|
+
listSelectedRowKey,
|
|
316
|
+
detail,
|
|
317
|
+
formActive,
|
|
318
|
+
callListApi,
|
|
319
|
+
changeListPage,
|
|
320
|
+
setListSelectedRowKey,
|
|
321
|
+
setFormActive,
|
|
322
|
+
cancelFormActive,
|
|
323
|
+
callSaveApi,
|
|
324
|
+
callDeleteApi,
|
|
325
|
+
} = useMemberListStore();
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
callListApi();
|
|
329
|
+
}, []);
|
|
330
|
+
|
|
331
|
+
const handleSave = async () => {
|
|
332
|
+
await callSaveApi();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const handleDelete = async () => {
|
|
336
|
+
await callDeleteApi();
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div className="member-list-page">
|
|
341
|
+
{/* 검색 영역 */}
|
|
342
|
+
<SearchForm />
|
|
343
|
+
|
|
344
|
+
{/* 리스트 영역 */}
|
|
345
|
+
<MemberTable
|
|
346
|
+
data={listData}
|
|
347
|
+
page={listPage}
|
|
348
|
+
loading={listSpinning}
|
|
349
|
+
selectedRowKey={listSelectedRowKey}
|
|
350
|
+
onSelect={(key) => setListSelectedRowKey(key, detail)}
|
|
351
|
+
onChangePage={changeListPage}
|
|
352
|
+
/>
|
|
353
|
+
|
|
354
|
+
{/* 상세/편집 영역 */}
|
|
355
|
+
{listSelectedRowKey && (
|
|
356
|
+
<MemberDetailForm
|
|
357
|
+
detail={detail}
|
|
358
|
+
formActive={formActive}
|
|
359
|
+
onSave={handleSave}
|
|
360
|
+
onDelete={handleDelete}
|
|
361
|
+
onEdit={setFormActive}
|
|
362
|
+
onCancel={cancelFormActive}
|
|
363
|
+
/>
|
|
364
|
+
)}
|
|
365
|
+
</div>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 예제 2: 상품 카테고리 (트리)
|
|
373
|
+
|
|
374
|
+
### 시나리오
|
|
375
|
+
|
|
376
|
+
상품 카테고리를 트리 구조로 표시하고, 선택된 카테고리를 오른쪽 폼에서 편집합니다.
|
|
377
|
+
|
|
378
|
+
### Repository
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// services/CategoryRepository.ts
|
|
382
|
+
export interface CategoryRepository {
|
|
383
|
+
postCategoryListCtgrTree(request: PostCategoryListCtgrTreeRequest): Promise<CategoryTreeResponse>;
|
|
384
|
+
postCategorySaveCtgr(request: PostCategorySaveCtgrRequest): Promise<CategorySaveResponse>;
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 요구사항
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
상품 카테고리를 트리로 표시하고, 선택된 카테고리의 정보를 편집할 수 있는 Store.
|
|
392
|
+
검색어로 카테고리를 찾을 수 있어야 합니다.
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Claude 대화
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
👤 사용자:
|
|
399
|
+
상품 카테고리용 Store를 만들어줘.
|
|
400
|
+
트리 구조가 필요해.
|
|
401
|
+
|
|
402
|
+
🤖 Claude:
|
|
403
|
+
CategoryRepository를 분석하겠습니다...
|
|
404
|
+
|
|
405
|
+
분석 결과:
|
|
406
|
+
- Tree 메서드: postCategoryListCtgrTree ✓
|
|
407
|
+
- Save 메서드: postCategorySaveCtgr ✓
|
|
408
|
+
|
|
409
|
+
추천 타입: Type 6 (Tree + Form) - 점수 0.98
|
|
410
|
+
|
|
411
|
+
Store를 생성할까요?
|
|
412
|
+
|
|
413
|
+
👤 사용자:
|
|
414
|
+
응, useProductCategoryStore.ts로 생성해줘.
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 생성된 Store (주요 부분)
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// stores/useProductCategoryStore.ts
|
|
421
|
+
interface MetaData {
|
|
422
|
+
programFn?: ProgramFn;
|
|
423
|
+
listRequestValue: ListRequest;
|
|
424
|
+
flexGrow: number;
|
|
425
|
+
selectedItem?: DtoItem;
|
|
426
|
+
saveRequestValues?: DtoItem;
|
|
427
|
+
expandedKeys: number[] | string[];
|
|
428
|
+
categorySearch?: boolean;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
interface States extends MetaData {
|
|
432
|
+
routePath?: string;
|
|
433
|
+
treeSpinning: boolean;
|
|
434
|
+
treeData: DtoItem[];
|
|
435
|
+
saveSpinning: boolean;
|
|
436
|
+
isDirty: boolean;
|
|
437
|
+
isFormDirty: boolean;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
|
|
441
|
+
...pageStoreActions(set, get, { createState }),
|
|
442
|
+
syncMetadata: (s = createState) => set(s),
|
|
443
|
+
onMountApp: async () => {},
|
|
444
|
+
|
|
445
|
+
setFlexGrow: (flexGrow) => set({ flexGrow }),
|
|
446
|
+
|
|
447
|
+
callTreeApi: async (request) => {
|
|
448
|
+
set({ treeSpinning: true });
|
|
449
|
+
try {
|
|
450
|
+
if (request?.ctgrNm && request.ctgrNm !== "") {
|
|
451
|
+
// 검색어가 있으면 리스트 조회
|
|
452
|
+
const { ds: list } = await CategoryService.postCategoryList({
|
|
453
|
+
ctgrNm: request.ctgrNm,
|
|
454
|
+
pathDelim: " > ",
|
|
455
|
+
});
|
|
456
|
+
set({ treeData: list, categorySearch: true });
|
|
457
|
+
} else {
|
|
458
|
+
// 없으면 트리 조회
|
|
459
|
+
const { ds: tree } = await CategoryService.postCategoryListTree({});
|
|
460
|
+
set({ treeData: tree, categorySearch: false });
|
|
461
|
+
}
|
|
462
|
+
} finally {
|
|
463
|
+
set({ treeSpinning: false });
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
setSelectedItem: (item) => {
|
|
468
|
+
set({ selectedItem: item, saveRequestValues: { ...item } });
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
setTreeData: (treeData) => set({ treeData }),
|
|
472
|
+
|
|
473
|
+
setExpandedKeys: (expandedKeys) => set({ expandedKeys }),
|
|
474
|
+
|
|
475
|
+
setSaveRequestValues: (saveRequestValues) => {
|
|
476
|
+
set({ saveRequestValues });
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
setListRequestValue: (requestValues) => {
|
|
480
|
+
set({ listRequestValue: requestValues });
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
setCategorySearch: (search) => set({ categorySearch: search }),
|
|
484
|
+
|
|
485
|
+
callTreeSaveApi: async (treeData) => {
|
|
486
|
+
set({ saveSpinning: true });
|
|
487
|
+
try {
|
|
488
|
+
await CategoryService.postCategorySave({ treeData });
|
|
489
|
+
await get().callTreeApi({});
|
|
490
|
+
} finally {
|
|
491
|
+
set({ saveSpinning: false });
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
setIsDirty: (dirty) => set({ isDirty: dirty }),
|
|
496
|
+
});
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## 예제 3: 주문 관리 (마스터-디테일)
|
|
502
|
+
|
|
503
|
+
### 시나리오
|
|
504
|
+
|
|
505
|
+
주문 목록에서 주문을 선택하면 하단에 해당 주문의 상세 내역이 표시됩니다.
|
|
506
|
+
|
|
507
|
+
### 요구사항
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
주문 목록과 상세 내역이 있는 Store.
|
|
511
|
+
상단에 주문 목록, 하단에 선택된 주문의 상세 내역이 표시되어야 함.
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 추천 타입
|
|
515
|
+
|
|
516
|
+
**Type 4: Master-Detail**
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
interface MetaData {
|
|
520
|
+
programFn?: ProgramFn;
|
|
521
|
+
listRequestValue: ListRequest;
|
|
522
|
+
listColWidths: number[];
|
|
523
|
+
listSortParams: AXDGSortParam[];
|
|
524
|
+
listSelectedRowKey?: React.Key;
|
|
525
|
+
listCheckedIndexes?: number[];
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## 예제 4: 대시보드 (다중 데이터)
|
|
532
|
+
|
|
533
|
+
### 시나리오
|
|
534
|
+
|
|
535
|
+
회원 대시보드 페이지. 상단에 가입/탈퇴 통계가 표시되고, 하단에 탭으로 구분된 회원 목록들이 있습니다.
|
|
536
|
+
|
|
537
|
+
### 요구사항
|
|
538
|
+
|
|
539
|
+
```
|
|
540
|
+
회원 대시보드 Store.
|
|
541
|
+
- 상단: 회원 통계 (가입자수, 탈퇴자수 등)
|
|
542
|
+
- 하단: 탭1 - 가입 회원 목록, 탭2 - 탈퇴 회원 목록
|
|
543
|
+
- 각 목록은 엑셀 다운로드 가능
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### 추천 타입
|
|
547
|
+
|
|
548
|
+
**Type 7: Dashboard**
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
interface MetaData {
|
|
552
|
+
programFn?: ProgramFn;
|
|
553
|
+
summaryRequestValue: SummaryRequest;
|
|
554
|
+
listRequest01Value: ListRequest01;
|
|
555
|
+
listRequest02Value: ListRequest02;
|
|
556
|
+
activeTabKey: PanelType;
|
|
557
|
+
list01SortParams: AXDGSortParam[];
|
|
558
|
+
list02SortParams: AXDGSortParam[];
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
interface States extends MetaData {
|
|
562
|
+
routePath?: string;
|
|
563
|
+
spinning: boolean;
|
|
564
|
+
summaryData: DtoItem;
|
|
565
|
+
list01Data: AXDGDataItem<DtoItem01>[];
|
|
566
|
+
list01Page: AXDGPage;
|
|
567
|
+
list02Data: AXDGDataItem<DtoItem02>[];
|
|
568
|
+
list02Page: AXDGPage;
|
|
569
|
+
summarySpinning: boolean;
|
|
570
|
+
list01Spinning: boolean;
|
|
571
|
+
list02Spinning: boolean;
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## 예제 5: 배치 처리 (체크박스)
|
|
578
|
+
|
|
579
|
+
### 시나리오
|
|
580
|
+
|
|
581
|
+
상품 목록에서 체크박스로 여러 상품을 선택하고, 일괄적으로 전시/비전시 상태를 변경합니다.
|
|
582
|
+
|
|
583
|
+
### 요구사항
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
상품 목록에서 체크박스로 여러 상품을 선택하여
|
|
587
|
+
일괄 상태 변경(전시/비전시/판매중/품절/판매중지)이 가능한 Store.
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 추천 타입
|
|
591
|
+
|
|
592
|
+
**Type 9: Status Change**
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
interface Actions extends PageStoreActions<States> {
|
|
596
|
+
// ... 기본 액션들
|
|
597
|
+
callChangeStatusApi: (changeStatus: string) => void;
|
|
598
|
+
callDeleteApi: (selectedItems: string | React.Key[]) => void;
|
|
599
|
+
callDataCopyApi: (prdCd: string) => Promise<void>;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 구현 예시
|
|
603
|
+
callChangeStatusApi: async (changeStatus) => {
|
|
604
|
+
const selectedPrdCds = get().checkedRowKeys as string[];
|
|
605
|
+
if (!selectedPrdCds || selectedPrdCds.length === 0) return;
|
|
606
|
+
|
|
607
|
+
if (get().updateSpinning) return;
|
|
608
|
+
set({ updateSpinning: true });
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
switch (changeStatus) {
|
|
612
|
+
case "전시":
|
|
613
|
+
await ProductService.postProductSavePrdDspyY({ prdCds: selectedPrdCds });
|
|
614
|
+
break;
|
|
615
|
+
case "비전시":
|
|
616
|
+
await ProductService.postProductSavePrdDspN({ prdCds: selectedPrdCds });
|
|
617
|
+
break;
|
|
618
|
+
case "판매중":
|
|
619
|
+
await ProductService.postProductSavePrdSleSttus10({ prdCds: selectedPrdCds });
|
|
620
|
+
break;
|
|
621
|
+
case "품절":
|
|
622
|
+
await ProductService.postProductSavePrdSleSttus20({ prdCds: selectedPrdCds });
|
|
623
|
+
break;
|
|
624
|
+
case "판매중지":
|
|
625
|
+
await ProductService.postProductSavePrdSleSttus50({ prdCds: selectedPrdCds });
|
|
626
|
+
break;
|
|
627
|
+
default:
|
|
628
|
+
console.warn("알 수 없는 상태 변경 요청:", changeStatus);
|
|
629
|
+
}
|
|
630
|
+
} finally {
|
|
631
|
+
set({ updateSpinning: false });
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## 예제 6: 게시판 (고정값)
|
|
639
|
+
|
|
640
|
+
### 시나리오
|
|
641
|
+
|
|
642
|
+
특정 게시판(bbsNo=26)의 게시글 목록을 표시하는 페이지입니다.
|
|
643
|
+
|
|
644
|
+
### 요구사항
|
|
645
|
+
|
|
646
|
+
```
|
|
647
|
+
bbsNo가 26인 공지사항 게시판의 목록을 보여주는 Store.
|
|
648
|
+
게시글은 제목, 작성일, 조회수 등을 표시.
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 추천 타입
|
|
652
|
+
|
|
653
|
+
**Type 10: Fixed Value Based** (또는 Type 1 + 고정값)
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
const bbsNo = 26; // 고정값
|
|
657
|
+
|
|
658
|
+
const createState: States = {
|
|
659
|
+
bbsNo, // States에 포함
|
|
660
|
+
listRequestValue: {
|
|
661
|
+
bbsNo, // listRequestValue에도 포함
|
|
662
|
+
pageNumber: 1,
|
|
663
|
+
pageSize: 100,
|
|
664
|
+
},
|
|
665
|
+
// ...
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// API 호출 시 사용
|
|
669
|
+
callListApi: async (request) => {
|
|
670
|
+
const apiParam = {
|
|
671
|
+
...get().listRequestValue,
|
|
672
|
+
...request,
|
|
673
|
+
bbsNo: get().bbsNo, // 고정값 사용
|
|
674
|
+
};
|
|
675
|
+
// ...
|
|
676
|
+
},
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## 팁: 생성 후 확인사항
|
|
682
|
+
|
|
683
|
+
모든 예제에서 Store 생성 후 다음을 확인하세요:
|
|
684
|
+
|
|
685
|
+
### 1. 초기 검색조건 설정
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
const createState: States = {
|
|
689
|
+
listRequestValue: {
|
|
690
|
+
pageNumber: 1,
|
|
691
|
+
pageSize: 100,
|
|
692
|
+
// 필요한 검색조건 추가
|
|
693
|
+
searchKeyword: "",
|
|
694
|
+
searchStatus: "ALL",
|
|
695
|
+
dateRange: undefined,
|
|
696
|
+
},
|
|
697
|
+
// ...
|
|
698
|
+
};
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 2. Service 메서드명 확인
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
// 생성된 코드
|
|
705
|
+
const response = await MemberService.postMemberList(apiParam);
|
|
706
|
+
|
|
707
|
+
// 실제 Service 메서드명으로 변경
|
|
708
|
+
const response = await MemberService.postMemberListMember(apiParam);
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### 3. 타입 정의 확인
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
// Repository Request 타입 확인
|
|
715
|
+
interface ListRequest extends PostMemberListMemberRequest {
|
|
716
|
+
// 추가 필드
|
|
717
|
+
dateRange?: [string, string];
|
|
718
|
+
}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### 4. TODO 주석 구현
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// TODO: 삭제 API 호출 구현 필요
|
|
725
|
+
// await MemberService.removeMember({ id });
|
|
726
|
+
|
|
727
|
+
// → 실제 구현
|
|
728
|
+
await MemberService.postMemberDelete({
|
|
729
|
+
memberUuid: id,
|
|
730
|
+
__status__: "D",
|
|
731
|
+
});
|
|
732
|
+
```
|