@axboot-mcp/mcp-server 1.0.0 → 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.
|
@@ -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 {
|
|
@@ -31,6 +38,14 @@ export interface ListDataGridConfig {
|
|
|
31
38
|
onClickType?: 'modal' | 'link' | 'select' | 'none';
|
|
32
39
|
/** 추가적인 import */
|
|
33
40
|
extraImports?: string[];
|
|
41
|
+
/** 행 높이 */
|
|
42
|
+
itemHeight?: number;
|
|
43
|
+
/** 행별 스타일 (조건 필드명) */
|
|
44
|
+
rowClassNameField?: string;
|
|
45
|
+
/** 삭제 버튼 포함 */
|
|
46
|
+
withDelete?: boolean;
|
|
47
|
+
/** 복사 버튼 포함 */
|
|
48
|
+
withCopy?: boolean;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
export interface ColumnConfig {
|
|
@@ -39,7 +54,7 @@ export interface ColumnConfig {
|
|
|
39
54
|
/** 라벨 */
|
|
40
55
|
label: string;
|
|
41
56
|
/** 컬럼 타입 */
|
|
42
|
-
type?: 'rowNo' | 'title' | 'money' | 'date' | 'dateTime' | 'user' | 'selectable' | 'custom';
|
|
57
|
+
type?: 'rowNo' | 'title' | 'money' | 'date' | 'dateTime' | 'user' | 'selectable' | 'image' | 'bbsTitle' | 'custom';
|
|
43
58
|
/** 정렬 */
|
|
44
59
|
align?: 'left' | 'center' | 'right';
|
|
45
60
|
/** 너비 */
|
|
@@ -48,6 +63,10 @@ export interface ColumnConfig {
|
|
|
48
63
|
itemRender?: string;
|
|
49
64
|
/** 코드 변수명 (코드 변환시 사용) */
|
|
50
65
|
codeVar?: string;
|
|
66
|
+
/** 이미지 타입 (ThumbPreview 또는 ImagePreview) */
|
|
67
|
+
imageType?: 'ThumbPreview' | 'ImagePreview';
|
|
68
|
+
/** className */
|
|
69
|
+
className?: string;
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
/**
|
|
@@ -56,10 +75,53 @@ export interface ColumnConfig {
|
|
|
56
75
|
export async function generateListDataGrid(params: GenerateListDataGridParams): Promise<{
|
|
57
76
|
content: Array<{ type: string; text: string }>;
|
|
58
77
|
}> {
|
|
59
|
-
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
|
+
}
|
|
60
122
|
|
|
61
123
|
// 코드 생성
|
|
62
|
-
const code = generateListDataGridCode(
|
|
124
|
+
const code = generateListDataGridCode(finalConfig);
|
|
63
125
|
|
|
64
126
|
// 파일에 쓰기
|
|
65
127
|
const dir = path.dirname(outputPath);
|
|
@@ -67,6 +129,15 @@ export async function generateListDataGrid(params: GenerateListDataGridParams):
|
|
|
67
129
|
fs.mkdirSync(dir, { recursive: true });
|
|
68
130
|
}
|
|
69
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
|
+
}
|
|
70
141
|
|
|
71
142
|
return {
|
|
72
143
|
content: [
|
|
@@ -76,6 +147,7 @@ export async function generateListDataGrid(params: GenerateListDataGridParams):
|
|
|
76
147
|
success: true,
|
|
77
148
|
outputPath,
|
|
78
149
|
message: `ListDataGrid 파일이 생성되었습니다: ${outputPath}`,
|
|
150
|
+
dtoAnalysis: responseDtoPath ? parseDtoForListDataGrid(responseDtoPath, storeName!, rowKey!).analysis : undefined,
|
|
79
151
|
nextSteps: `
|
|
80
152
|
## 다음 단계
|
|
81
153
|
|
|
@@ -105,19 +177,19 @@ import { ListDataGrid } from "./ListDataGrid";
|
|
|
105
177
|
\`\`\`typescript
|
|
106
178
|
const { columns } = useDataGridColumns<DtoItem>(
|
|
107
179
|
[
|
|
108
|
-
{ key: "${
|
|
180
|
+
{ key: "${finalConfig.rowKey}", label: t("번호"), type: "rowNo" },
|
|
109
181
|
// TODO: 여기에 컬럼 추가
|
|
110
|
-
${
|
|
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 ')}
|
|
111
183
|
],
|
|
112
184
|
{
|
|
113
185
|
colWidths: listColWidths,
|
|
114
|
-
${
|
|
186
|
+
${finalConfig.columns.some(c => c.codeVar) ? `deps: [${finalConfig.columns.filter(c => c.codeVar).map(c => c.codeVar).join(', ')}],` : ''}
|
|
115
187
|
}
|
|
116
188
|
);
|
|
117
189
|
\`\`\`
|
|
118
190
|
|
|
119
191
|
### 4. onClickItem 핸들러 구현
|
|
120
|
-
onClickType이 "${
|
|
192
|
+
onClickType이 "${finalConfig.onClickType || 'select'}"이므로 필요한 동작을 구현하세요.
|
|
121
193
|
`.trim(),
|
|
122
194
|
}),
|
|
123
195
|
},
|
|
@@ -139,6 +211,10 @@ function generateListDataGridCode(config: ListDataGridConfig): string {
|
|
|
139
211
|
columns,
|
|
140
212
|
onClickType = 'select',
|
|
141
213
|
extraImports = [],
|
|
214
|
+
itemHeight,
|
|
215
|
+
rowClassNameField,
|
|
216
|
+
withDelete = false,
|
|
217
|
+
withCopy = false,
|
|
142
218
|
} = config;
|
|
143
219
|
|
|
144
220
|
// Import 생성
|
|
@@ -215,15 +291,21 @@ function generateImports(config: ListDataGridConfig, extraImports: string[] = []
|
|
|
215
291
|
];
|
|
216
292
|
|
|
217
293
|
if (config.withFormHeader) {
|
|
218
|
-
imports.push(`import { Button, Flex } from "antd";`);
|
|
294
|
+
imports.push(`import { Button, Flex, Space, Divider } from "antd";`);
|
|
219
295
|
imports.push(`import { IconDownload } from "components/icon";`);
|
|
220
296
|
}
|
|
221
297
|
|
|
222
298
|
imports.push(`import styled from "@emotion/styled";`);
|
|
223
299
|
imports.push(`import { useI18n } from "hooks";`);
|
|
224
|
-
imports.push(`import React from "react";`);
|
|
300
|
+
imports.push(`import React, { useCallback, useState } from "react";`);
|
|
225
301
|
imports.push(`import { PageLayout } from "styles/pageStyled";`);
|
|
226
302
|
imports.push(`import { errorHandling } from "utils";`);
|
|
303
|
+
|
|
304
|
+
// 모달이나 삭제/복사 기능이 있을 때
|
|
305
|
+
if (config.withFormHeader || config.onClickType === 'modal' || config.withDelete || config.withCopy) {
|
|
306
|
+
imports.push(`import { alertDialog, confirmDialog } from "@core/components/dialogs";`);
|
|
307
|
+
}
|
|
308
|
+
|
|
227
309
|
imports.push(`import { ${config.dtoType} } from "services";`);
|
|
228
310
|
|
|
229
311
|
// Store import
|
|
@@ -231,12 +313,31 @@ function generateImports(config: ListDataGridConfig, extraImports: string[] = []
|
|
|
231
313
|
imports.push(`import { ${config.storeName} } from "./${storeFileName}";`);
|
|
232
314
|
|
|
233
315
|
// 컬럼 타입별 필요한 import
|
|
234
|
-
|
|
235
|
-
|
|
316
|
+
const hasMoney = config.columns.some(c => c.type === 'money');
|
|
317
|
+
const hasDate = config.columns.some(c => c.type === 'date' || c.type === 'dateTime');
|
|
318
|
+
const hasImage = config.columns.some(c => c.type === 'image');
|
|
319
|
+
const hasCode = config.columns.some(c => c.codeVar);
|
|
320
|
+
|
|
321
|
+
if (hasMoney || hasDate) {
|
|
322
|
+
imports.push(`import { formatterDate, formatterNumber } from "../../../../@core/utils";`);
|
|
323
|
+
imports.push(`import { DT_FORMAT } from "../../../../@types";`);
|
|
236
324
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
325
|
+
|
|
326
|
+
if (hasImage) {
|
|
327
|
+
const hasThumbPreview = config.columns.some(c => c.imageType === 'ThumbPreview');
|
|
328
|
+
const hasImagePreview = config.columns.some(c => c.imageType === 'ImagePreview');
|
|
329
|
+
|
|
330
|
+
if (hasThumbPreview && hasImagePreview) {
|
|
331
|
+
imports.push(`import { ImagePreview, ThumbPreview } from "../../../../components/common";`);
|
|
332
|
+
} else if (hasThumbPreview) {
|
|
333
|
+
imports.push(`import { ThumbPreview } from "../../../../components/common/ThumbPreview";`);
|
|
334
|
+
} else if (hasImagePreview) {
|
|
335
|
+
imports.push(`import { ImagePreview } from "../../../../components/common";`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (hasCode) {
|
|
340
|
+
imports.push(`import { useCodeStore } from "stores";`);
|
|
240
341
|
}
|
|
241
342
|
|
|
242
343
|
// 추가 imports
|
|
@@ -313,15 +414,29 @@ function generateStoreConnections(config: ListDataGridConfig): string {
|
|
|
313
414
|
* 컬럼 정의 생성
|
|
314
415
|
*/
|
|
315
416
|
function generateColumnsDefinition(config: ListDataGridConfig): string {
|
|
316
|
-
const { storeName, columns, rowKey } = config;
|
|
417
|
+
const { storeName, columns, rowKey, withDelete, withCopy } = config;
|
|
317
418
|
|
|
318
419
|
// 컬럼 배열 생성
|
|
319
420
|
const columnItems = columns.map(col => {
|
|
320
|
-
const { key, label, type, align, width, itemRender, codeVar } = col;
|
|
421
|
+
const { key, label, type, align, width, itemRender, codeVar, imageType, className } = col;
|
|
321
422
|
|
|
322
423
|
// itemRender가 있는 경우
|
|
323
424
|
if (itemRender) {
|
|
324
|
-
return ` { key: "${key}", label: t("${label}")${width ? `, width: ${width}` : ''}${align ? `, align: "${align}"` : ''}, itemRender: ${itemRender} }`;
|
|
425
|
+
return ` { key: "${key}", label: t("${label}")${width ? `, width: ${width}` : ''}${align ? `, align: "${align}"` : ''}${className ? `, className: "${className}"` : ''}, itemRender: ${itemRender} }`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 이미지 타입
|
|
429
|
+
if (type === 'image') {
|
|
430
|
+
if (imageType === 'ThumbPreview') {
|
|
431
|
+
return ` { key: "${key}", label: t("${label}"), align: "center"${width ? `, width: ${width}` : ', width: 100'}, itemRender: ({ value }) => <ThumbPreview url={value?.prevewUrlCtnts} /> }`;
|
|
432
|
+
} else if (imageType === 'ImagePreview') {
|
|
433
|
+
return ` { key: "${key}", label: t("${label}"), align: "right"${width ? `, width: ${width}` : ', width: 80'}${className ? `, className: "${className}"` : ''}, itemRender: (item) => <ImagePreview files={item.values.${key}List} height={60} swap rotate zoom thumbList text /> }`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// bbsTitle 타입
|
|
438
|
+
if (type === 'bbsTitle') {
|
|
439
|
+
return ` { key: "${key}", label: t("${label}"), type: "bbsTitle"${width ? `, width: ${width}` : ''} }`;
|
|
325
440
|
}
|
|
326
441
|
|
|
327
442
|
// 코드 변환이 필요한 경우
|
|
@@ -340,6 +455,22 @@ function generateColumnsDefinition(config: ListDataGridConfig): string {
|
|
|
340
455
|
|
|
341
456
|
const hasCodeVar = columns.some(c => c.codeVar);
|
|
342
457
|
|
|
458
|
+
// 관리 버튼 컬럼
|
|
459
|
+
if (withDelete || withCopy) {
|
|
460
|
+
const actionButtons: string[] = [];
|
|
461
|
+
if (withCopy) actionButtons.push('handleCopy');
|
|
462
|
+
if (withDelete) actionButtons.push('handleDelete');
|
|
463
|
+
|
|
464
|
+
const actionColumn = ` { key: "", label: t("관리"), align: "center", width: ${actionButtons.length * 80 + 40}, itemRender: (item) => (
|
|
465
|
+
<Space size={8}>
|
|
466
|
+
${withCopy ? `<Button size={"small"} variant={"outlined"} color={"primary"} onClick={() => handleCopy(item.values.${rowKey})}>{t("복사")}</Button>` : ''}
|
|
467
|
+
${withDelete ? `<Button size={"small"} variant={"outlined"} color={"danger"} onClick={() => handleDelete(item.values.${rowKey})}>{t("삭제")}</Button>` : ''}
|
|
468
|
+
</Space>
|
|
469
|
+
) }`;
|
|
470
|
+
|
|
471
|
+
columnItems.push(actionColumn);
|
|
472
|
+
}
|
|
473
|
+
|
|
343
474
|
return ` const handleColumnsChange = React.useCallback(
|
|
344
475
|
(columnIndex: number | null, { columns }: AXDGChangeColumnsInfo<DtoItem>) => {
|
|
345
476
|
setListColWidths(columns.map((column) => column.width));
|
|
@@ -347,7 +478,7 @@ function generateColumnsDefinition(config: ListDataGridConfig): string {
|
|
|
347
478
|
[setListColWidths],
|
|
348
479
|
);
|
|
349
480
|
|
|
350
|
-
const { columns } = useDataGridColumns<DtoItem>(
|
|
481
|
+
const ${hasCodeVar ? '// 코드 의존성\n ' : ''}{ columns } = useDataGridColumns<DtoItem>(
|
|
351
482
|
[
|
|
352
483
|
{ key: "${rowKey}", label: t("번호"), type: "rowNo" },
|
|
353
484
|
${columnItems.join(',\n')}
|
|
@@ -362,7 +493,7 @@ ${columnItems.join(',\n')}
|
|
|
362
493
|
* 이벤트 핸들러 생성
|
|
363
494
|
*/
|
|
364
495
|
function generateEventHandlers(config: ListDataGridConfig): string {
|
|
365
|
-
const { storeName, onClickType, withExcel } = config;
|
|
496
|
+
const { storeName, onClickType, withExcel, withDelete, withCopy, rowKey } = config;
|
|
366
497
|
const handlers: string[] = [];
|
|
367
498
|
|
|
368
499
|
// 엑셀 다운로드 핸들러
|
|
@@ -379,6 +510,52 @@ function generateEventHandlers(config: ListDataGridConfig): string {
|
|
|
379
510
|
);
|
|
380
511
|
}
|
|
381
512
|
|
|
513
|
+
// 삭제 핸들러
|
|
514
|
+
if (withDelete) {
|
|
515
|
+
handlers.push(
|
|
516
|
+
` const handleDelete = useCallback(
|
|
517
|
+
async (id: string) => {
|
|
518
|
+
try {
|
|
519
|
+
await confirmDialog({
|
|
520
|
+
content: "정말 삭제하시겠습니까?",
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// TODO: Service.delete 호출
|
|
524
|
+
// await Service.delete({ ${rowKey}: id });
|
|
525
|
+
messageApi.info("삭제되었습니다.");
|
|
526
|
+
await callListApi({ pageNumber: 1 });
|
|
527
|
+
} catch (err) {
|
|
528
|
+
await errorHandling(err);
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
[callListApi, messageApi]
|
|
532
|
+
);`,
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// 복사 핸들러
|
|
537
|
+
if (withCopy) {
|
|
538
|
+
handlers.push(
|
|
539
|
+
` const handleCopy = useCallback(
|
|
540
|
+
async (id: string) => {
|
|
541
|
+
try {
|
|
542
|
+
await confirmDialog({
|
|
543
|
+
content: "해당 정보를 복사하시겠습니까?",
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// TODO: Service.copy 호출
|
|
547
|
+
// const result = await Service.copy({ ${rowKey}: id });
|
|
548
|
+
messageApi.success("복사되었습니다.");
|
|
549
|
+
await callListApi();
|
|
550
|
+
} catch (err) {
|
|
551
|
+
await errorHandling(err);
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
[callListApi, messageApi]
|
|
555
|
+
);`,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
382
559
|
// onClickItem 핸들러
|
|
383
560
|
if (onClickType === 'modal') {
|
|
384
561
|
handlers.push(
|
|
@@ -407,7 +584,7 @@ function generateEventHandlers(config: ListDataGridConfig): string {
|
|
|
407
584
|
try {
|
|
408
585
|
// TODO: 링크 이동
|
|
409
586
|
// const menu = MENUS_LIST.find((m) => m.progId === "TARGET_PAGE");
|
|
410
|
-
// if (menu) linkByMenu(menu, {
|
|
587
|
+
// if (menu) linkByMenu(menu, ${rowKey}: params.item.${rowKey} });
|
|
411
588
|
} catch (err) {
|
|
412
589
|
await errorHandling(err);
|
|
413
590
|
}
|
|
@@ -469,7 +646,7 @@ function generateFormHeader(config: ListDataGridConfig): string {
|
|
|
469
646
|
* DataGrid Props 생성
|
|
470
647
|
*/
|
|
471
648
|
function generateDataGridProps(config: ListDataGridConfig): string {
|
|
472
|
-
const { selectionMode, rowKey } = config;
|
|
649
|
+
const { selectionMode, rowKey, itemHeight, rowClassNameField } = config;
|
|
473
650
|
const props: string[] = [
|
|
474
651
|
` onClick={onClickItem}`,
|
|
475
652
|
` page={{`,
|
|
@@ -501,6 +678,18 @@ function generateDataGridProps(config: ListDataGridConfig): string {
|
|
|
501
678
|
);
|
|
502
679
|
}
|
|
503
680
|
|
|
681
|
+
// 행 높이
|
|
682
|
+
if (itemHeight) {
|
|
683
|
+
props.push(` itemHeight={${itemHeight}}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// 행별 스타일
|
|
687
|
+
if (rowClassNameField) {
|
|
688
|
+
props.push(` getRowClassName={(ri, item) => {`);
|
|
689
|
+
props.push(` return item.values.${rowClassNameField} === "Y" ? "highlight-row" : "";`);
|
|
690
|
+
props.push(` }}`);
|
|
691
|
+
}
|
|
692
|
+
|
|
504
693
|
return props.join('\n');
|
|
505
694
|
}
|
|
506
695
|
|
|
@@ -512,6 +701,17 @@ function generateStyledComponents(config: ListDataGridConfig): string {
|
|
|
512
701
|
flex: 1;
|
|
513
702
|
\`;`;
|
|
514
703
|
|
|
704
|
+
if (config.rowClassNameField) {
|
|
705
|
+
styled = `const Container = styled.div\`
|
|
706
|
+
flex: 1;
|
|
707
|
+
|
|
708
|
+
.highlight-row {
|
|
709
|
+
background-color: #fffbe6;
|
|
710
|
+
color: var(--text-display-color);
|
|
711
|
+
}
|
|
712
|
+
\`;`;
|
|
713
|
+
}
|
|
714
|
+
|
|
515
715
|
if (config.withFormHeader) {
|
|
516
716
|
styled += `
|
|
517
717
|
const FormHeader = styled(PageLayout.FrameHeader)\`\`;`;
|
|
@@ -519,3 +719,247 @@ const FormHeader = styled(PageLayout.FrameHeader)\`\`;`;
|
|
|
519
719
|
|
|
520
720
|
return styled;
|
|
521
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
|
+
}
|