/** * 다국어 라벨 추출 유틸리티 * 화면 디자이너의 컴포넌트에서 다국어 처리가 필요한 라벨을 추출합니다. */ import { ComponentData } from "@/types/screen"; // 추출된 라벨 타입 export interface ExtractedLabel { id: string; componentId: string; label: string; type: "label" | "title" | "button" | "placeholder" | "column" | "filter" | "field" | "tab" | "action"; parentType?: string; parentLabel?: string; langKeyId?: number; langKey?: string; } // 입력 폼 컴포넌트인지 확인 const INPUT_COMPONENT_TYPES = new Set([ "text-field", "number-field", "date-field", "datetime-field", "select-field", "checkbox-field", "radio-field", "textarea-field", "file-field", "email-field", "tel-field", "password-field", "entity-field", "code-field", "category-field", "input-field", "widget", ]); const isInputComponent = (comp: any): boolean => { const compType = comp.componentType || comp.type; if (INPUT_COMPONENT_TYPES.has(compType)) return true; if (compType === "widget" && comp.widgetType) return true; if (comp.inputType || comp.webType) return true; return false; }; // 컬럼 라벨 맵 타입 export type ColumnLabelMap = Record>; /** * 컴포넌트에서 다국어 라벨 추출 * @param components 컴포넌트 배열 * @param columnLabelMap 테이블별 컬럼 라벨 맵 (선택사항) * @returns 추출된 라벨 배열 */ export function extractMultilangLabels( components: ComponentData[], columnLabelMap: ColumnLabelMap = {} ): ExtractedLabel[] { const labels: ExtractedLabel[] = []; const addedLabels = new Set(); const addLabel = ( componentId: string, label: string, type: ExtractedLabel["type"], parentType?: string, parentLabel?: string, langKeyId?: number, langKey?: string ) => { const key = `${componentId}_${type}_${label}`; if (label && label.trim() && !addedLabels.has(key)) { addedLabels.add(key); labels.push({ id: key, componentId, label: label.trim(), type, parentType, parentLabel, langKeyId, langKey, }); } }; const extractFromComponent = (comp: ComponentData, parentType?: string, parentLabel?: string) => { const anyComp = comp as any; const config = anyComp.componentConfig; const compType = anyComp.componentType || anyComp.type; const compLabel = anyComp.label || anyComp.title || compType; // 1. 기본 라벨 - 입력 폼 컴포넌트인 경우에만 추출 if (isInputComponent(anyComp)) { if (anyComp.label && typeof anyComp.label === "string") { addLabel(comp.id, anyComp.label, "label", parentType, parentLabel, anyComp.langKeyId, anyComp.langKey); } } // 2. 제목 if (anyComp.title && typeof anyComp.title === "string") { addLabel(comp.id, anyComp.title, "title", parentType, parentLabel); } // 3. 버튼 텍스트 (componentId에 _button 접미사 추가하여 라벨과 구분) if (config?.text && typeof config.text === "string") { addLabel( `${comp.id}_button`, config.text, "button", parentType, parentLabel, config.langKeyId, config.langKey ); } // 4. placeholder if (anyComp.placeholder && typeof anyComp.placeholder === "string") { addLabel(comp.id, anyComp.placeholder, "placeholder", parentType, parentLabel); } // 5. 테이블 컬럼 - columnLabelMap에서 한글 라벨 조회 const tableName = config?.selectedTable || config?.tableName || config?.table || anyComp.tableName; if (config?.columns && Array.isArray(config.columns)) { config.columns.forEach((col: any, index: number) => { const colName = col.columnName || col.field || col.name; // columnLabelMap에서 한글 라벨 조회, 없으면 displayName 사용 const colLabel = columnLabelMap[tableName]?.[colName] || col.displayName || col.label || colName; if (colLabel && typeof colLabel === "string") { addLabel( `${comp.id}_col_${index}`, colLabel, "column", compType, compLabel, col.langKeyId, col.langKey ); } }); } // 6. 분할 패널 제목 및 컬럼 - columnLabelMap에서 한글 라벨 조회 // 6-1. 좌측 패널 제목 if (config?.leftPanel?.title && typeof config.leftPanel.title === "string") { addLabel( `${comp.id}_left_title`, config.leftPanel.title, "title", compType, `${compLabel} (좌측)`, config.leftPanel.langKeyId, config.leftPanel.langKey ); } // 6-2. 우측 패널 제목 if (config?.rightPanel?.title && typeof config.rightPanel.title === "string") { addLabel( `${comp.id}_right_title`, config.rightPanel.title, "title", compType, `${compLabel} (우측)`, config.rightPanel.langKeyId, config.rightPanel.langKey ); } // 6-3. 좌측 패널 컬럼 const leftTableName = config?.leftPanel?.selectedTable || config?.leftPanel?.tableName || tableName; if (config?.leftPanel?.columns && Array.isArray(config.leftPanel.columns)) { config.leftPanel.columns.forEach((col: any, index: number) => { const colName = col.columnName || col.field || col.name; const colLabel = columnLabelMap[leftTableName]?.[colName] || col.displayName || col.label || colName; if (colLabel && typeof colLabel === "string") { addLabel( `${comp.id}_left_col_${index}`, colLabel, "column", compType, `${compLabel} (좌측)`, col.langKeyId, col.langKey ); } }); } const rightTableName = config?.rightPanel?.selectedTable || config?.rightPanel?.tableName || tableName; if (config?.rightPanel?.columns && Array.isArray(config.rightPanel.columns)) { config.rightPanel.columns.forEach((col: any, index: number) => { const colName = col.columnName || col.field || col.name; const colLabel = columnLabelMap[rightTableName]?.[colName] || col.displayName || col.label || colName; if (colLabel && typeof colLabel === "string") { addLabel( `${comp.id}_right_col_${index}`, colLabel, "column", compType, `${compLabel} (우측)`, col.langKeyId, col.langKey ); } }); } // 6-5. 추가 탭 (additionalTabs) 제목 및 컬럼 - rightPanel.additionalTabs 확인 const additionalTabs = config?.rightPanel?.additionalTabs || config?.additionalTabs; if (additionalTabs && Array.isArray(additionalTabs)) { additionalTabs.forEach((tab: any, tabIndex: number) => { // 탭 라벨 if (tab.label && typeof tab.label === "string") { addLabel( `${comp.id}_addtab_${tabIndex}_label`, tab.label, "tab", compType, compLabel, tab.langKeyId, tab.langKey ); } // 탭 제목 if (tab.title && typeof tab.title === "string") { addLabel( `${comp.id}_addtab_${tabIndex}_title`, tab.title, "title", compType, `${compLabel} (탭: ${tab.label || tabIndex})`, tab.titleLangKeyId, tab.titleLangKey ); } // 탭 컬럼 const tabTableName = tab.tableName || tab.selectedTable || rightTableName; if (tab.columns && Array.isArray(tab.columns)) { tab.columns.forEach((col: any, colIndex: number) => { const colName = col.columnName || col.field || col.name; const colLabel = columnLabelMap[tabTableName]?.[colName] || col.displayName || col.label || colName; if (colLabel && typeof colLabel === "string") { addLabel( `${comp.id}_addtab_${tabIndex}_col_${colIndex}`, colLabel, "column", compType, `${compLabel} (탭: ${tab.label || tabIndex})`, col.langKeyId, col.langKey ); } }); } }); } // 7. 검색 필터 if (config?.filter?.filters && Array.isArray(config.filter.filters)) { config.filter.filters.forEach((filter: any, index: number) => { if (filter.label && typeof filter.label === "string") { addLabel( `${comp.id}_filter_${index}`, filter.label, "filter", compType, compLabel, filter.langKeyId, filter.langKey ); } }); } // 8. 폼 필드 if (config?.fields && Array.isArray(config.fields)) { config.fields.forEach((field: any, index: number) => { if (field.label && typeof field.label === "string") { addLabel( `${comp.id}_field_${index}`, field.label, "field", compType, compLabel, field.langKeyId, field.langKey ); } }); } // 9. 탭 if (config?.tabs && Array.isArray(config.tabs)) { config.tabs.forEach((tab: any, index: number) => { if (tab.label && typeof tab.label === "string") { addLabel( `${comp.id}_tab_${index}`, tab.label, "tab", compType, compLabel, tab.langKeyId, tab.langKey ); } }); } // 10. 액션 버튼 if (config?.actions?.actions && Array.isArray(config.actions.actions)) { config.actions.actions.forEach((action: any, index: number) => { if (action.label && typeof action.label === "string") { addLabel( `${comp.id}_action_${index}`, action.label, "action", compType, compLabel, action.langKeyId, action.langKey ); } }); } // 자식 컴포넌트 재귀 탐색 if (anyComp.children && Array.isArray(anyComp.children)) { anyComp.children.forEach((child: ComponentData) => { extractFromComponent(child, compType, compLabel); }); } }; components.forEach((comp) => extractFromComponent(comp)); return labels; } /** * 컴포넌트에서 테이블명 추출 * @param components 컴포넌트 배열 * @returns 테이블명 Set */ export function extractTableNames(components: ComponentData[]): Set { const tableNames = new Set(); const extractTableName = (comp: any) => { const config = comp.componentConfig; // 1. 컴포넌트 직접 tableName if (comp.tableName) tableNames.add(comp.tableName); // 2. componentConfig 직접 tableName if (config?.tableName) tableNames.add(config.tableName); // 3. 테이블 리스트 컴포넌트 - selectedTable (주요!) if (config?.selectedTable) tableNames.add(config.selectedTable); // 4. 테이블 리스트 컴포넌트 - table 속성 if (config?.table) tableNames.add(config.table); // 5. 분할 패널의 leftPanel/rightPanel if (config?.leftPanel?.tableName) tableNames.add(config.leftPanel.tableName); if (config?.rightPanel?.tableName) tableNames.add(config.rightPanel.tableName); if (config?.leftPanel?.table) tableNames.add(config.leftPanel.table); if (config?.rightPanel?.table) tableNames.add(config.rightPanel.table); if (config?.leftPanel?.selectedTable) tableNames.add(config.leftPanel.selectedTable); if (config?.rightPanel?.selectedTable) tableNames.add(config.rightPanel.selectedTable); // 6. 검색 필터의 tableName if (config?.filter?.tableName) tableNames.add(config.filter.tableName); // 7. properties 안의 tableName if (comp.properties?.tableName) tableNames.add(comp.properties.tableName); if (comp.properties?.selectedTable) tableNames.add(comp.properties.selectedTable); // 자식 컴포넌트 탐색 if (comp.children && Array.isArray(comp.children)) { comp.children.forEach(extractTableName); } }; components.forEach(extractTableName); return tableNames; } /** * 다국어 키 매핑 결과를 컴포넌트에 적용 * @param components 원본 컴포넌트 배열 * @param mappings 다국어 키 매핑 결과 [{componentId, keyId, langKey}] * @returns 업데이트된 컴포넌트 배열 */ export function applyMultilangMappings( components: ComponentData[], mappings: Array<{ componentId: string; keyId: number; langKey: string }> ): ComponentData[] { // 매핑을 빠르게 찾기 위한 맵 생성 const mappingMap = new Map(mappings.map((m) => [m.componentId, m])); const updateComponent = (comp: ComponentData): ComponentData => { const anyComp = comp as any; const config = anyComp.componentConfig; let updated = { ...comp } as any; // 기본 컴포넌트 라벨 매핑 확인 const labelMapping = mappingMap.get(comp.id); if (labelMapping) { updated.langKeyId = labelMapping.keyId; updated.langKey = labelMapping.langKey; } // 버튼 텍스트 매핑 (componentId_button 형식으로 조회) const buttonMapping = mappingMap.get(`${comp.id}_button`); if (buttonMapping && config?.text) { updated.componentConfig = { ...updated.componentConfig, langKeyId: buttonMapping.keyId, langKey: buttonMapping.langKey, }; } // 컬럼 매핑 if (config?.columns && Array.isArray(config.columns)) { const updatedColumns = config.columns.map((col: any, index: number) => { const colMapping = mappingMap.get(`${comp.id}_col_${index}`); if (colMapping) { return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey }; } return col; }); updated.componentConfig = { ...config, columns: updatedColumns }; } // 분할 패널 좌측 제목 매핑 const leftTitleMapping = mappingMap.get(`${comp.id}_left_title`); if (leftTitleMapping && config?.leftPanel?.title) { updated.componentConfig = { ...updated.componentConfig, leftPanel: { ...updated.componentConfig?.leftPanel, langKeyId: leftTitleMapping.keyId, langKey: leftTitleMapping.langKey, }, }; } // 분할 패널 우측 제목 매핑 const rightTitleMapping = mappingMap.get(`${comp.id}_right_title`); if (rightTitleMapping && config?.rightPanel?.title) { updated.componentConfig = { ...updated.componentConfig, rightPanel: { ...updated.componentConfig?.rightPanel, langKeyId: rightTitleMapping.keyId, langKey: rightTitleMapping.langKey, }, }; } // 분할 패널 좌측 컬럼 매핑 (이미 업데이트된 leftPanel 사용) const currentLeftPanel = updated.componentConfig?.leftPanel || config?.leftPanel; if (currentLeftPanel?.columns && Array.isArray(currentLeftPanel.columns)) { const updatedLeftColumns = currentLeftPanel.columns.map((col: any, index: number) => { const colMapping = mappingMap.get(`${comp.id}_left_col_${index}`); if (colMapping) { return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey }; } return col; }); updated.componentConfig = { ...updated.componentConfig, leftPanel: { ...currentLeftPanel, columns: updatedLeftColumns }, }; } // 분할 패널 우측 컬럼 매핑 (이미 업데이트된 rightPanel 사용) const currentRightPanel = updated.componentConfig?.rightPanel || config?.rightPanel; if (currentRightPanel?.columns && Array.isArray(currentRightPanel.columns)) { const updatedRightColumns = currentRightPanel.columns.map((col: any, index: number) => { const colMapping = mappingMap.get(`${comp.id}_right_col_${index}`); if (colMapping) { return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey }; } return col; }); updated.componentConfig = { ...updated.componentConfig, rightPanel: { ...currentRightPanel, columns: updatedRightColumns }, }; } // 필터 매핑 if (config?.filter?.filters && Array.isArray(config.filter.filters)) { const updatedFilters = config.filter.filters.map((filter: any, index: number) => { const filterMapping = mappingMap.get(`${comp.id}_filter_${index}`); if (filterMapping) { return { ...filter, langKeyId: filterMapping.keyId, langKey: filterMapping.langKey }; } return filter; }); updated.componentConfig = { ...updated.componentConfig, filter: { ...config.filter, filters: updatedFilters }, }; } // 폼 필드 매핑 if (config?.fields && Array.isArray(config.fields)) { const updatedFields = config.fields.map((field: any, index: number) => { const fieldMapping = mappingMap.get(`${comp.id}_field_${index}`); if (fieldMapping) { return { ...field, langKeyId: fieldMapping.keyId, langKey: fieldMapping.langKey }; } return field; }); updated.componentConfig = { ...updated.componentConfig, fields: updatedFields }; } // 탭 매핑 if (config?.tabs && Array.isArray(config.tabs)) { const updatedTabs = config.tabs.map((tab: any, index: number) => { const tabMapping = mappingMap.get(`${comp.id}_tab_${index}`); if (tabMapping) { return { ...tab, langKeyId: tabMapping.keyId, langKey: tabMapping.langKey }; } return tab; }); updated.componentConfig = { ...updated.componentConfig, tabs: updatedTabs }; } // 추가 탭 (additionalTabs) 매핑 - rightPanel.additionalTabs 또는 additionalTabs 확인 const currentRightPanelForAddTabs = updated.componentConfig?.rightPanel || config?.rightPanel; const configAdditionalTabs = currentRightPanelForAddTabs?.additionalTabs || config?.additionalTabs; if (configAdditionalTabs && Array.isArray(configAdditionalTabs)) { const updatedAdditionalTabs = configAdditionalTabs.map((tab: any, tabIndex: number) => { let updatedTab = { ...tab }; // 탭 라벨 매핑 const labelMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_label`); if (labelMapping) { updatedTab.langKeyId = labelMapping.keyId; updatedTab.langKey = labelMapping.langKey; } // 탭 제목 매핑 const titleMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_title`); if (titleMapping) { updatedTab.titleLangKeyId = titleMapping.keyId; updatedTab.titleLangKey = titleMapping.langKey; } // 탭 컬럼 매핑 if (tab.columns && Array.isArray(tab.columns)) { updatedTab.columns = tab.columns.map((col: any, colIndex: number) => { const colMapping = mappingMap.get(`${comp.id}_addtab_${tabIndex}_col_${colIndex}`); if (colMapping) { return { ...col, langKeyId: colMapping.keyId, langKey: colMapping.langKey }; } return col; }); } return updatedTab; }); // rightPanel.additionalTabs에 저장하거나 additionalTabs에 저장 if (currentRightPanelForAddTabs?.additionalTabs) { updated.componentConfig = { ...updated.componentConfig, rightPanel: { ...currentRightPanelForAddTabs, additionalTabs: updatedAdditionalTabs }, }; } else { updated.componentConfig = { ...updated.componentConfig, additionalTabs: updatedAdditionalTabs }; } } // 액션 버튼 매핑 if (config?.actions?.actions && Array.isArray(config.actions.actions)) { const updatedActions = config.actions.actions.map((action: any, index: number) => { const actionMapping = mappingMap.get(`${comp.id}_action_${index}`); if (actionMapping) { return { ...action, langKeyId: actionMapping.keyId, langKey: actionMapping.langKey }; } return action; }); updated.componentConfig = { ...updated.componentConfig, actions: { ...config.actions, actions: updatedActions }, }; } // 자식 컴포넌트 재귀 처리 if (anyComp.children && Array.isArray(anyComp.children)) { updated.children = anyComp.children.map((child: ComponentData) => updateComponent(child)); } return updated; }; return components.map(updateComponent); }