/** * 컴포넌트 ID로 해당 컴포넌트의 ConfigPanel을 동적으로 로드하는 유틸리티 */ import React from "react"; // 컴포넌트별 ConfigPanel 동적 import 맵 const CONFIG_PANEL_MAP: Record Promise> = { "text-input": () => import("@/lib/registry/components/text-input/TextInputConfigPanel"), "number-input": () => import("@/lib/registry/components/number-input/NumberInputConfigPanel"), "date-input": () => import("@/lib/registry/components/date-input/DateInputConfigPanel"), "textarea-basic": () => import("@/lib/registry/components/textarea-basic/TextareaBasicConfigPanel"), "select-basic": () => import("@/lib/registry/components/select-basic/SelectBasicConfigPanel"), "checkbox-basic": () => import("@/lib/registry/components/checkbox-basic/CheckboxBasicConfigPanel"), "radio-basic": () => import("@/lib/registry/components/radio-basic/RadioBasicConfigPanel"), "toggle-switch": () => import("@/lib/registry/components/toggle-switch/ToggleSwitchConfigPanel"), "file-upload": () => import("@/lib/registry/components/file-upload/FileUploadConfigPanel"), "button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"), "text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"), "slider-basic": () => import("@/lib/registry/components/slider-basic/SliderBasicConfigPanel"), "image-display": () => import("@/lib/registry/components/image-display/ImageDisplayConfigPanel"), "divider-line": () => import("@/lib/registry/components/divider-line/DividerLineConfigPanel"), "accordion-basic": () => import("@/lib/registry/components/accordion-basic/AccordionBasicConfigPanel"), "table-list": () => import("@/lib/registry/components/table-list/TableListConfigPanel"), "card-display": () => import("@/lib/registry/components/card-display/CardDisplayConfigPanel"), "split-panel-layout": () => import("@/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel"), "repeater-field-group": () => import("@/components/webtypes/config/RepeaterConfigPanel"), "flow-widget": () => import("@/components/screen/config-panels/FlowWidgetConfigPanel"), // 🆕 수주 등록 관련 컴포넌트들 "autocomplete-search-input": () => import("@/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel"), "entity-search-input": () => import("@/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel"), "modal-repeater-table": () => import("@/lib/registry/components/modal-repeater-table/ModalRepeaterTableConfigPanel"), "order-registration-modal": () => import("@/lib/registry/components/order-registration-modal/OrderRegistrationModalConfigPanel"), // 🆕 조건부 컨테이너 "conditional-container": () => import("@/lib/registry/components/conditional-container/ConditionalContainerConfigPanel"), // 🆕 선택 항목 상세입력 "selected-items-detail-input": () => import("@/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputConfigPanel"), }; // ConfigPanel 컴포넌트 캐시 const configPanelCache = new Map>(); /** * 컴포넌트 ID로 ConfigPanel 컴포넌트를 동적으로 로드 */ export async function getComponentConfigPanel(componentId: string): Promise | null> { // 캐시에서 먼저 확인 if (configPanelCache.has(componentId)) { return configPanelCache.get(componentId)!; } // 매핑에서 import 함수 찾기 const importFn = CONFIG_PANEL_MAP[componentId]; if (!importFn) { console.warn(`컴포넌트 "${componentId}"에 대한 ConfigPanel을 찾을 수 없습니다.`); return null; } try { const module = await importFn(); // 모듈에서 ConfigPanel 컴포넌트 추출 const ConfigPanelComponent = module[`${toPascalCase(componentId)}ConfigPanel`] || module.RepeaterConfigPanel || // repeater-field-group의 export명 module.FlowWidgetConfigPanel || // flow-widget의 export명 module.CustomerItemMappingConfigPanel || // customer-item-mapping의 export명 module.SelectedItemsDetailInputConfigPanel || // selected-items-detail-input의 export명 module.ButtonConfigPanel || // button-primary의 export명 module.default; if (!ConfigPanelComponent) { console.error(`컴포넌트 "${componentId}"의 ConfigPanel을 모듈에서 찾을 수 없습니다.`); return null; } // 캐시에 저장 configPanelCache.set(componentId, ConfigPanelComponent); return ConfigPanelComponent; } catch (error) { console.error(`컴포넌트 "${componentId}"의 ConfigPanel 로드 실패:`, error); return null; } } /** * 컴포넌트 ID가 ConfigPanel을 지원하는지 확인 */ export function hasComponentConfigPanel(componentId: string): boolean { return componentId in CONFIG_PANEL_MAP; } /** * 지원되는 모든 컴포넌트 ID 목록 조회 */ export function getSupportedConfigPanelComponents(): string[] { return Object.keys(CONFIG_PANEL_MAP); } /** * kebab-case를 PascalCase로 변환 * text-input → TextInput */ function toPascalCase(str: string): string { return str .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(""); } /** * 컴포넌트 설정 패널을 렌더링하는 React 컴포넌트 */ export interface ComponentConfigPanelProps { componentId: string; config: Record; onChange: (config: Record) => void; screenTableName?: string; // 화면에서 지정한 테이블명 tableColumns?: any[]; // 테이블 컬럼 정보 tables?: any[]; // 전체 테이블 목록 menuObjid?: number; // 🆕 메뉴 OBJID (코드/카테고리/채번규칙 스코프용) } export const DynamicComponentConfigPanel: React.FC = ({ componentId, config, onChange, screenTableName, tableColumns, tables, menuObjid, }) => { // 모든 useState를 최상단에 선언 (Hooks 규칙) const [ConfigPanelComponent, setConfigPanelComponent] = React.useState | null>(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [selectedTableColumns, setSelectedTableColumns] = React.useState(tableColumns); const [allTablesList, setAllTablesList] = React.useState([]); // 🆕 selected-items-detail-input 전용 상태 const [sourceTableColumns, setSourceTableColumns] = React.useState([]); const [targetTableColumns, setTargetTableColumns] = React.useState([]); React.useEffect(() => { let mounted = true; async function loadConfigPanel() { try { setLoading(true); setError(null); const component = await getComponentConfigPanel(componentId); if (mounted) { setConfigPanelComponent(() => component); setLoading(false); } } catch (err) { console.error(`❌ DynamicComponentConfigPanel: ${componentId} 로드 실패:`, err); if (mounted) { setError(err instanceof Error ? err.message : String(err)); setLoading(false); } } } loadConfigPanel(); return () => { mounted = false; }; }, [componentId]); // tableColumns가 변경되면 selectedTableColumns도 업데이트 React.useEffect(() => { setSelectedTableColumns(tableColumns); }, [tableColumns]); // RepeaterConfigPanel과 selected-items-detail-input에서 전체 테이블 목록 로드 React.useEffect(() => { if (componentId === "repeater-field-group" || componentId === "selected-items-detail-input") { const loadAllTables = async () => { try { const { tableManagementApi } = await import("@/lib/api/tableManagement"); const response = await tableManagementApi.getTableList(); if (response.success && response.data) { console.log(`✅ 전체 테이블 목록 로드 완료 (${componentId}):`, response.data.length); setAllTablesList(response.data); } } catch (error) { console.error("전체 테이블 목록 로드 실패:", error); } }; loadAllTables(); } }, [componentId]); // 🆕 selected-items-detail-input: 초기 sourceTable/targetTable 컬럼 로드 React.useEffect(() => { if (componentId === "selected-items-detail-input") { console.log("🔍 selected-items-detail-input 초기 설정:", config); // 원본 테이블 컬럼 로드 if (config.sourceTable) { const loadSourceColumns = async () => { try { const { tableTypeApi } = await import("@/lib/api/screen"); const columnsResponse = await tableTypeApi.getColumns(config.sourceTable); const columns = (columnsResponse || []).map((col: any) => ({ columnName: col.columnName || col.column_name, columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, dataType: col.dataType || col.data_type || col.dbType, inputType: col.inputType || col.input_type, // 🆕 inputType 추가 })); console.log("✅ 원본 테이블 컬럼 초기 로드 완료:", columns.length); setSourceTableColumns(columns); } catch (error) { console.error("❌ 원본 테이블 컬럼 초기 로드 실패:", error); } }; loadSourceColumns(); } // 대상 테이블 컬럼 로드 if (config.targetTable) { const loadTargetColumns = async () => { try { const { tableTypeApi } = await import("@/lib/api/screen"); const columnsResponse = await tableTypeApi.getColumns(config.targetTable); const columns = (columnsResponse || []).map((col: any) => ({ columnName: col.columnName || col.column_name, columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, dataType: col.dataType || col.data_type || col.dbType, inputType: col.inputType || col.input_type, // 🆕 inputType 추가 codeCategory: col.codeCategory || col.code_category, // 🆕 codeCategory 추가 })); console.log("✅ 대상 테이블 컬럼 초기 로드 완료:", columns.length); setTargetTableColumns(columns); } catch (error) { console.error("❌ 대상 테이블 컬럼 초기 로드 실패:", error); } }; loadTargetColumns(); } } }, [componentId, config.sourceTable, config.targetTable]); if (loading) { return (
⏳ 로딩 중...

설정 패널을 불러오는 중입니다.

); } if (error) { return (
⚠️ 로드 실패

설정 패널을 불러올 수 없습니다: {error}

); } if (!ConfigPanelComponent) { console.warn(`⚠️ DynamicComponentConfigPanel: ${componentId} ConfigPanelComponent가 null`); return (
⚠️ 설정 패널 없음

컴포넌트 "{componentId}"에 대한 설정 패널이 없습니다.

); } // 테이블 변경 핸들러 - 선택된 테이블의 컬럼을 동적으로 로드 const handleTableChange = async (tableName: string) => { try { // 먼저 tables에서 찾아보기 (이미 컬럼이 있는 경우) const existingTable = tables?.find((t) => t.tableName === tableName); if (existingTable && existingTable.columns && existingTable.columns.length > 0) { setSelectedTableColumns(existingTable.columns); return; } // 컬럼이 없으면 tableTypeApi로 조회 (ScreenDesigner와 동일한 방식) const { tableTypeApi } = await import("@/lib/api/screen"); const columnsResponse = await tableTypeApi.getColumns(tableName); const columns = (columnsResponse || []).map((col: any) => ({ tableName: col.tableName || tableName, columnName: col.columnName || col.column_name, columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, dataType: col.dataType || col.data_type || col.dbType, webType: col.webType || col.web_type, input_type: col.inputType || col.input_type, widgetType: col.widgetType || col.widget_type || col.webType || col.web_type, isNullable: col.isNullable || col.is_nullable, required: col.required !== undefined ? col.required : col.isNullable === "NO" || col.is_nullable === "NO", columnDefault: col.columnDefault || col.column_default, characterMaximumLength: col.characterMaximumLength || col.character_maximum_length, codeCategory: col.codeCategory || col.code_category, codeValue: col.codeValue || col.code_value, })); setSelectedTableColumns(columns); } catch (error) { console.error("❌ 테이블 변경 오류:", error); // 오류 발생 시 빈 배열 setSelectedTableColumns([]); } }; // 🆕 원본 테이블 컬럼 로드 핸들러 (selected-items-detail-input용) const handleSourceTableChange = async (tableName: string) => { console.log("🔄 원본 테이블 변경:", tableName); try { const { tableTypeApi } = await import("@/lib/api/screen"); const columnsResponse = await tableTypeApi.getColumns(tableName); const columns = (columnsResponse || []).map((col: any) => ({ columnName: col.columnName || col.column_name, columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, dataType: col.dataType || col.data_type || col.dbType, inputType: col.inputType || col.input_type, // 🆕 inputType 추가 })); console.log("✅ 원본 테이블 컬럼 로드 완료:", columns.length); setSourceTableColumns(columns); } catch (error) { console.error("❌ 원본 테이블 컬럼 로드 실패:", error); setSourceTableColumns([]); } }; // 🆕 대상 테이블 컬럼 로드 핸들러 (selected-items-detail-input용) const handleTargetTableChange = async (tableName: string) => { console.log("🔄 대상 테이블 변경:", tableName); try { const { tableTypeApi } = await import("@/lib/api/screen"); const columnsResponse = await tableTypeApi.getColumns(tableName); console.log("📡 [handleTargetTableChange] API 응답 (원본):", { totalColumns: columnsResponse.length, sampleColumns: columnsResponse.slice(0, 3), currency_code_raw: columnsResponse.find((c: any) => (c.columnName || c.column_name) === 'currency_code') }); const columns = (columnsResponse || []).map((col: any) => ({ columnName: col.columnName || col.column_name, columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, dataType: col.dataType || col.data_type || col.dbType, inputType: col.inputType || col.input_type, // 🆕 inputType 추가 codeCategory: col.codeCategory || col.code_category, // 🆕 codeCategory 추가 })); console.log("✅ 대상 테이블 컬럼 변환 완료:", { tableName, totalColumns: columns.length, currency_code: columns.find((c: any) => c.columnName === "currency_code"), discount_rate: columns.find((c: any) => c.columnName === "discount_rate") }); setTargetTableColumns(columns); } catch (error) { console.error("❌ 대상 테이블 컬럼 로드 실패:", error); setTargetTableColumns([]); } }; // 🆕 수주 등록 관련 컴포넌트들은 간단한 인터페이스 사용 const isSimpleConfigPanel = [ "autocomplete-search-input", "entity-search-input", "modal-repeater-table", "order-registration-modal", "conditional-container", ].includes(componentId); if (isSimpleConfigPanel) { return ; } // 🆕 selected-items-detail-input은 특별한 props 사용 if (componentId === "selected-items-detail-input") { return ( 0 ? allTablesList : tables} // 전체 테이블 목록 (동적 로드 or 전달된 목록) screenTableName={screenTableName} // 🆕 현재 화면의 테이블명 (자동 설정용) onSourceTableChange={handleSourceTableChange} // 🆕 원본 테이블 변경 핸들러 onTargetTableChange={handleTargetTableChange} // 🆕 대상 테이블 변경 핸들러 /> ); } return ( ); };