@axboot-mcp/mcp-server 1.0.1 → 1.0.6
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/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.6",
|
|
7
7
|
"description": "Axboot MCP Server - Store 생성 및 다른 기능 확장 가능",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"author": "",
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@axboot-mcp/mcp-server": "^1.0.1",
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
31
32
|
"chokidar": "^5.0.0",
|
|
32
33
|
"glob": "^13.0.0",
|
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# MCP 명령어 사용 가이드
|
|
2
|
+
|
|
3
|
+
이 문서는 NH-FE-B 프로젝트에서 MCP(Model Context Protocol)를 사용하여 SearchParams와 ListDataGrid를 자동 생성하는 방법을 안내합니다.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 목차
|
|
8
|
+
|
|
9
|
+
1. [케이스 1: SearchParams 생성 (검색 영역)](#케이스-1-searchparams-생성-검색-영역)
|
|
10
|
+
2. [케이스 2: ListDataGrid 생성 (목록 테이블)](#케이스-2-listdatagrid-생성-목록-테이블)
|
|
11
|
+
3. [케이스 3: 완전한 화면 생성 (검색 + 목록)](#케이스-3-완전한-화면-생성-검색-목록)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 케이스 1: SearchParams 생성 (검색 영역)
|
|
16
|
+
|
|
17
|
+
### 개요
|
|
18
|
+
|
|
19
|
+
페이지 상단의 **검색 조건 영역**을 자동 생성합니다. App.tsx 파일을 수정하여 `<PageSearchBar>` 컴포넌트를 추가합니다.
|
|
20
|
+
|
|
21
|
+
### 명령어
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
axboot_generate_search_params
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 파라미터
|
|
28
|
+
|
|
29
|
+
| 파라미터 | 타입 | 필수 | 설명 |
|
|
30
|
+
|---------|------|:----:|------|
|
|
31
|
+
| `searchDtoPath` | string | ✓ | 검색용 DTO 파일 경로 (절대 경로) |
|
|
32
|
+
| `appPath` | string | ✗ | App.tsx 경로 (자동 감지됨) |
|
|
33
|
+
| `searchParams` | object | ✗ | 수동 설정 (DTO 사용 시 불필요) |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 예시 1: 기본 검색 (날짜 범위)
|
|
38
|
+
|
|
39
|
+
**DTO:** `BannerSearch.ts`
|
|
40
|
+
```typescript
|
|
41
|
+
export interface BannerSearch extends Request {
|
|
42
|
+
fromDate?: string; // 등록일 시작
|
|
43
|
+
toDate?: string; // 등록일 종료
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**요청:**
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerSearch.ts"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**결과:** App.tsx에 날짜 범위 검색이 추가됨
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 예시 2: 코드 선택 포함
|
|
59
|
+
|
|
60
|
+
**DTO:** `ProductSearch.ts`
|
|
61
|
+
```typescript
|
|
62
|
+
export interface ProductSearch extends Request {
|
|
63
|
+
categoryCd?: string; // 카테고리 코드
|
|
64
|
+
useYn?: string; // 사용여부
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**요청:**
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/ProductSearch.ts"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**결과:**
|
|
76
|
+
- 카테고리 셀렉트박스 추가
|
|
77
|
+
- 사용여부 라디오 버튼 추가
|
|
78
|
+
- CodeModel에 `CATEGORYCD` 자동 추가
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### 예시 3: 복합 검색 조건
|
|
83
|
+
|
|
84
|
+
**DTO:** `OrderSearch.ts`
|
|
85
|
+
```typescript
|
|
86
|
+
export interface OrderSearch extends Request {
|
|
87
|
+
orderDateFrom?: string; // 주문일 시작
|
|
88
|
+
orderDateTo?: string; // 주문일 종료
|
|
89
|
+
orderStatusCd?: string; // 주문상태 코드
|
|
90
|
+
paymentTypeCd?: string; // 결제수단 코드
|
|
91
|
+
useYn?: string; // 사용여부
|
|
92
|
+
searchText?: string; // 검색어
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**요청:**
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/OrderSearch.ts"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**결과:**
|
|
104
|
+
- 날짜 범위 (주문일)
|
|
105
|
+
- 코드 선택 (주문상태, 결제수단)
|
|
106
|
+
- Y/N 라디오 (사용여부)
|
|
107
|
+
- 검색어 입력
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### 예시 4: 수동 설정
|
|
112
|
+
|
|
113
|
+
**요청:**
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"appPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/App.tsx",
|
|
117
|
+
"searchParams": {
|
|
118
|
+
"dateRange": {
|
|
119
|
+
"label": "등록일",
|
|
120
|
+
"presetType": "all"
|
|
121
|
+
},
|
|
122
|
+
"codes": [
|
|
123
|
+
{
|
|
124
|
+
"name": "statusCd",
|
|
125
|
+
"label": "상태",
|
|
126
|
+
"codeVar": "STATUS_CODE",
|
|
127
|
+
"width": 150
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"search": {
|
|
131
|
+
"withSearchType": false,
|
|
132
|
+
"textName": "searchText"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### 자동 감지 필드 규칙
|
|
141
|
+
|
|
142
|
+
| 필드 패턴 | 감지 결과 |
|
|
143
|
+
|-----------|----------|
|
|
144
|
+
| `fromDate`, `toDate`, `regDtFrom`, `regDtTo` | 날짜 범위 |
|
|
145
|
+
| `xxxTpcd`, `xxxClcd`, `xxxCd` (2글자 이상) | 코드 선택 |
|
|
146
|
+
| `xxxYn` | Y/N 라디오 |
|
|
147
|
+
| `searchText`, `keyword`, `xxxText` | 검색어 입력 |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 케이스 2: ListDataGrid 생성 (목록 테이블)
|
|
152
|
+
|
|
153
|
+
### 개요
|
|
154
|
+
|
|
155
|
+
목록 **테이블 컬럼**을 자동 생성합니다. `ListDataGrid.tsx` 파일을 새로 생성합니다.
|
|
156
|
+
|
|
157
|
+
### 명령어
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
axboot_generate_listdatagrid
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 파라미터
|
|
164
|
+
|
|
165
|
+
| 파라미터 | 타입 | 필수 | 설명 |
|
|
166
|
+
|---------|------|:----:|------|
|
|
167
|
+
| `outputPath` | string | ✓ | 생성할 파일 경로 (절대 경로) |
|
|
168
|
+
| `responseDtoPath` | string | ✓ | 목록용 DTO 파일 경로 |
|
|
169
|
+
| `storeName` | string | ✓ | Zustand Store 이름 |
|
|
170
|
+
| `rowKey` | string | ✓ | 행 키 (예: uuid, id) |
|
|
171
|
+
| `config` | object | ✗ | 수동 설정 (DTO 사용 시 불필요) |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### 예시 1: 기본 목록
|
|
176
|
+
|
|
177
|
+
**DTO:** `BannerRes.ts`
|
|
178
|
+
```typescript
|
|
179
|
+
export interface BannerRes extends Response {
|
|
180
|
+
uuid: string;
|
|
181
|
+
title: string;
|
|
182
|
+
statusCd: string;
|
|
183
|
+
useYn: string;
|
|
184
|
+
regDt: string;
|
|
185
|
+
regUser: string;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**요청:**
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/ListDataGrid.tsx",
|
|
193
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerRes.ts",
|
|
194
|
+
"storeName": "useBannerStore",
|
|
195
|
+
"rowKey": "uuid"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**결과:**
|
|
200
|
+
```typescript
|
|
201
|
+
// 자동 생성된 컬럼
|
|
202
|
+
{ key: "uuid", label: t("번호"), type: "rowNo" }
|
|
203
|
+
{ key: "title", label: t("Title"), type: "title", align: "left", width: 200 }
|
|
204
|
+
{ key: "statusCd", label: t("Status"), type: "custom", codeVar: "STATUSCD", width: 120 }
|
|
205
|
+
{ key: "useYn", label: t("Use"), type: "selectable", align: "center", width: 80 }
|
|
206
|
+
{ key: "regDt", label: t("Reg"), type: "date", align: "center", width: 100 }
|
|
207
|
+
{ key: "regUser", label: t("RegUser"), type: "user", align: "left", width: 100 }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### 예시 2: 금액/날짜 포함 목록
|
|
213
|
+
|
|
214
|
+
**DTO:** `OrderRes.ts`
|
|
215
|
+
```typescript
|
|
216
|
+
export interface OrderRes extends Response {
|
|
217
|
+
orderNo: string;
|
|
218
|
+
orderDate: string;
|
|
219
|
+
customerName: string;
|
|
220
|
+
orderAmt: number;
|
|
221
|
+
paymentTypeCd: string;
|
|
222
|
+
orderStatusCd: string;
|
|
223
|
+
regDttm: string;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**요청:**
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/order/ListDataGrid.tsx",
|
|
231
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/OrderRes.ts",
|
|
232
|
+
"storeName": "useOrderStore",
|
|
233
|
+
"rowKey": "orderNo"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**결과:**
|
|
238
|
+
- `orderDate` → `date` 타입 (날짜)
|
|
239
|
+
- `orderAmt` → `money` 타입 (금액, 우측 정렬)
|
|
240
|
+
- `regDttm` → `dateTime` 타입 (날짜시간)
|
|
241
|
+
- `paymentTypeCd`, `orderStatusCd` → 코드 변환
|
|
242
|
+
- `customerName` → `user` 타입
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### 예시 3: 이미지 포함 목록
|
|
247
|
+
|
|
248
|
+
**DTO:** `ProductRes.ts`
|
|
249
|
+
```typescript
|
|
250
|
+
export interface ProductRes extends Response {
|
|
251
|
+
prdCd: string;
|
|
252
|
+
prdName: string;
|
|
253
|
+
thumbImage: string;
|
|
254
|
+
listImage: string;
|
|
255
|
+
price: number;
|
|
256
|
+
displayYn: string;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**요청:**
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/product/ListDataGrid.tsx",
|
|
264
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/ProductRes.ts",
|
|
265
|
+
"storeName": "useProductStore",
|
|
266
|
+
"rowKey": "prdCd"
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**결과:**
|
|
271
|
+
- `thumbImage` → `ThumbPreview` (썸네일)
|
|
272
|
+
- `listImage` → `ImagePreview` (이미지 프리뷰)
|
|
273
|
+
- `price` → `money` 타입
|
|
274
|
+
- `displayYn` → `selectable` 타입
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
### 예시 4: 게시판 스타일
|
|
279
|
+
|
|
280
|
+
**DTO:** `BoardRes.ts`
|
|
281
|
+
```typescript
|
|
282
|
+
export interface BoardRes extends Response {
|
|
283
|
+
bbsNo: string;
|
|
284
|
+
bbsTitle: string;
|
|
285
|
+
bbsTypeCd: string;
|
|
286
|
+
noticeYn: string;
|
|
287
|
+
fileCount: number;
|
|
288
|
+
readCount: number;
|
|
289
|
+
regDt: string;
|
|
290
|
+
regUser: string;
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**요청:**
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/board/ListDataGrid.tsx",
|
|
298
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BoardRes.ts",
|
|
299
|
+
"storeName": "useBoardStore",
|
|
300
|
+
"rowKey": "bbsNo"
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**결과:**
|
|
305
|
+
- `bbsTitle` → `bbsTitle` 타입 (제목 + 파일 유형 표시)
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### 예시 5: 옵션 추가 (엑셀, 삭제, 복사)
|
|
310
|
+
|
|
311
|
+
**요청:**
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/ListDataGrid.tsx",
|
|
315
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerRes.ts",
|
|
316
|
+
"storeName": "useBannerStore",
|
|
317
|
+
"rowKey": "uuid",
|
|
318
|
+
"config": {
|
|
319
|
+
"withFormHeader": true,
|
|
320
|
+
"withExcel": true,
|
|
321
|
+
"withDelete": true,
|
|
322
|
+
"withCopy": true,
|
|
323
|
+
"selectionMode": "single",
|
|
324
|
+
"onClickType": "modal"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### 자동 감지 컬럼 타입
|
|
332
|
+
|
|
333
|
+
| 필드 패턴 | 컬럼 타입 | 정렬 | 너비 |
|
|
334
|
+
|-----------|----------|------|------|
|
|
335
|
+
| `xxxDt`, `xxxDate` | `date` | center | 100 |
|
|
336
|
+
| `xxxDttm`, `xxxDateTime` | `dateTime` | center | 140 |
|
|
337
|
+
| `xxxAmt`, `xxxPrice`, `xxxCost` | `money` | right | 120 |
|
|
338
|
+
| `xxxYn` | `selectable` | center | 80 |
|
|
339
|
+
| `xxxNm`, `xxxName`, `xxxUser` | `user` | left | 100 |
|
|
340
|
+
| `xxxTpcd`, `xxxClcd`, `xxxCd` | `custom` (코드) | - | 120 |
|
|
341
|
+
| `title`, `subject`, `bbsTitle` | `title`/`bbsTitle` | left | 200 |
|
|
342
|
+
| `xxxImg`, `xxxThumb` | `image` | center | 100 |
|
|
343
|
+
| `content`, `description`, `remark` | `custom` | left | 300 |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 케이스 3: 완전한 화면 생성 (검색 + 목록)
|
|
348
|
+
|
|
349
|
+
### 개요
|
|
350
|
+
|
|
351
|
+
검색 영역과 목록 테이블을 모두 생성하여 **완전한 화면**을 만듭니다. 두 명령어를 순서대로 실행합니다.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
### 예시 1: 배너 관리 화면
|
|
356
|
+
|
|
357
|
+
**Step 1: SearchParams 생성**
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerSearch.ts"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Step 2: ListDataGrid 생성**
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/ListDataGrid.tsx",
|
|
368
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerRes.ts",
|
|
369
|
+
"storeName": "useBannerStore",
|
|
370
|
+
"rowKey": "uuid"
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Step 3: App.tsx에 import 추가**
|
|
375
|
+
```typescript
|
|
376
|
+
import { ListDataGrid } from "./ListDataGrid";
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**결과:** 검색 + 목록 화면 완성
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### 예시 2: 상품 관리 화면
|
|
384
|
+
|
|
385
|
+
**DTO:**
|
|
386
|
+
- `ProductSearch.ts` (검색용)
|
|
387
|
+
- `ProductRes.ts` (목록용)
|
|
388
|
+
|
|
389
|
+
**Step 1: SearchParams**
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/ProductSearch.ts"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Step 2: ListDataGrid**
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/product/ListDataGrid.tsx",
|
|
400
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/ProductRes.ts",
|
|
401
|
+
"storeName": "useProductStore",
|
|
402
|
+
"rowKey": "prdCd",
|
|
403
|
+
"config": {
|
|
404
|
+
"withFormHeader": true,
|
|
405
|
+
"withExcel": true,
|
|
406
|
+
"selectionMode": "single",
|
|
407
|
+
"onClickType": "modal"
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### 예시 3: 주문 관리 화면
|
|
415
|
+
|
|
416
|
+
**DTO:**
|
|
417
|
+
- `OrderSearch.ts`
|
|
418
|
+
- `OrderRes.ts`
|
|
419
|
+
|
|
420
|
+
**Step 1: SearchParams**
|
|
421
|
+
```json
|
|
422
|
+
{
|
|
423
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/OrderSearch.ts"
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Step 2: ListDataGrid**
|
|
428
|
+
```json
|
|
429
|
+
{
|
|
430
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/order/ListDataGrid.tsx",
|
|
431
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/OrderRes.ts",
|
|
432
|
+
"storeName": "useOrderStore",
|
|
433
|
+
"rowKey": "orderNo",
|
|
434
|
+
"config": {
|
|
435
|
+
"selectionMode": "multi",
|
|
436
|
+
"onClickType": "link"
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
### 예시 4: 게시판 화면
|
|
444
|
+
|
|
445
|
+
**DTO:**
|
|
446
|
+
- `BbsSearch.ts`
|
|
447
|
+
- `BbsRes.ts`
|
|
448
|
+
|
|
449
|
+
**Step 1: SearchParams**
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BbsSearch.ts"
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Step 2: ListDataGrid**
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/bbs/ListDataGrid.tsx",
|
|
460
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BbsRes.ts",
|
|
461
|
+
"storeName": "useBbsStore",
|
|
462
|
+
"rowKey": "bbsNo",
|
|
463
|
+
"config": {
|
|
464
|
+
"withFormHeader": true,
|
|
465
|
+
"withExcel": true,
|
|
466
|
+
"onClickType": "modal"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### 예시 5: 코드 관리 화면
|
|
474
|
+
|
|
475
|
+
**DTO:**
|
|
476
|
+
- `CodeSearch.ts`
|
|
477
|
+
- `CodeRes.ts`
|
|
478
|
+
|
|
479
|
+
**Step 1: SearchParams**
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"searchDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/CodeSearch.ts"
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Step 2: ListDataGrid**
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"outputPath": "/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/code/ListDataGrid.tsx",
|
|
490
|
+
"responseDtoPath": "/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/CodeRes.ts",
|
|
491
|
+
"storeName": "useCodeStore",
|
|
492
|
+
"rowKey": "cd",
|
|
493
|
+
"config": {
|
|
494
|
+
"withFormHeader": true,
|
|
495
|
+
"withDelete": true,
|
|
496
|
+
"selectionMode": "single",
|
|
497
|
+
"onClickType": "modal"
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 파라미터 이름 구분
|
|
505
|
+
|
|
506
|
+
| 핸들러 | 파라미터 | 용도 | DTO 예시 |
|
|
507
|
+
|--------|---------|------|----------|
|
|
508
|
+
| `axboot_generate_search_params` | `searchDtoPath` | 검색 조건 생성 | `BannerSearch` |
|
|
509
|
+
| `axboot_generate_listdatagrid` | `responseDtoPath` | 목록 테이블 생성 | `BannerRes` |
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## 추가 기능
|
|
514
|
+
|
|
515
|
+
### ESLint 자동 실행
|
|
516
|
+
|
|
517
|
+
모든 생성/수정 작업 후 ESLint가 자동으로 실행됩니다.
|
|
518
|
+
|
|
519
|
+
### CodeModel 자동 업데이트
|
|
520
|
+
|
|
521
|
+
코드 필드가 감지되면 `useCodeStore.ts`의 `CodeModel` 인터페이스에 자동 추가됩니다.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 팁
|
|
526
|
+
|
|
527
|
+
1. **DTO 네이밍 컨벤션 따르기**
|
|
528
|
+
- 검색용: `xxxSearch.ts` → `xxxSearch` 인터페이스
|
|
529
|
+
- 목록용: `xxxRes.ts` → `xxxRes` 인터페이스
|
|
530
|
+
|
|
531
|
+
2. **주석 활용**
|
|
532
|
+
- DTO 필드에 주석을 달면 라벨로 사용됩니다
|
|
533
|
+
```typescript
|
|
534
|
+
export interface BannerRes extends Response {
|
|
535
|
+
uuid: string; // UUID
|
|
536
|
+
title: string; // 배너 제목
|
|
537
|
+
statusCd?: string; // 상태
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
3. **rowKey 선택**
|
|
542
|
+
- 주로 `uuid`, `id`, `code` 등 고유값 사용
|
|
543
|
+
- DB 기본키와 일치시키기
|
|
544
|
+
|
|
545
|
+
4. **Store 생성 먼저**
|
|
546
|
+
- `axboot_generate_store_and_hook_interactive`로 Store 생성 후 사용
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## 문제 해결
|
|
551
|
+
|
|
552
|
+
### 문제: DTO를 찾을 수 없음
|
|
553
|
+
**해결:** 절대 경로를 사용하세요
|
|
554
|
+
```
|
|
555
|
+
/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerSearch.ts
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### 문제: CodeModel 이미 존재
|
|
559
|
+
**해결:** 이미 존재하는 코드는 건너뜁니다. 안심하세요.
|
|
560
|
+
|
|
561
|
+
### 문제: App.tsx 경로 자동 감지 실패
|
|
562
|
+
**해결:** `appPath` 파라미터로 직접 지정하세요.
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 참고
|
|
567
|
+
|
|
568
|
+
- Store 생성: `axboot_generate_store_and_hook_interactive`
|
|
569
|
+
- SearchParams 패턴: `src/prompts/search-params.md`
|
|
570
|
+
- ListDataGrid 패턴: `src/prompts/listdatagrid-usage.md`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ListDataGrid 컴포넌트 생성 도구
|
|
@@ -8,8 +9,14 @@ import * as path from 'path';
|
|
|
8
9
|
export interface GenerateListDataGridParams {
|
|
9
10
|
/** 생성할 ListDataGrid 파일 경로 (절대 경로) */
|
|
10
11
|
outputPath: string;
|
|
11
|
-
/** ListDataGrid 설정 */
|
|
12
|
-
config
|
|
12
|
+
/** ListDataGrid 설정 (responseDtoPath를 사용하면 자동 생성됨) */
|
|
13
|
+
config?: ListDataGridConfig;
|
|
14
|
+
/** DTO 파일 경로 (절대 경로) - DTO를 분석해서 자동으로 컬럼 생성 */
|
|
15
|
+
responseDtoPath?: string;
|
|
16
|
+
/** Store 이름 (responseDtoPath 시 필수) */
|
|
17
|
+
storeName?: string;
|
|
18
|
+
/** 행 키 (responseDtoPath 시 필수, 예: id, prdCd) */
|
|
19
|
+
rowKey?: string;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export interface ListDataGridConfig {
|
|
@@ -68,10 +75,53 @@ export interface ColumnConfig {
|
|
|
68
75
|
export async function generateListDataGrid(params: GenerateListDataGridParams): Promise<{
|
|
69
76
|
content: Array<{ type: string; text: string }>;
|
|
70
77
|
}> {
|
|
71
|
-
const { outputPath, config } = params;
|
|
78
|
+
const { outputPath, config, responseDtoPath, storeName, rowKey } = params;
|
|
79
|
+
|
|
80
|
+
let finalConfig = config;
|
|
81
|
+
|
|
82
|
+
// DTO 파싱으로 자동 생성
|
|
83
|
+
if (responseDtoPath && !finalConfig) {
|
|
84
|
+
if (!storeName) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: JSON.stringify({
|
|
89
|
+
success: false,
|
|
90
|
+
error: 'responseDtoPath를 사용할 때는 storeName이 필수입니다.',
|
|
91
|
+
}),
|
|
92
|
+
}],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!rowKey) {
|
|
96
|
+
return {
|
|
97
|
+
content: [{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: JSON.stringify({
|
|
100
|
+
success: false,
|
|
101
|
+
error: 'responseDtoPath를 사용할 때는 rowKey가 필수입니다.',
|
|
102
|
+
}),
|
|
103
|
+
}],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parsed = parseDtoForListDataGrid(responseDtoPath, storeName, rowKey);
|
|
108
|
+
finalConfig = parsed.config;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!finalConfig) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
success: false,
|
|
117
|
+
error: 'config 또는 responseDtoPath 중 하나는 필수입니다.',
|
|
118
|
+
}),
|
|
119
|
+
}],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
72
122
|
|
|
73
123
|
// 코드 생성
|
|
74
|
-
const code = generateListDataGridCode(
|
|
124
|
+
const code = generateListDataGridCode(finalConfig);
|
|
75
125
|
|
|
76
126
|
// 파일에 쓰기
|
|
77
127
|
const dir = path.dirname(outputPath);
|
|
@@ -79,6 +129,15 @@ export async function generateListDataGrid(params: GenerateListDataGridParams):
|
|
|
79
129
|
fs.mkdirSync(dir, { recursive: true });
|
|
80
130
|
}
|
|
81
131
|
fs.writeFileSync(outputPath, code, 'utf-8');
|
|
132
|
+
console.error(`[MCP] ListDataGrid 파일 생성 완료: ${outputPath}`);
|
|
133
|
+
|
|
134
|
+
// ESLint 실행
|
|
135
|
+
try {
|
|
136
|
+
execSync(`npx eslint "${outputPath}" --fix`, { cwd: dir, stdio: 'pipe' });
|
|
137
|
+
console.error(`[MCP] ESLint 실행 완료: ${outputPath}`);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error(`[MCP] ESLint 실행 실패 (무시): ${e}`);
|
|
140
|
+
}
|
|
82
141
|
|
|
83
142
|
return {
|
|
84
143
|
content: [
|
|
@@ -88,6 +147,7 @@ export async function generateListDataGrid(params: GenerateListDataGridParams):
|
|
|
88
147
|
success: true,
|
|
89
148
|
outputPath,
|
|
90
149
|
message: `ListDataGrid 파일이 생성되었습니다: ${outputPath}`,
|
|
150
|
+
dtoAnalysis: responseDtoPath ? parseDtoForListDataGrid(responseDtoPath, storeName!, rowKey!).analysis : undefined,
|
|
91
151
|
nextSteps: `
|
|
92
152
|
## 다음 단계
|
|
93
153
|
|
|
@@ -117,19 +177,19 @@ import { ListDataGrid } from "./ListDataGrid";
|
|
|
117
177
|
\`\`\`typescript
|
|
118
178
|
const { columns } = useDataGridColumns<DtoItem>(
|
|
119
179
|
[
|
|
120
|
-
{ key: "${
|
|
180
|
+
{ key: "${finalConfig.rowKey}", label: t("번호"), type: "rowNo" },
|
|
121
181
|
// TODO: 여기에 컬럼 추가
|
|
122
|
-
${
|
|
182
|
+
${finalConfig.columns.map(c => `{ key: "${c.key}", label: t("${c.label}")${c.type ? `, type: "${c.type}"` : ''}${c.width ? `, width: ${c.width}` : ''}${c.align ? `, align: "${c.align}"` : ''} }`).join('\n ')}
|
|
123
183
|
],
|
|
124
184
|
{
|
|
125
185
|
colWidths: listColWidths,
|
|
126
|
-
${
|
|
186
|
+
${finalConfig.columns.some(c => c.codeVar) ? `deps: [${finalConfig.columns.filter(c => c.codeVar).map(c => c.codeVar).join(', ')}],` : ''}
|
|
127
187
|
}
|
|
128
188
|
);
|
|
129
189
|
\`\`\`
|
|
130
190
|
|
|
131
191
|
### 4. onClickItem 핸들러 구현
|
|
132
|
-
onClickType이 "${
|
|
192
|
+
onClickType이 "${finalConfig.onClickType || 'select'}"이므로 필요한 동작을 구현하세요.
|
|
133
193
|
`.trim(),
|
|
134
194
|
}),
|
|
135
195
|
},
|
|
@@ -659,3 +719,247 @@ const FormHeader = styled(PageLayout.FrameHeader)\`\`;`;
|
|
|
659
719
|
|
|
660
720
|
return styled;
|
|
661
721
|
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* DTO 필드 정보
|
|
725
|
+
*/
|
|
726
|
+
interface DtoField {
|
|
727
|
+
name: string;
|
|
728
|
+
type: string;
|
|
729
|
+
optional: boolean;
|
|
730
|
+
comment?: string;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* DTO 파일 파싱하여 ListDataGrid 설정 자동 생성
|
|
735
|
+
*/
|
|
736
|
+
function parseDtoForListDataGrid(
|
|
737
|
+
responseDtoPath: string,
|
|
738
|
+
storeName: string,
|
|
739
|
+
rowKey: string
|
|
740
|
+
): { config: ListDataGridConfig; analysis: string } {
|
|
741
|
+
let content = '';
|
|
742
|
+
try {
|
|
743
|
+
content = fs.readFileSync(responseDtoPath, 'utf-8');
|
|
744
|
+
} catch (e) {
|
|
745
|
+
return {
|
|
746
|
+
config: {
|
|
747
|
+
storeName,
|
|
748
|
+
dtoType: 'DtoItem',
|
|
749
|
+
rowKey,
|
|
750
|
+
columns: [],
|
|
751
|
+
},
|
|
752
|
+
analysis: `DTO 파일을 읽을 수 없습니다: ${responseDtoPath}`,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// 인터페이스 내용 추출
|
|
757
|
+
const interfaceMatch = content.match(/export\s+interface\s+(\w+)\s+extends\s+[\w.]+\s*\{([\s\S]*?)^\}/m);
|
|
758
|
+
if (!interfaceMatch) {
|
|
759
|
+
return {
|
|
760
|
+
config: {
|
|
761
|
+
storeName,
|
|
762
|
+
dtoType: 'DtoItem',
|
|
763
|
+
rowKey,
|
|
764
|
+
columns: [],
|
|
765
|
+
},
|
|
766
|
+
analysis: 'DTO 인터페이스를 찾을 수 없습니다.',
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const interfaceBody = interfaceMatch[2];
|
|
771
|
+
const interfaceName = interfaceMatch[1];
|
|
772
|
+
|
|
773
|
+
// 필드 파싱
|
|
774
|
+
const fieldLines = interfaceBody.split('\n').filter(line => line.trim() && !line.trim().startsWith('//'));
|
|
775
|
+
|
|
776
|
+
const fields: DtoField[] = [];
|
|
777
|
+
const columns: ColumnConfig[] = [];
|
|
778
|
+
const detectedCodes: string[] = [];
|
|
779
|
+
|
|
780
|
+
for (const line of fieldLines) {
|
|
781
|
+
// 주석 추출
|
|
782
|
+
const commentMatch = line.match(/\/\/(.*)$/);
|
|
783
|
+
const comment = commentMatch ? commentMatch[1].trim() : '';
|
|
784
|
+
|
|
785
|
+
// 필드 정의 추출
|
|
786
|
+
const fieldMatch = line.match(/^\s*(\w+)\s*:\s*([^;]+);/);
|
|
787
|
+
if (fieldMatch) {
|
|
788
|
+
const name = fieldMatch[1];
|
|
789
|
+
const type = fieldMatch[2].trim();
|
|
790
|
+
const optional = type.endsWith('?');
|
|
791
|
+
|
|
792
|
+
fields.push({ name, type, optional, comment });
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// 컬럼 설정 생성
|
|
797
|
+
for (const field of fields) {
|
|
798
|
+
const { name, comment } = field;
|
|
799
|
+
|
|
800
|
+
// 페이지네이션 필드 제외
|
|
801
|
+
if (['pageNumber', 'pageSize', 'pageNo', 'rowNum', 'totalCount'].includes(name)) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// rowKey 제외 (번호 컬럼으로 자동 생성됨)
|
|
806
|
+
if (name === rowKey) {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let column: ColumnConfig | null = null;
|
|
811
|
+
|
|
812
|
+
// 코드 필드 감지 (xxxTpcd, xxxClcd, xxxKncd, xxxCd 등)
|
|
813
|
+
if (name.endsWith('Tpcd') || name.endsWith('Clcd') || name.endsWith('Kncd') ||
|
|
814
|
+
(name.endsWith('Cd') && name.length > 2 && !name.endsWith('YnCd'))) {
|
|
815
|
+
const label = comment || name.replace(/Tpcd$|Clcd$|Kncd$|Cd$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
816
|
+
const codeVar = name.toUpperCase();
|
|
817
|
+
detectedCodes.push(codeVar);
|
|
818
|
+
|
|
819
|
+
column = {
|
|
820
|
+
key: name.charAt(0).toLowerCase() + name.slice(1), // camelCase
|
|
821
|
+
label,
|
|
822
|
+
type: 'custom',
|
|
823
|
+
codeVar,
|
|
824
|
+
width: 120,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
// 날짜 필드 (xxxDt, xxxDate)
|
|
828
|
+
else if (name.endsWith('Dt') || name.endsWith('Date')) {
|
|
829
|
+
const label = comment || name.replace(/Dt$|Date$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
830
|
+
column = {
|
|
831
|
+
key: name,
|
|
832
|
+
label,
|
|
833
|
+
type: 'date',
|
|
834
|
+
align: 'center',
|
|
835
|
+
width: 100,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
// 날짜시간 필드 (xxxDttm, xxxDateTime)
|
|
839
|
+
else if (name.endsWith('Dttm') || name.endsWith('DateTime')) {
|
|
840
|
+
const label = comment || name.replace(/Dttm$|DateTime$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
841
|
+
column = {
|
|
842
|
+
key: name,
|
|
843
|
+
label,
|
|
844
|
+
type: 'dateTime',
|
|
845
|
+
align: 'center',
|
|
846
|
+
width: 140,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
// 금액 필드 (xxxAmt, xxxPrice, xxxAmt)
|
|
850
|
+
else if (name.endsWith('Amt') || name.endsWith('Price') || name.endsWith('Cost') || name.endsWith('Amt')) {
|
|
851
|
+
const label = comment || name.replace(/Amt$|Price$|Cost$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
852
|
+
column = {
|
|
853
|
+
key: name,
|
|
854
|
+
label,
|
|
855
|
+
type: 'money',
|
|
856
|
+
align: 'right',
|
|
857
|
+
width: 120,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
// Y/N 필드 (xxxYn)
|
|
861
|
+
else if (name.endsWith('Yn')) {
|
|
862
|
+
const label = comment || name.replace(/Yn$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
863
|
+
column = {
|
|
864
|
+
key: name,
|
|
865
|
+
label,
|
|
866
|
+
type: 'selectable',
|
|
867
|
+
align: 'center',
|
|
868
|
+
width: 80,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
// 사용자명 필드 (xxxNm, xxxName, xxxUser)
|
|
872
|
+
else if (name.endsWith('Nm') || name.endsWith('Name') || name.endsWith('User')) {
|
|
873
|
+
const label = comment || name.replace(/Nm$|Name$|User$/, '').replace(/^[a-z]/g, c => c.toUpperCase());
|
|
874
|
+
column = {
|
|
875
|
+
key: name,
|
|
876
|
+
label,
|
|
877
|
+
type: 'user',
|
|
878
|
+
align: 'left',
|
|
879
|
+
width: 100,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
// 이미지 필드 (xxxImg, xxxImage, xxxThumb, xxxPhoto)
|
|
883
|
+
else if (name.includes('img') || name.includes('Img') || name.includes('Image') ||
|
|
884
|
+
name.includes('thumb') || name.includes('Thumb') || name.includes('photo') || name.includes('Photo')) {
|
|
885
|
+
const label = comment || name.replace(/^[a-z]/g, c => c.toUpperCase());
|
|
886
|
+
const imageType = name.includes('thumb') || name.includes('Thumb') ? 'ThumbPreview' : 'ImagePreview';
|
|
887
|
+
column = {
|
|
888
|
+
key: name,
|
|
889
|
+
label,
|
|
890
|
+
type: 'image',
|
|
891
|
+
align: 'center',
|
|
892
|
+
width: 100,
|
|
893
|
+
imageType,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
// 제목 필드 (title, subject, xxxTitle, xxxSubject, bbsTitle)
|
|
897
|
+
else if (name === 'title' || name === 'subject' || name.endsWith('Title') || name.endsWith('Subject') || name === 'bbsTitle') {
|
|
898
|
+
const label = comment || name.replace(/^[a-z]/g, c => c.toUpperCase());
|
|
899
|
+
column = {
|
|
900
|
+
key: name,
|
|
901
|
+
label,
|
|
902
|
+
type: name === 'bbsTitle' ? 'bbsTitle' : 'title',
|
|
903
|
+
align: 'left',
|
|
904
|
+
width: 200,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
// 내용 필드 (content, contents, description) - 넓게
|
|
908
|
+
else if (name === 'content' || name === 'contents' || name === 'description' || name === 'remark') {
|
|
909
|
+
const label = comment || name.replace(/^[a-z]/g, c => c.toUpperCase());
|
|
910
|
+
column = {
|
|
911
|
+
key: name,
|
|
912
|
+
label,
|
|
913
|
+
type: 'custom',
|
|
914
|
+
align: 'left',
|
|
915
|
+
width: 300,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
// 그 외 필드
|
|
919
|
+
else {
|
|
920
|
+
const label = comment || name.replace(/^[a-z]/g, c => c.toUpperCase());
|
|
921
|
+
column = {
|
|
922
|
+
key: name,
|
|
923
|
+
label,
|
|
924
|
+
type: 'custom',
|
|
925
|
+
align: 'left',
|
|
926
|
+
width: 150,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (column) {
|
|
931
|
+
columns.push(column);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 분석 결과 생성
|
|
936
|
+
let analysis = `## DTO: ${interfaceName}\n\n`;
|
|
937
|
+
analysis += `### 필드 분석 결과\n\n`;
|
|
938
|
+
analysis += `- 총 ${fields.length}개 필드 중 ${columns.length}개 컬럼 자동 생성\n\n`;
|
|
939
|
+
|
|
940
|
+
if (detectedCodes.length > 0) {
|
|
941
|
+
analysis += `- ✅ **코드 필드** (${detectedCodes.length}개): ${detectedCodes.join(', ')}\n`;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const dateCount = columns.filter(c => c.type === 'date').length;
|
|
945
|
+
const dateTimeCount = columns.filter(c => c.type === 'dateTime').length;
|
|
946
|
+
const moneyCount = columns.filter(c => c.type === 'money').length;
|
|
947
|
+
const ynCount = columns.filter(c => c.type === 'selectable').length;
|
|
948
|
+
const imageCount = columns.filter(c => c.type === 'image').length;
|
|
949
|
+
|
|
950
|
+
if (dateCount > 0) analysis += `- ✅ **날짜 필드**: ${dateCount}개\n`;
|
|
951
|
+
if (dateTimeCount > 0) analysis += `- ✅ **날짜시간 필드**: ${dateTimeCount}개\n`;
|
|
952
|
+
if (moneyCount > 0) analysis += `- ✅ **금액 필드**: ${moneyCount}개\n`;
|
|
953
|
+
if (ynCount > 0) analysis += `- ✅ **Y/N 필드**: ${ynCount}개\n`;
|
|
954
|
+
if (imageCount > 0) analysis += `- ✅ **이미지 필드**: ${imageCount}개\n`;
|
|
955
|
+
|
|
956
|
+
return {
|
|
957
|
+
config: {
|
|
958
|
+
storeName,
|
|
959
|
+
dtoType: interfaceName,
|
|
960
|
+
rowKey,
|
|
961
|
+
columns,
|
|
962
|
+
},
|
|
963
|
+
analysis,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SearchParams 코드 생성 도구
|
|
6
7
|
* App.tsx에 직접 SearchParams를 추가합니다.
|
|
7
8
|
*/
|
|
8
9
|
export interface GenerateSearchParamsParams {
|
|
9
|
-
/** App.tsx 파일 경로 (절대 경로) -
|
|
10
|
+
/** App.tsx 파일 경로 (절대 경로) - searchDtoPath를 사용하면 자동 감지 */
|
|
10
11
|
appPath?: string;
|
|
11
12
|
/** SearchParams 설정 */
|
|
12
13
|
searchParams?: SearchParamsConfig;
|
|
13
|
-
/** DTO 파일 경로 (절대 경로) - DTO를 분석해서 자동으로 SearchParams 생성 및 App.tsx 수정 */
|
|
14
|
-
|
|
14
|
+
/** 검색용 DTO 파일 경로 (절대 경로) - DTO를 분석해서 자동으로 SearchParams 생성 및 App.tsx 수정 */
|
|
15
|
+
searchDtoPath?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface SearchParamsConfig {
|
|
@@ -100,15 +101,15 @@ interface DtoField {
|
|
|
100
101
|
export async function generateSearchParams(params: GenerateSearchParamsParams): Promise<{
|
|
101
102
|
content: Array<{ type: string; text: string }>;
|
|
102
103
|
}> {
|
|
103
|
-
const {
|
|
104
|
+
const { searchDtoPath, searchParams } = params;
|
|
104
105
|
|
|
105
106
|
// 1. DTO 파싱
|
|
106
107
|
let config: SearchParamsConfig | null = null;
|
|
107
108
|
let dtoAnalysis = '';
|
|
108
109
|
let detectedCodes: string[] = [];
|
|
109
110
|
|
|
110
|
-
if (
|
|
111
|
-
const dtoResult = parseDtoFile(
|
|
111
|
+
if (searchDtoPath) {
|
|
112
|
+
const dtoResult = parseDtoFile(searchDtoPath);
|
|
112
113
|
dtoAnalysis = dtoResult.analysis;
|
|
113
114
|
config = dtoResult.searchParams;
|
|
114
115
|
detectedCodes = dtoResult.detectedCodes;
|
|
@@ -133,12 +134,12 @@ export async function generateSearchParams(params: GenerateSearchParamsParams):
|
|
|
133
134
|
|
|
134
135
|
// 2. App.tsx 경로 결정
|
|
135
136
|
let appPath = params.appPath;
|
|
136
|
-
if (!appPath &&
|
|
137
|
+
if (!appPath && searchDtoPath) {
|
|
137
138
|
// DTO 경로에서 App.tsx 경로 추정
|
|
138
139
|
// 예: /Users/kyle/Desktop/nh-fe-bo/src/services/@interface/dto/BannerSearch.ts
|
|
139
140
|
// → /Users/kyle/Desktop/nh-fe-bo/src/pages/resources/banner/App.tsx
|
|
140
141
|
const projectRoot = '/Users/kyle/Desktop/nh-fe-bo';
|
|
141
|
-
const dtoName = path.basename(
|
|
142
|
+
const dtoName = path.basename(searchDtoPath, '.ts').replace('Search', '');
|
|
142
143
|
const pageName = dtoName.toLowerCase();
|
|
143
144
|
|
|
144
145
|
const possiblePaths = [
|
|
@@ -177,7 +178,7 @@ export async function generateSearchParams(params: GenerateSearchParamsParams):
|
|
|
177
178
|
dtoAnalysis,
|
|
178
179
|
appPath,
|
|
179
180
|
modifications,
|
|
180
|
-
message: generateSuccessMessage(
|
|
181
|
+
message: generateSuccessMessage(searchDtoPath, finalConfig, appPath),
|
|
181
182
|
}),
|
|
182
183
|
}],
|
|
183
184
|
};
|
|
@@ -186,7 +187,7 @@ export async function generateSearchParams(params: GenerateSearchParamsParams):
|
|
|
186
187
|
/**
|
|
187
188
|
* DTO 파일 파싱
|
|
188
189
|
*/
|
|
189
|
-
function parseDtoFile(
|
|
190
|
+
function parseDtoFile(searchDtoPath: string): {
|
|
190
191
|
fields: DtoField[];
|
|
191
192
|
analysis: string;
|
|
192
193
|
searchParams: SearchParamsConfig;
|
|
@@ -194,11 +195,11 @@ function parseDtoFile(dtoPath: string): {
|
|
|
194
195
|
} {
|
|
195
196
|
let content = '';
|
|
196
197
|
try {
|
|
197
|
-
content = fs.readFileSync(
|
|
198
|
+
content = fs.readFileSync(searchDtoPath, 'utf-8');
|
|
198
199
|
} catch (e) {
|
|
199
200
|
return {
|
|
200
201
|
fields: [],
|
|
201
|
-
analysis: `DTO 파일을 읽을 수 없습니다: ${
|
|
202
|
+
analysis: `DTO 파일을 읽을 수 없습니다: ${searchDtoPath}`,
|
|
202
203
|
searchParams: {},
|
|
203
204
|
detectedCodes: [],
|
|
204
205
|
};
|
|
@@ -517,6 +518,16 @@ async function modifyAppTsx(appPath: string, config: SearchParamsConfig): Promis
|
|
|
517
518
|
fs.writeFileSync(appPath, content, 'utf-8');
|
|
518
519
|
console.error(`[MCP] App.tsx 수정 완료: ${appPath}`);
|
|
519
520
|
|
|
521
|
+
// ESLint 실행
|
|
522
|
+
try {
|
|
523
|
+
const appDir = path.dirname(appPath);
|
|
524
|
+
execSync(`npx eslint "${appPath}" --fix`, { cwd: appDir, stdio: 'pipe' });
|
|
525
|
+
console.error(`[MCP] ESLint 실행 완료: ${appPath}`);
|
|
526
|
+
modifications.push('ESLint 실행 완료');
|
|
527
|
+
} catch (e) {
|
|
528
|
+
console.error(`[MCP] ESLint 실행 실패 (무시): ${e}`);
|
|
529
|
+
}
|
|
530
|
+
|
|
520
531
|
return modifications;
|
|
521
532
|
}
|
|
522
533
|
|
|
@@ -671,15 +682,15 @@ function generateSearchParamsJsx(): string {
|
|
|
671
682
|
* 성공 메시지 생성
|
|
672
683
|
*/
|
|
673
684
|
function generateSuccessMessage(
|
|
674
|
-
|
|
685
|
+
searchDtoPath: string | undefined,
|
|
675
686
|
config: SearchParamsConfig,
|
|
676
687
|
appPath: string | undefined
|
|
677
688
|
): string {
|
|
678
689
|
let message = '# ✅ SearchParams 자동 생성 완료\n\n';
|
|
679
690
|
|
|
680
|
-
if (
|
|
691
|
+
if (searchDtoPath) {
|
|
681
692
|
message += `## DTO 파일\n`;
|
|
682
|
-
message += `\`${
|
|
693
|
+
message += `\`${searchDtoPath}\`\n\n`;
|
|
683
694
|
}
|
|
684
695
|
|
|
685
696
|
message += `## App.tsx 수정 완료\n`;
|