import { AutoGenerateConfig, AutoGenerateColumn } from "./AutoGenerateModal"; // ComponentData 타입 (ScreenDesigner에서 사용하는 것과 동일) export interface ComponentData { id: string; componentType: string; position: { x: number; y: number; z: number }; size: { width: number; height: number }; layerId: number; componentConfig: Record; style?: Record; label?: string; tableName?: string; columnName?: string; } // 컴포넌트 ID 생성 function generateComponentId(): string { return `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // 시스템 컬럼 목록 const SYSTEM_COLUMNS = [ "created_by", "updated_by", "created_date", "updated_date", "created_at", "updated_at", "writer", "company_code", ]; // 시스템 컬럼 판별 function isSystemColumn(columnName: string): boolean { return SYSTEM_COLUMNS.includes(columnName.toLowerCase()); } // WebType별 기본 높이 function getFieldHeight(webType: string): number { const heightMap: Record = { textarea: 120, file: 240, image: 240, }; return heightMap[webType] || 40; } /** * 템플릿별 메타 컴포넌트 자동 생성 */ export function generateComponents( tableName: string, tableLabel: string, config: AutoGenerateConfig, columns: AutoGenerateColumn[], startX: number, startY: number, ): ComponentData[] { const components: ComponentData[] = []; // 선택된 컬럼만 필터링 const selectedCols = columns.filter((col) => config.selectedColumns.includes(col.columnName), ); switch (config.templateType) { case "list": return generateListTemplate( tableName, tableLabel, config, selectedCols, startX, startY, ); case "form": return generateFormTemplate( tableName, tableLabel, config, selectedCols, startX, startY, ); case "master-detail": return generateMasterDetailTemplate( tableName, tableLabel, config, selectedCols, startX, startY, ); case "card": return generateCardTemplate( tableName, tableLabel, config, selectedCols, startX, startY, ); default: return components; } } /** * 목록형 템플릿 * Search + Action (등록/삭제) + DataView (table) + Modal (form) */ function generateListTemplate( tableName: string, tableLabel: string, config: AutoGenerateConfig, columns: AutoGenerateColumn[], startX: number, startY: number, ): ComponentData[] { const components: ComponentData[] = []; let currentY = startY; // ID를 미리 생성 (컴포넌트 간 참조를 위해) const dataViewId = generateComponentId(); const createModalId = generateComponentId(); // 1. Search 컴포넌트 (조건: includeSearch) if (config.includeSearch) { components.push({ id: generateComponentId(), componentType: "meta-search", position: { x: startX, y: currentY, z: 1 }, size: { width: 800, height: 60 }, layerId: 1, componentConfig: { mode: "combined", tableName, fields: columns.slice(0, 3).map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), }, style: { labelDisplay: true }, label: "검색", }); currentY += 70; } // 2. Action 그룹 (등록/삭제 버튼) (조건: includeCrud) if (config.includeCrud) { components.push({ id: generateComponentId(), componentType: "meta-action", position: { x: startX, y: currentY, z: 1 }, size: { width: 200, height: 40 }, layerId: 1, componentConfig: { label: "등록", variant: "default", steps: [ { type: "open_modal", targetId: createModalId, }, ], }, style: { labelDisplay: false }, label: "등록 버튼", }); components.push({ id: generateComponentId(), componentType: "meta-action", position: { x: startX + 110, y: currentY, z: 1 }, size: { width: 200, height: 40 }, layerId: 1, componentConfig: { label: "삭제", variant: "destructive", steps: [ { type: "api_call", method: "DELETE", url: `/api/screen-data/${tableName}/{id}`, }, { type: "refresh_component", targetId: dataViewId, }, ], }, style: { labelDisplay: false }, label: "삭제 버튼", }); currentY += 50; } // 3. DataView 컴포넌트 (테이블 뷰) components.push({ id: dataViewId, componentType: "meta-dataview", position: { x: startX, y: currentY, z: 1 }, size: { width: 900, height: 400 }, layerId: 1, componentConfig: { viewMode: "table", tableName, columns: columns.map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), pagination: true, selectable: true, }, style: { labelDisplay: true }, label: tableLabel, }); currentY += 410; // 4. Modal 컴포넌트 (등록/수정 폼) (조건: includeModal) if (config.includeModal) { const formFields = columns.map((col) => ({ id: generateComponentId(), componentType: "meta-field", columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, required: col.required, })); components.push({ id: createModalId, componentType: "meta-modal", position: { x: startX, y: currentY, z: 1 }, size: { width: 600, height: 400 }, layerId: 1, componentConfig: { trigger: "button", title: `${tableLabel} 등록`, formFields, submitAction: { type: "api_call", method: "POST", url: `/api/screen-data/${tableName}`, }, successAction: { type: "refresh_component", targetId: dataViewId, // 위에서 생성한 DataView ID 참조 }, }, style: { labelDisplay: false }, label: "등록 모달", }); } return components; } /** * 폼형 템플릿 * Field 컴포넌트들 (2열 그리드) + Action (저장/취소) */ function generateFormTemplate( tableName: string, tableLabel: string, config: AutoGenerateConfig, columns: AutoGenerateColumn[], startX: number, startY: number, ): ComponentData[] { const components: ComponentData[] = []; let currentY = startY; let currentX = startX; const columnWidth = 350; const gapX = 20; const gapY = 10; // Field 컴포넌트 2열로 배치 columns.forEach((col, index) => { const fieldHeight = getFieldHeight(col.webType); const isLeftColumn = index % 2 === 0; if (!isLeftColumn) { currentX = startX + columnWidth + gapX; } else { currentX = startX; if (index > 0) { currentY += Math.max( getFieldHeight(columns[index - 2]?.webType || "text"), getFieldHeight(columns[index - 1]?.webType || "text"), ) + gapY; } } components.push({ id: generateComponentId(), componentType: "meta-field", position: { x: currentX, y: currentY, z: 1 }, size: { width: columnWidth, height: fieldHeight }, layerId: 1, componentConfig: { columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, required: col.required, tableName, }, style: { labelDisplay: true }, label: col.label || col.columnName, tableName, columnName: col.columnName, }); }); // 마지막 행 높이 계산 const lastRowHeight = columns.length % 2 === 0 ? Math.max( getFieldHeight(columns[columns.length - 2]?.webType || "text"), getFieldHeight(columns[columns.length - 1]?.webType || "text"), ) : getFieldHeight(columns[columns.length - 1]?.webType || "text"); currentY += lastRowHeight + 20; // Action 버튼들 (조건: includeCrud) if (config.includeCrud) { components.push({ id: generateComponentId(), componentType: "meta-action", position: { x: startX, y: currentY, z: 1 }, size: { width: 100, height: 40 }, layerId: 1, componentConfig: { label: "저장", variant: "default", steps: [ { type: "api_call", method: "POST", url: `/api/screen-data/${tableName}`, }, ], }, style: { labelDisplay: false }, label: "저장 버튼", }); components.push({ id: generateComponentId(), componentType: "meta-action", position: { x: startX + 110, y: currentY, z: 1 }, size: { width: 100, height: 40 }, layerId: 1, componentConfig: { label: "취소", variant: "outline", steps: [ { type: "reset_form", }, ], }, style: { labelDisplay: false }, label: "취소 버튼", }); } return components; } /** * 마스터-디테일 템플릿 * 좌측: Search + DataView (table) * 우측: Field 컴포넌트들 + Action (저장) */ function generateMasterDetailTemplate( tableName: string, tableLabel: string, config: AutoGenerateConfig, columns: AutoGenerateColumn[], startX: number, startY: number, ): ComponentData[] { const components: ComponentData[] = []; const masterWidth = 450; const detailX = startX + masterWidth + 30; let masterY = startY; let detailY = startY; // === 좌측 (Master) === // 1. Search if (config.includeSearch) { components.push({ id: generateComponentId(), componentType: "meta-search", position: { x: startX, y: masterY, z: 1 }, size: { width: masterWidth, height: 60 }, layerId: 1, componentConfig: { mode: "simple", tableName, fields: columns.slice(0, 2).map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), }, style: { labelDisplay: true }, label: "검색", }); masterY += 70; } // 2. DataView (Master) const masterDataViewId = generateComponentId(); components.push({ id: masterDataViewId, componentType: "meta-dataview", position: { x: startX, y: masterY, z: 1 }, size: { width: masterWidth, height: 500 }, layerId: 1, componentConfig: { viewMode: "table", tableName, columns: columns.slice(0, 4).map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), pagination: true, selectable: "single", }, style: { labelDisplay: true }, label: tableLabel, }); // === 우측 (Detail) === // Field 컴포넌트들 (단일 열) columns.forEach((col, index) => { const fieldHeight = getFieldHeight(col.webType); components.push({ id: generateComponentId(), componentType: "meta-field", position: { x: detailX, y: detailY, z: 1 }, size: { width: 400, height: fieldHeight }, layerId: 1, componentConfig: { columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, required: col.required, tableName, }, style: { labelDisplay: true }, label: col.label || col.columnName, tableName, columnName: col.columnName, }); detailY += fieldHeight + 10; }); // Action 버튼 (저장) components.push({ id: generateComponentId(), componentType: "meta-action", position: { x: detailX, y: detailY, z: 1 }, size: { width: 100, height: 40 }, layerId: 1, componentConfig: { label: "저장", variant: "default", steps: [ { type: "api_call", method: "PUT", url: `/api/screen-data/${tableName}/{id}`, }, { type: "refresh_component", targetId: masterDataViewId, // 동적 ID 참조 }, ], }, style: { labelDisplay: false }, label: "저장 버튼", }); return components; } /** * 카드형 템플릿 * Search + DataView (card) */ function generateCardTemplate( tableName: string, tableLabel: string, config: AutoGenerateConfig, columns: AutoGenerateColumn[], startX: number, startY: number, ): ComponentData[] { const components: ComponentData[] = []; let currentY = startY; // 1. Search (간단) if (config.includeSearch) { components.push({ id: generateComponentId(), componentType: "meta-search", position: { x: startX, y: currentY, z: 1 }, size: { width: 800, height: 60 }, layerId: 1, componentConfig: { mode: "simple", tableName, fields: columns.slice(0, 2).map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), }, style: { labelDisplay: true }, label: "검색", }); currentY += 70; } // 2. DataView (카드 뷰) components.push({ id: generateComponentId(), componentType: "meta-dataview", position: { x: startX, y: currentY, z: 1 }, size: { width: 900, height: 500 }, layerId: 1, componentConfig: { viewMode: "card", tableName, columns: columns.map((col) => ({ columnName: col.columnName, label: col.label || col.columnName, webType: col.webType, })), pagination: true, }, style: { labelDisplay: true }, label: tableLabel, }); return components; }