@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,1133 @@
1
+ # MCP 바이브코딩 가이드
2
+
3
+ > NH-FE-BO 프로젝트의 Store, Hook, Component 패턴을 MCP 서버에서 활용하여 자동 코드 생성을 위한 매뉴얼입니다.
4
+
5
+ ## 목차
6
+
7
+ 1. [아키텍처 개요](#아키텍처-개요)
8
+ 2. [Store 구현 패턴](#store-구현-패턴)
9
+ 3. [Hook 구현 패턴](#hook-구현-패턴)
10
+ 4. [Component 구현 패턴](#component-구현-패턴)
11
+ 5. [Ant Design 컴포넌트 패턴](#ant-design-컴포넌트-패턴)
12
+ 6. [AX Grid 컴포넌트 패턴](#ax-grid-컴포넌트-패턴)
13
+ 7. [레이어 간 연결 방식](#레이어-간-연결-방식)
14
+ 8. [MCP 활용 예시](#mcp-활용-예시)
15
+
16
+ ---
17
+
18
+ ## 아키텍처 개요
19
+
20
+ ### 레이어 구조
21
+
22
+ ```
23
+ ┌─────────────────────────────────────────────────────────────┐
24
+ │ Component Layer │
25
+ │ (App.tsx, ListDataGrid.tsx, FormModal.tsx) │
26
+ └────────────────────────┬────────────────────────────────────┘
27
+ │ subscribe/useStore
28
+ ┌────────────────────────▼────────────────────────────────────┐
29
+ │ Store Layer │
30
+ │ (useXxxListStore.ts - Zustand) │
31
+ │ - States: 상태 정의 │
32
+ │ - Actions: 액션 정의 │
33
+ │ - MetaData: 영속화 데이터 │
34
+ └────────────────────────┬────────────────────────────────────┘
35
+ │ API 호출
36
+ ┌────────────────────────▼────────────────────────────────────┐
37
+ │ Hook Layer │
38
+ │ (useXxxService.ts) │
39
+ │ - API 호출 래핑 │
40
+ │ - 데이터 변환 │
41
+ └────────────────────────┬────────────────────────────────────┘
42
+ │ HTTP Request
43
+ ┌────────────────────────▼────────────────────────────────────┐
44
+ │ Service Layer │
45
+ │ (XxxService.ts) │
46
+ └────────────────────────┬────────────────────────────────────┘
47
+ │ Repository 호출
48
+ ┌────────────────────────▼────────────────────────────────────┐
49
+ │ Repository Layer │
50
+ │ (XxxRepository.ts - Interface) │
51
+ └────────────────────────┬────────────────────────────────────┘
52
+ │ axios
53
+ ┌────────────────────────▼────────────────────────────────────┐
54
+ │ Backend API │
55
+ └─────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Store 구현 패턴
61
+
62
+ ### 기본 구조 (Zustand)
63
+
64
+ ```typescript
65
+ // use[Entity]ListStore.ts
66
+
67
+ import { AXDGDataItem, AXDGPage, AXDGSortParam } from "@axboot/datagrid";
68
+ import { pageStoreActions } from "@core/stores/pageStoreActions";
69
+ import { PageStoreActions, StoreActions } from "@core/stores/types";
70
+ import { deleteEmptyValue } from "@core/utils/object";
71
+ import { errorHandling } from "utils";
72
+ import { ProgramFn } from "@types";
73
+ import { EntityService } from "services";
74
+ import { getTabStoreListener } from "stores";
75
+ import { create } from "zustand";
76
+ import { subscribeWithSelector } from "zustand/middleware";
77
+ import { shallow } from "zustand/shallow";
78
+
79
+ // ========== 1. Request/Response 인터페이스 ==========
80
+ interface ListRequest extends PostEntityListRequest {}
81
+
82
+ interface DtoItem extends Entity {}
83
+
84
+ // ========== 2. MetaData 정의 (영속화 대상) ==========
85
+ interface MetaData {
86
+ programFn?: ProgramFn;
87
+ listRequestValue: ListRequest;
88
+ listColWidths: number[];
89
+ listSortParams: AXDGSortParam[];
90
+ listSelectedRowKey?: React.Key;
91
+ listCheckedIndexes?: number[];
92
+ modalOpen: boolean;
93
+ }
94
+
95
+ // ========== 3. States 정의 ==========
96
+ interface States extends MetaData {
97
+ routePath?: string;
98
+ listSpinning: boolean;
99
+ deleteSpinning: boolean;
100
+ saveSpinning: boolean;
101
+ detailSpinning: boolean;
102
+ listData: AXDGDataItem<DtoItem>[];
103
+ listPage: AXDGPage;
104
+ selectedItem?: DtoItem;
105
+ detail?: DtoItem;
106
+ checkedRowKeys: React.Key[];
107
+ }
108
+
109
+ // ========== 4. 초기 상태 ==========
110
+ const createState: States = {
111
+ routePath: "/entity/list",
112
+ listRequestValue: {
113
+ pageNumber: 1,
114
+ pageSize: 100,
115
+ // 초기 검색 조건
116
+ },
117
+ listColWidths: [],
118
+ listSortParams: [],
119
+ listSpinning: false,
120
+ deleteSpinning: false,
121
+ saveSpinning: false,
122
+ detailSpinning: false,
123
+ listData: [],
124
+ listPage: {
125
+ currentPage: 0,
126
+ totalPages: 0,
127
+ },
128
+ selectedItem: undefined,
129
+ detail: undefined,
130
+ modalOpen: false,
131
+ checkedRowKeys: [],
132
+ listSelectedRowKey: "",
133
+ listCheckedIndexes: [],
134
+ };
135
+
136
+ // ========== 5. Actions 정의 ==========
137
+ interface Actions extends PageStoreActions<States> {
138
+ // 검색 관련
139
+ setListRequestValue: (requestValue: ListRequest) => void;
140
+ setListColWidths: (colWidths: number[]) => void;
141
+ setListSortParams: (sortParams: AXDGSortParam[]) => void;
142
+ setListSpinning: (spinning: boolean) => void;
143
+ callListApi: (request?: ListRequest) => Promise<void>;
144
+ changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
145
+
146
+ // 선택 관련
147
+ setListSelectedRowKey: (key?: React.Key) => void;
148
+ setSelectedItem: (selectedItem?: DtoItem) => void;
149
+ setDetail: (detail?: DtoItem) => void;
150
+ callDetailApi: (id: number | string) => Promise<void>;
151
+
152
+ // CRUD 관련
153
+ callSaveApi: (request?: PostEntitySaveRequest) => Promise<void>;
154
+ callDeleteApi: (request: PostEntityDeleteRequest) => Promise<void>;
155
+
156
+ // UI 관련
157
+ setModalOpen: (open: boolean) => void;
158
+ setCheckedRowKeys: (checkedRowKeys: React.Key[]) => void;
159
+ }
160
+
161
+ // ========== 6. Actions 구현 ==========
162
+ const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
163
+ ...pageStoreActions(set, get, { createState }),
164
+ syncMetadata: (s = createState) => set(s),
165
+ onMountApp: async () => {},
166
+
167
+ // 검색 관련
168
+ setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
169
+ setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
170
+ setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
171
+ setListSpinning: (spinning) => set({ listSpinning: spinning }),
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 EntityService.postEntityList(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
+ } catch (err) {
192
+ await errorHandling(err);
193
+ } finally {
194
+ set({ listSpinning: false });
195
+ }
196
+ },
197
+
198
+ changeListPage: async (pageNumber, pageSize) => {
199
+ await get().callListApi({ ...get().listRequestValue, pageNumber, pageSize });
200
+ },
201
+
202
+ // 선택 관련
203
+ setListSelectedRowKey: (key) => set({ listSelectedRowKey: key }),
204
+ setSelectedItem: async (selectedItem) => {
205
+ set({ selectedItem, detailSpinning: true });
206
+ if (!selectedItem || (selectedItem as any)["__status__"] === "C") {
207
+ set({ modalOpen: false });
208
+ }
209
+ set({ detailSpinning: false });
210
+ },
211
+ setDetail: (detail) => set({ detail }),
212
+ setModalOpen: (open) => set({ modalOpen: open }),
213
+ setCheckedRowKeys: (checkedRowKeys) => set({ checkedRowKeys }),
214
+
215
+ // CRUD 관련
216
+ callSaveApi: async (request) => {
217
+ if (get().saveSpinning) return;
218
+ if (!request) return;
219
+
220
+ try {
221
+ set({ saveSpinning: true });
222
+ request.__status__ = get().listSelectedRowKey ? ("U" as const) : ("C" as const);
223
+ await EntityService.postEntitySave(request);
224
+ await get().callListApi();
225
+ set({ modalOpen: false });
226
+ } catch (err) {
227
+ await errorHandling(err);
228
+ } finally {
229
+ set({ saveSpinning: false });
230
+ }
231
+ },
232
+
233
+ callDeleteApi: async (request) => {
234
+ if (get().deleteSpinning) return;
235
+ try {
236
+ set({ deleteSpinning: true });
237
+ await EntityService.postEntityDelete(request);
238
+ await get().callListApi();
239
+ } catch (err) {
240
+ await errorHandling(err);
241
+ } finally {
242
+ set({ deleteSpinning: false });
243
+ }
244
+ },
245
+ });
246
+
247
+ // ========== 7. Store 생성 ==========
248
+ export interface EntityListStore extends States, Actions, PageStoreActions<States> {}
249
+
250
+ export const useEntityListStore = create(
251
+ subscribeWithSelector<EntityListStore>((set, get) => ({
252
+ ...createState,
253
+ ...createActions(set, get),
254
+ })),
255
+ );
256
+
257
+ // ========== 8. 메타데이터 구독 ==========
258
+ useEntityListStore.subscribe(
259
+ (s): Record<keyof MetaData, any> => ({
260
+ programFn: s.programFn,
261
+ listRequestValue: s.listRequestValue,
262
+ listColWidths: s.listColWidths,
263
+ listSortParams: s.listSortParams,
264
+ listSelectedRowKey: s.listSelectedRowKey,
265
+ listCheckedIndexes: s.listCheckedIndexes,
266
+ modalOpen: s.modalOpen,
267
+ }),
268
+ getTabStoreListener<MetaData>(createState.routePath),
269
+ { equalityFn: shallow },
270
+ );
271
+ ```
272
+
273
+ ### Store 타입별 패턴
274
+
275
+ | 타입 | 특징 | 추가 States | 추가 Actions |
276
+ |------|------|-------------|--------------|
277
+ | **기본 목록** | 단순 조회, 페이징 | - | `callListApi`, `changeListPage` |
278
+ | **마스터-디테일** | 목록 + 상세 | `selectedItem`, `detail` | `setSelectedItem`, `callDetailApi` |
279
+ | **모달 포함** | 팝업 폼 | `modalOpen` | `setModalOpen`, `callSaveApi` |
280
+ | **삭제 포함** | 일괄 삭제 | `deleteSpinning` | `callDeleteApi` |
281
+ | **체크박스** | 다중 선택 | `checkedRowKeys` | `setCheckedRowKeys` |
282
+ | **트리 구조** | 계층 데이터 | `treeData`, `expandedKeys` | `setExpandedKeys`, `toggleExpand` |
283
+
284
+ ---
285
+
286
+ ## Hook 구현 패턴
287
+
288
+ ### 기본 서비스 훅
289
+
290
+ ```typescript
291
+ // use[Entity]Service.ts
292
+
293
+ import { useState, useCallback } from "react";
294
+ import { deleteEmptyValue } from "@core/utils/object";
295
+ import { errorHandling } from "utils/errorHandling";
296
+ import { EntityRes, PostEntityListRequest, EntityService } from "services";
297
+
298
+ interface UseEntityServiceParams {
299
+ autoFetch?: boolean;
300
+ initialParams?: Partial<PostEntityListRequest>;
301
+ }
302
+
303
+ export function useEntityService(params?: UseEntityServiceParams) {
304
+ const [list, setList] = useState<EntityRes[]>([]);
305
+ const [spinning, setSpinning] = useState(false);
306
+
307
+ const getList = useCallback(async (listParams?: Partial<PostEntityListRequest>) => {
308
+ setSpinning(true);
309
+ try {
310
+ const data = await EntityService.postEntityList(
311
+ deleteEmptyValue(listParams ?? {})
312
+ );
313
+ setList(data.ds);
314
+ } catch (err) {
315
+ await errorHandling(err);
316
+ } finally {
317
+ setSpinning(false);
318
+ }
319
+ }, []);
320
+
321
+ return {
322
+ list,
323
+ spinning,
324
+ getList,
325
+ };
326
+ }
327
+ ```
328
+
329
+ ### Repository 기반 훅 생성
330
+
331
+ MCP 서버를 활용한 훅 자동 생성:
332
+
333
+ ```
334
+ MCP Tool: mcp__mcp-axboot__generate_hook
335
+
336
+ Input:
337
+ - interfacePath: /path/to/XxxRepository.ts
338
+ - outputPath: /path/to/src/hooks/useXxxService.ts (optional)
339
+
340
+ Output:
341
+ - Repository 인터페이스 분석
342
+ - list/detail API 기반 훅 자동 생성
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Component 구현 패턴
348
+
349
+ ### App.tsx 기본 구조
350
+
351
+ ```typescript
352
+ // App.tsx
353
+
354
+ import { ProgramTitle, StoreResetButton } from "@core/components/common";
355
+ import { SearchParams, SearchParamType } from "@core/components/search";
356
+ import { useDidMountEffect } from "@core/hooks/useDidMountEffect";
357
+ import styled from "@emotion/styled";
358
+ import { Button, Flex, Form } from "antd";
359
+ import { useI18n, useUnmountEffect } from "hooks";
360
+ import React from "react";
361
+ import { PageLayout } from "styles/pageStyled";
362
+ import { errorHandling } from "utils/errorHandling";
363
+ import { ListDataGrid } from "./ListDataGrid";
364
+ import { useEntityListStore } from "./useEntityListStore";
365
+
366
+ interface Props {}
367
+
368
+ function App({}: Props) {
369
+ const { t } = useI18n("_entity");
370
+
371
+ // Store 상태
372
+ const init = useEntityListStore((s) => s.init);
373
+ const destroy = useEntityListStore((s) => s.destroy);
374
+ const callListApi = useEntityListStore((s) => s.callListApi);
375
+ const listRequestValue = useEntityListStore((s) => s.listRequestValue);
376
+ const setListRequestValue = useEntityListStore((s) => s.setListRequestValue);
377
+ const listSpinning = useEntityListStore((s) => s.listSpinning);
378
+ const programFn = useEntityListStore((s) => s.programFn);
379
+
380
+ const resizerContainerRef = React.useRef<HTMLDivElement>(null);
381
+ const [searchForm] = Form.useForm();
382
+
383
+ const handleSearch = React.useCallback(async () => {
384
+ try {
385
+ await callListApi({ pageNumber: 1 });
386
+ } catch (e) {
387
+ await errorHandling(e);
388
+ }
389
+ }, [callListApi]);
390
+
391
+ useDidMountEffect(() => {
392
+ (async () => {
393
+ try {
394
+ await init();
395
+ await callListApi();
396
+ } catch (e) {
397
+ await errorHandling(e);
398
+ }
399
+ })();
400
+ });
401
+
402
+ useUnmountEffect(() => {
403
+ destroy();
404
+ });
405
+
406
+ return (
407
+ <Container stretch role={"page-container"}>
408
+ <Header>
409
+ <ProgramTitle>
410
+ <StoreResetButton onReset={useEntityListStore.getState().reset} />
411
+ </ProgramTitle>
412
+
413
+ <Flex align={"center"} gap={6}>
414
+ {programFn?.fn01 && <Button onClick={handleSearch}>{t("검색")}</Button>}
415
+ </Flex>
416
+
417
+ <PageSearchBar>
418
+ <SearchParams
419
+ form={searchForm}
420
+ params={[
421
+ {
422
+ label: t("검색어"),
423
+ name: "filter",
424
+ type: SearchParamType.INPUT,
425
+ placeholder: t("검색어를 입력하세요"),
426
+ },
427
+ ]}
428
+ paramsValue={listRequestValue}
429
+ onChangeParamsValue={(value) => setListRequestValue(value)}
430
+ onSearch={handleSearch}
431
+ spinning={listSpinning}
432
+ />
433
+ </PageSearchBar>
434
+ </Header>
435
+
436
+ <Body ref={resizerContainerRef}>
437
+ <Frame>
438
+ <ListDataGrid />
439
+ </Frame>
440
+ </Body>
441
+ </Container>
442
+ );
443
+ }
444
+
445
+ const Container = styled(PageLayout)``;
446
+ const Header = styled(PageLayout.Header)``;
447
+ const PageSearchBar = styled(PageLayout.PageSearchBar)``;
448
+ const Body = styled(PageLayout.FrameRow)``;
449
+ const Frame = styled(PageLayout.FrameColumn)``;
450
+
451
+ export default App;
452
+ ```
453
+
454
+ ### ListDataGrid.tsx 기본 구조
455
+
456
+ ```typescript
457
+ // ListDataGrid.tsx
458
+
459
+ import { DataGrid } from "@core/components/DataGrid";
460
+ import { useDataGridColumns } from "@core/hooks/useDataGridColumns";
461
+ import { useDataGridSortedList } from "@core/hooks/useDataGridSortedList";
462
+ import { useContainerSize } from "hooks";
463
+ import React from "react";
464
+ import { PageLayout } from "styles/pageStyled";
465
+ import { useEntityListStore } from "./useEntityListStore";
466
+
467
+ interface DtoItem extends Entity {}
468
+
469
+ interface Props {}
470
+
471
+ export function ListDataGrid({}: Props) {
472
+ // Store 상태
473
+ const listColWidths = useEntityListStore((s) => s.listColWidths);
474
+ const listSortParams = useEntityListStore((s) => s.listSortParams);
475
+ const listData = useEntityListStore((s) => s.listData);
476
+ const listPage = useEntityListStore((s) => s.listPage);
477
+ const listSpinning = useEntityListStore((s) => s.listSpinning);
478
+ const setListColWidths = useEntityListStore((s) => s.setListColWidths);
479
+ const setListSortParams = useEntityListStore((s) => s.setListSortParams);
480
+ const changeListPage = useEntityListStore((s) => s.changeListPage);
481
+ const programFn = useEntityListStore((s) => s.programFn);
482
+
483
+ // 컬럼 정의
484
+ const { columns } = useDataGridColumns<DtoItem>(
485
+ [
486
+ { key: "id", label: "ID", align: "left", width: 80 },
487
+ { key: "name", label: "이름", align: "left", width: 150 },
488
+ { key: "status", label: "상태", align: "center", width: 100 },
489
+ ],
490
+ {
491
+ colWidths: listColWidths,
492
+ },
493
+ );
494
+
495
+ const sortedListData = useDataGridSortedList<DtoItem>(listData, listSortParams);
496
+ const { containerRef, width: containerWidth, height: containerHeight } = useContainerSize();
497
+
498
+ const handleColumnsChange = React.useCallback(
499
+ (columnIndex: number | null, { columns }: AXDGChangeColumnsInfo<DtoItem>) => {
500
+ setListColWidths(columns.map((column) => column.width));
501
+ },
502
+ [setListColWidths],
503
+ );
504
+
505
+ const onClickItem = React.useCallback((params: AXDGClickParams<DtoItem>) => {
506
+ // 행 클릭 처리
507
+ }, []);
508
+
509
+ return (
510
+ <>
511
+ <FormHeader>
512
+ 목록
513
+ <FormHeaderActions programFn={programFn} />
514
+ </FormHeader>
515
+ <Container ref={containerRef}>
516
+ <DataGrid<DtoItem>
517
+ frozenColumnIndex={0}
518
+ width={containerWidth}
519
+ height={containerHeight}
520
+ columns={columns}
521
+ data={sortedListData}
522
+ spinning={listSpinning}
523
+ onClick={onClickItem}
524
+ page={{
525
+ ...listPage,
526
+ loading: false,
527
+ onChange: async (currentPage, pageSize) => {
528
+ await changeListPage(currentPage, pageSize);
529
+ },
530
+ }}
531
+ sort={{
532
+ sortParams: listSortParams,
533
+ onChange: setListSortParams,
534
+ }}
535
+ onChangeColumns={handleColumnsChange}
536
+ />
537
+ </Container>
538
+ </>
539
+ );
540
+ }
541
+
542
+ const Container = styled.div`
543
+ flex: 1;
544
+ `;
545
+ const FormHeader = styled(PageLayout.FrameHeader)``;
546
+ ```
547
+
548
+ ---
549
+
550
+ ## Ant Design 컴포넌트 패턴
551
+
552
+ ### 1. Form 관련 컴포넌트
553
+
554
+ #### Form.Item 기본 패턴
555
+
556
+ ```typescript
557
+ import { Form, Input, Select } from "antd";
558
+
559
+ // 텍스트 입력
560
+ <Form.Item
561
+ name="fieldName"
562
+ label="필드명"
563
+ rules={[{ required: true, message: "필수 입력입니다" }]}
564
+ >
565
+ <Input placeholder="입력하세요" />
566
+ </Form.Item>
567
+
568
+ // 선택 (Select)
569
+ <Form.Item
570
+ name="status"
571
+ label="상태"
572
+ initialValue="Y"
573
+ >
574
+ <Select
575
+ options={[
576
+ { label: "활성", value: "Y" },
577
+ { label: "비활성", value: "N" },
578
+ ]}
579
+ />
580
+ </Form.Item>
581
+ ```
582
+
583
+ #### DatePicker 패턴
584
+
585
+ ```typescript
586
+ import { DatePicker } from "antd";
587
+ import dayjs from "dayjs";
588
+
589
+ // 단일 날짜
590
+ <Form.Item
591
+ name="date"
592
+ label="날짜"
593
+ >
594
+ <DatePicker
595
+ format="YYYY-MM-DD"
596
+ placeholder="날짜 선택"
597
+ />
598
+ </Form.Item>
599
+
600
+ // 날짜 범위
601
+ <Form.Item
602
+ name="dateRange"
603
+ label="기간"
604
+ >
605
+ <DatePicker.RangePicker
606
+ format="YYYY-MM-DD"
607
+ placeholder={["시작일", "종료일"]}
608
+ />
609
+ </Form.Item>
610
+ ```
611
+
612
+ ### 2. Button 패턴
613
+
614
+ ```typescript
615
+ import { Button } from "antd";
616
+
617
+ // 기본 버튼
618
+ <Button onClick={handleClick}>클릭</Button>
619
+
620
+ // 타입별
621
+ <Button type="primary">주요 버튼</Button>
622
+ <Button type="default">기본 버튼</Button>
623
+ <Button danger>삭제</Button>
624
+
625
+ // 아이콘 포함
626
+ <Button icon={<SearchOutlined />} onClick={handleSearch}>
627
+ 검색
628
+ </Button>
629
+
630
+ // 로딩 상태
631
+ <Button loading={loading} onClick={handleSave}>
632
+ 저장
633
+ </Button>
634
+
635
+ // 크기별
636
+ <Button size="small">작은 버튼</Button>
637
+ <Button size="middle">중간 버튼</Button>
638
+ <Button size="large">큰 버튼</Button>
639
+ ```
640
+
641
+ ### 3. Modal/Drawer 패턴
642
+
643
+ ```typescript
644
+ import { Modal } from "antd";
645
+ import { AXModal, AXDrawer } from "@core/components";
646
+
647
+ // Ant Design Modal
648
+ <Modal
649
+ open={open}
650
+ onOk={handleOk}
651
+ onCancel={handleCancel}
652
+ title="제목"
653
+ width={800}
654
+ >
655
+ {/* 내용 */}
656
+ </Modal>
657
+
658
+ // AXModal (프로젝트 커스텀)
659
+ <AXModal<Q, R>
660
+ open={modalOpen}
661
+ params={selectedItem}
662
+ onOk={handleSave}
663
+ onCancel={() => setModalOpen(false)}
664
+ width={800}
665
+ >
666
+ <AXModal.Header title="배너 정보" />
667
+ <AXModal.Body>
668
+ {/* 폼 내용 */}
669
+ </AXModal.Body>
670
+ <AXModal.Footer>
671
+ <Button onClick={() => setModalOpen(false)}>취소</Button>
672
+ <Button type="primary" onClick={handleSave}>저장</Button>
673
+ </AXModal.Footer>
674
+ </AXModal>
675
+ ```
676
+
677
+ ### 4. Table 관련
678
+
679
+ ```typescript
680
+ import { Table } from "antd";
681
+
682
+ <Table
683
+ columns={[
684
+ {
685
+ title: "이름",
686
+ dataIndex: "name",
687
+ key: "name",
688
+ align: "center",
689
+ width: 150,
690
+ },
691
+ {
692
+ title: "상태",
693
+ dataIndex: "status",
694
+ key: "status",
695
+ render: (value) => (value === "Y" ? "활성" : "비활성"),
696
+ },
697
+ ]}
698
+ dataSource={data}
699
+ rowKey="id"
700
+ pagination={{
701
+ current: page,
702
+ pageSize: pageSize,
703
+ total: total,
704
+ onChange: handlePageChange,
705
+ }}
706
+ loading={loading}
707
+ />
708
+ ```
709
+
710
+ ### 5. Space, Flex 레이아웃
711
+
712
+ ```typescript
713
+ import { Space, Flex } from "antd";
714
+
715
+ // Space (수평 간격)
716
+ <Space size={8}>
717
+ <Button>버튼1</Button>
718
+ <Button>버튼2</Button>
719
+ </Space>
720
+
721
+ // Flex (유연한 레이아웃)
722
+ <Flex gap={6} align="center" justify="space-between">
723
+ <div>왼쪽</div>
724
+ <div>오른쪽</div>
725
+ </Flex>
726
+
727
+ // 수직 Flex
728
+ <Flex vertical gap={12}>
729
+ <div>항목1</div>
730
+ <div>항목2</div>
731
+ </Flex>
732
+ ```
733
+
734
+ ---
735
+
736
+ ## AX Grid 컴포넌트 패턴
737
+
738
+ ### DataGrid 컬럼 타입
739
+
740
+ ```typescript
741
+ // src/components/@dataGridColumn.tsx 참고
742
+
743
+ type DataGridColumnType =
744
+ | "default" // 기본
745
+ | "money" // 금액 (오른쪽 정렬, 천단위 콤마)
746
+ | "percent" //百分比 (오른쪽 정렬, % 추가)
747
+ | "title" // 텍스트 (왼쪽 정렬)
748
+ | "rowNo" // 행 번호
749
+ | "date" // 날짜
750
+ | "dateTime" // 날짜시간 (YYYY-MM-DD HH:mm:ss)
751
+ | "user" // 사용자 정보
752
+ | "selectable" // 선택 가능 텍스트
753
+ | "fileDownload" // 파일 다운로드
754
+ | "custom"; // 커스텀 렌더링
755
+ ```
756
+
757
+ ### 컬럼 정의 예시
758
+
759
+ ```typescript
760
+ import { useDataGridColumns } from "@core/hooks/useDataGridColumns";
761
+
762
+ const { columns } = useDataGridColumns<DtoItem>([
763
+ // 기본 컬럼
764
+ { key: "id", label: "ID", align: "left", width: 80 },
765
+
766
+ // 프리셋 타입 사용
767
+ { key: "amount", label: "금액", type: "money" },
768
+ { key: "rate", label: "비율", type: "percent" },
769
+ { key: "name", label: "이름", type: "title" },
770
+ { key: "createdAt", label: "생성일", type: "dateTime" },
771
+
772
+ // 커스텀 렌더링
773
+ {
774
+ key: "status",
775
+ label: "상태",
776
+ width: 100,
777
+ align: "center",
778
+ itemRender: ({ value }) => {
779
+ return value === "Y" ? <Tag color="green">활성</Tag> : <Tag color="red">비활성</Tag>;
780
+ },
781
+ },
782
+
783
+ // 복합 컬럼
784
+ {
785
+ key: "user",
786
+ label: "사용자",
787
+ type: "user",
788
+ width: 120,
789
+ itemRender: ({ values }) => (
790
+ <div>
791
+ <div>{values.userName}</div>
792
+ <div style={{ fontSize: 12, color: "#999" }}>{values.userId}</div>
793
+ </div>
794
+ ),
795
+ },
796
+ ], {
797
+ colWidths: listColWidths,
798
+ deps: [CODES], // 컬럼 의존성 배열
799
+ });
800
+ ```
801
+
802
+ ### DataGrid Props 패턴
803
+
804
+ ```typescript
805
+ <DataGrid<DtoItem>
806
+ // 필수
807
+ width={containerWidth}
808
+ height={containerHeight}
809
+ columns={columns}
810
+ data={sortedListData}
811
+
812
+ // 행 식별
813
+ rowKey="id"
814
+ selectedRowKey={selectedItem?.id}
815
+
816
+ // 로딩
817
+ spinning={listSpinning}
818
+ loading={false}
819
+
820
+ // 이벤트
821
+ onClick={onClickItem}
822
+ onChangeColumns={handleColumnsChange}
823
+
824
+ // 페이징
825
+ page={{
826
+ currentPage: listPage.currentPage,
827
+ pageSize: listPage.pageSize,
828
+ totalPages: listPage.totalPages,
829
+ totalElements: listPage.totalElements,
830
+ loading: false,
831
+ onChange: async (currentPage, pageSize) => {
832
+ await changeListPage(currentPage, pageSize);
833
+ },
834
+ }}
835
+
836
+ // 정렬
837
+ sort={{
838
+ sortParams: listSortParams,
839
+ onChange: setListSortParams,
840
+ }}
841
+
842
+ // 체크박스
843
+ rowChecked={{
844
+ checkedRowKeys: checkedRowKeys,
845
+ onChange: (checkedIndexes, checkedRowKeys) => {
846
+ setCheckedRowKeys(checkedRowKeys);
847
+ },
848
+ }}
849
+
850
+ // 고정 컬럼
851
+ frozenColumnIndex={0}
852
+
853
+ // 행 높이
854
+ headerHeight={28}
855
+ itemHeight={20}
856
+ footerHeight={28}
857
+ />
858
+ ```
859
+
860
+ ### DataGrid Editor 패턴
861
+
862
+ ```typescript
863
+ import { InputEditor } from "@core/components/dataGridEditor/InputEditor";
864
+ import { SelectEditor } from "@core/components/dataGridEditor/getSelectEditor";
865
+ import { DateEditor } from "@core/components/dataGridEditor/DateEditor";
866
+
867
+ const editableColumns = [
868
+ { key: "id", label: "ID", width: 80, editable: false },
869
+ {
870
+ key: "name",
871
+ label: "이름",
872
+ width: 150,
873
+ editable: true,
874
+ itemRender: InputEditor,
875
+ },
876
+ {
877
+ key: "status",
878
+ label: "상태",
879
+ width: 100,
880
+ editable: true,
881
+ itemRender: SelectEditor,
882
+ editorConfig: {
883
+ options: [
884
+ { label: "활성", value: "Y" },
885
+ { label: "비활성", value: "N" },
886
+ ],
887
+ },
888
+ },
889
+ ];
890
+
891
+ <DataGrid
892
+ editable={true}
893
+ editTrigger="dblclick"
894
+ columns={editableColumns}
895
+ onChangeData={(index, columnIndex, item, column) => {
896
+ // 데이터 변경 처리
897
+ }}
898
+ />
899
+ ```
900
+
901
+ ---
902
+
903
+ ## 레이어 간 연결 방식
904
+
905
+ ### Store ↔ Component 연결
906
+
907
+ ```typescript
908
+ // Component에서 Store 사용
909
+
910
+ // 1. 단일 상태 구독
911
+ const listData = useEntityListStore((s) => s.listData);
912
+
913
+ // 2. 복수 상태 구독 (shallow 비교)
914
+ const { listData, listSpinning } = useEntityListStore(
915
+ (s) => ({ listData: s.listData, listSpinning: s.listSpinning }),
916
+ shallow
917
+ );
918
+
919
+ // 3. 액션 구독
920
+ const callListApi = useEntityListStore((s) => s.callListApi);
921
+ const setListRequestValue = useEntityListStore((s) => s.setListRequestValue);
922
+ ```
923
+
924
+ ### SearchParams → Store 연결
925
+
926
+ ```typescript
927
+ <SearchParams
928
+ form={searchForm}
929
+ params={searchParamsConfig}
930
+ paramsValue={listRequestValue} // Store의 검색 조건
931
+ onChangeParamsValue={(value) => { // 검색 조건 변경 시
932
+ setListRequestValue(value); // Store 업데이트
933
+ }}
934
+ onSearch={async () => { // 검색 버튼 클릭 시
935
+ await callListApi({ pageNumber: 1 }); // API 호출
936
+ }}
937
+ spinning={listSpinning} // 로딩 상태
938
+ />
939
+ ```
940
+
941
+ ### DataGrid → Store 연결
942
+
943
+ ```typescript
944
+ const sortedListData = useDataGridSortedList(listData, listSortParams);
945
+
946
+ <DataGrid
947
+ data={sortedListData} // Store 데이터
948
+ spinning={listSpinning} // Store 로딩 상태
949
+ page={{
950
+ ...listPage, // Store 페이지 정보
951
+ onChange: changeListPage, // Store 액션
952
+ }}
953
+ sort={{
954
+ sortParams: listSortParams, // Store 정렬 정보
955
+ onChange: setListSortParams, // Store 액션
956
+ }}
957
+ onChangeColumns={(idx, { columns }) => {
958
+ setListColWidths(columns.map(c => c.width)); // Store 액션
959
+ }}
960
+ />
961
+ ```
962
+
963
+ ---
964
+
965
+ ## MCP 활용 예시
966
+
967
+ ### MCP Tool 활용
968
+
969
+ ```typescript
970
+ // 1. Store + Hook 자동 생성
971
+ mcp__mcp-axboot__generate_store_and_hook_interactive({
972
+ fileName: "MemberRepository",
973
+ projectRoot: "/path/to/project",
974
+ })
975
+
976
+ // 2. Store 생성 (타입 지정)
977
+ mcp__mcp-axboot__generate_store({
978
+ interfacePath: "/path/to/MemberRepository.ts",
979
+ outputPath: "/path/to/src/stores/useMemberListStore.ts",
980
+ storeType: 2, // 1: 기본, 2: 마스터-디테일+모달+삭제
981
+ storeName: "useMemberListStore",
982
+ })
983
+
984
+ // 3. Hook 생성
985
+ mcp__mcp-axboot__generate_hook({
986
+ interfacePath: "/path/to/MemberRepository.ts",
987
+ outputPath: "/path/to/src/hooks/useMemberService.ts",
988
+ })
989
+
990
+ // 4. ListDataGrid 생성
991
+ mcp__mcp-axboot__generate_listdatagrid({
992
+ outputPath: "/path/to/src/pages/member/ListDataGrid.tsx",
993
+ config: {
994
+ storeName: "useMemberListStore",
995
+ dtoType: "MemberRes",
996
+ rowKey: "memberNo",
997
+ selectionMode: "multi",
998
+ withFormHeader: true,
999
+ withExcel: true,
1000
+ columns: [
1001
+ { key: "memberNo", label: "회원번호", type: "rowNo" },
1002
+ { key: "memberNm", label: "이름", align: "left" },
1003
+ { key: "joinDt", label: "가입일", type: "date" },
1004
+ ],
1005
+ },
1006
+ })
1007
+
1008
+ // 5. SearchParams 생성
1009
+ mcp__mcp-axboot__generate_search_params({
1010
+ appPath: "/path/to/src/pages/member/App.tsx",
1011
+ searchParams: {
1012
+ dateRange: {
1013
+ withPeriodType: true,
1014
+ periodOptions: [
1015
+ { label: "가입일", value: "JOIN" },
1016
+ { label: "수정일", value: "UPDATE" },
1017
+ ],
1018
+ },
1019
+ codes: [
1020
+ { name: "statusCd", label: "상태", codeVar: "STATUS_CODE", multi: true },
1021
+ ],
1022
+ search: {
1023
+ withSearchType: true,
1024
+ options: [
1025
+ { label: "이름", value: "NAME" },
1026
+ { label: "이메일", value: "EMAIL" },
1027
+ ],
1028
+ },
1029
+ },
1030
+ })
1031
+ ```
1032
+
1033
+ ### MCP로 자동 생성 가능한 파일
1034
+
1035
+ | 파일 | MCP Tool | 설명 |
1036
+ |------|----------|------|
1037
+ | `useXxxStore.ts` | `generate_store` | Repository 기반 Store 자동 생성 |
1038
+ | `useXxxService.ts` | `generate_hook` | Repository 기반 Hook 자동 생성 |
1039
+ | `ListDataGrid.tsx` | `generate_listdatagrid` | DataGrid 컴포넌트 자동 생성 |
1040
+ | `App.tsx` (SearchParams) | `generate_search_params` | 검색 폼 JSX 자동 생성 |
1041
+ | Store + Hook | `generate_store_and_hook_interactive` | 대화형 생성 |
1042
+
1043
+ ---
1044
+
1045
+ ## 파일 명명 규칙
1046
+
1047
+ ### Store
1048
+ ```
1049
+ use[도메인][기능]Store.ts
1050
+ 예: useMemberListStore.ts, useProductMasterStore.ts
1051
+ ```
1052
+
1053
+ ### Hook
1054
+ ```
1055
+ use[도메인][기능]Service.ts
1056
+ 예: useMemberService.ts, useProductService.ts
1057
+ ```
1058
+
1059
+ ### Component
1060
+ ```
1061
+ - ListDataGrid.tsx (목록 그리드)
1062
+ - FormModal.tsx (모달 폼)
1063
+ - App.tsx (메인 컴포넌트)
1064
+ ```
1065
+
1066
+ ### Repository
1067
+ ```
1068
+ [도메인]Repository.ts
1069
+ 예: MemberRepository.ts, ProductRepository.ts
1070
+ ```
1071
+
1072
+ ---
1073
+
1074
+ ## 공통 유틸리티
1075
+
1076
+ ### 데이터 변환
1077
+
1078
+ ```typescript
1079
+ import { deleteEmptyValue, dateRangeToDts } from "@core/utils/object";
1080
+
1081
+ // 빈 값 제거
1082
+ const cleanParams = deleteEmptyValue({
1083
+ name: "John",
1084
+ email: "", // 제거됨
1085
+ age: null, // 제거됨
1086
+ });
1087
+
1088
+ // 날짜 범위 변환
1089
+ const apiParams = {
1090
+ ...dateRangeToDts(dateRange, {
1091
+ startDate: "YYYY-MM-DD",
1092
+ endDate: "YYYY-MM-DD",
1093
+ }),
1094
+ };
1095
+ ```
1096
+
1097
+ ### 에러 핸들링
1098
+
1099
+ ```typescript
1100
+ import { errorHandling } from "utils/errorHandling";
1101
+
1102
+ try {
1103
+ await someApiCall();
1104
+ } catch (err) {
1105
+ await errorHandling(err); // 메시지 표시 자동 처리
1106
+ }
1107
+ ```
1108
+
1109
+ ### 포맷터
1110
+
1111
+ ```typescript
1112
+ import { formatterNumber, formatterDate } from "@core/utils";
1113
+
1114
+ // 숫자 포맷 (천단위 콤마)
1115
+ formatterNumber(1234567); // "1,234,567"
1116
+
1117
+ // 날짜 포맷
1118
+ formatterDate("2024-01-15 10:30:00", "YYYY-MM-DD"); // "2024-01-15"
1119
+ ```
1120
+
1121
+ ---
1122
+
1123
+ ## 요약
1124
+
1125
+ 이 가이드를 통해 다음과 같은 작업을 MCP 서버를 통해 자동화할 수 있습니다:
1126
+
1127
+ 1. **Repository 분석** → 인터페이스에서 타입 추출
1128
+ 2. **Store 자동 생성** → 타입별 템플릿 적용
1129
+ 3. **Hook 자동 생성** → API 호출 래핑
1130
+ 4. **Component 자동 생성** → DataGrid, SearchParams 등
1131
+ 5. **검색 폼 자동 생성** → SearchParams JSX
1132
+
1133
+ MCP 서버는 이 패턴들을 참고하여 일관성 있는 코드를 생성합니다.