2025-09-12 14:24:25 +09:00
|
|
|
/**
|
|
|
|
|
* 컴포넌트 ID로 해당 컴포넌트의 ConfigPanel을 동적으로 로드하는 유틸리티
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React from "react";
|
2026-03-11 14:41:14 +09:00
|
|
|
import type { ConfigPanelContext } from "@/lib/registry/components/common/ConfigPanelTypes";
|
2025-09-12 14:24:25 +09:00
|
|
|
|
|
|
|
|
// 컴포넌트별 ConfigPanel 동적 import 맵
|
|
|
|
|
const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
|
2026-01-28 17:36:19 +09:00
|
|
|
// ========== V2 컴포넌트 ==========
|
2026-03-12 15:01:05 +09:00
|
|
|
"v2-input": () => import("@/components/v2/config-panels/V2FieldConfigPanel"),
|
|
|
|
|
"v2-select": () => import("@/components/v2/config-panels/V2FieldConfigPanel"),
|
2026-01-28 17:36:19 +09:00
|
|
|
"v2-date": () => import("@/components/v2/config-panels/V2DateConfigPanel"),
|
|
|
|
|
"v2-list": () => import("@/components/v2/config-panels/V2ListConfigPanel"),
|
|
|
|
|
"v2-media": () => import("@/components/v2/config-panels/V2MediaConfigPanel"),
|
|
|
|
|
"v2-biz": () => import("@/components/v2/config-panels/V2BizConfigPanel"),
|
|
|
|
|
"v2-group": () => import("@/components/v2/config-panels/V2GroupConfigPanel"),
|
|
|
|
|
"v2-hierarchy": () => import("@/components/v2/config-panels/V2HierarchyConfigPanel"),
|
|
|
|
|
"v2-layout": () => import("@/components/v2/config-panels/V2LayoutConfigPanel"),
|
|
|
|
|
"v2-repeater": () => import("@/components/v2/config-panels/V2RepeaterConfigPanel"),
|
2026-01-20 14:01:35 +09:00
|
|
|
|
2026-01-19 09:37:02 +09:00
|
|
|
// ========== 기본 입력 컴포넌트 ==========
|
2025-09-12 14:24:25 +09:00
|
|
|
"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"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"slider-basic": () => import("@/lib/registry/components/slider-basic/SliderBasicConfigPanel"),
|
|
|
|
|
"test-input": () => import("@/lib/registry/components/test-input/TestInputConfigPanel"),
|
|
|
|
|
|
|
|
|
|
// ========== 버튼 ==========
|
2025-11-17 15:25:08 +09:00
|
|
|
"button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-button-primary": () => import("@/components/screen/config-panels/ButtonConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
|
|
|
|
|
// ========== 표시 컴포넌트 ==========
|
2025-09-12 14:24:25 +09:00
|
|
|
"text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-text-display": () => import("@/lib/registry/components/v2-text-display/TextDisplayConfigPanel"),
|
2025-09-12 14:24:25 +09:00
|
|
|
"image-display": () => import("@/lib/registry/components/image-display/ImageDisplayConfigPanel"),
|
|
|
|
|
"divider-line": () => import("@/lib/registry/components/divider-line/DividerLineConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-divider-line": () => import("@/lib/registry/components/v2-divider-line/DividerLineConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"image-widget": () => import("@/lib/registry/components/image-widget/ImageWidgetConfigPanel"),
|
|
|
|
|
|
|
|
|
|
// ========== 레이아웃/컨테이너 ==========
|
2025-09-12 16:47:02 +09:00
|
|
|
"accordion-basic": () => import("@/lib/registry/components/accordion-basic/AccordionBasicConfigPanel"),
|
2025-09-15 17:10:46 +09:00
|
|
|
"card-display": () => import("@/lib/registry/components/card-display/CardDisplayConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-card-display": () => import("@/lib/registry/components/v2-card-display/CardDisplayConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"section-card": () => import("@/lib/registry/components/section-card/SectionCardConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-section-card": () => import("@/lib/registry/components/v2-section-card/SectionCardConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"section-paper": () => import("@/lib/registry/components/section-paper/SectionPaperConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-section-paper": () => import("@/lib/registry/components/v2-section-paper/SectionPaperConfigPanel"),
|
2025-10-15 17:25:38 +09:00
|
|
|
"split-panel-layout": () => import("@/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-split-panel-layout": () => import("@/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel"),
|
2025-12-03 17:45:22 +09:00
|
|
|
"split-panel-layout2": () => import("@/lib/registry/components/split-panel-layout2/SplitPanelLayout2ConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"screen-split-panel": () => import("@/lib/registry/components/screen-split-panel/ScreenSplitPanelConfigPanel"),
|
|
|
|
|
"conditional-container": () => import("@/lib/registry/components/conditional-container/ConditionalContainerConfigPanel"),
|
2026-02-24 09:29:44 +09:00
|
|
|
"v2-split-line": () => import("@/lib/registry/components/v2-split-line/SplitLineConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
|
|
|
|
|
// ========== 테이블/리스트 ==========
|
|
|
|
|
"table-list": () => import("@/lib/registry/components/table-list/TableListConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-table-list": () => import("@/lib/registry/components/v2-table-list/TableListConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"pivot-grid": () => import("@/lib/registry/components/pivot-grid/PivotGridConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-pivot-grid": () => import("@/lib/registry/components/v2-pivot-grid/PivotGridConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"table-search-widget": () => import("@/lib/registry/components/table-search-widget/TableSearchWidgetConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-table-search-widget": () => import("@/lib/registry/components/v2-table-search-widget/TableSearchWidgetConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"tax-invoice-list": () => import("@/lib/registry/components/tax-invoice-list/TaxInvoiceListConfigPanel"),
|
|
|
|
|
|
|
|
|
|
// ========== 리피터/반복 ==========
|
|
|
|
|
"repeat-container": () => import("@/lib/registry/components/repeat-container/RepeatContainerConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-repeat-container": () => import("@/lib/registry/components/v2-repeat-container/RepeatContainerConfigPanel"),
|
2025-10-16 15:05:24 +09:00
|
|
|
"repeater-field-group": () => import("@/components/webtypes/config/RepeaterConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"simple-repeater-table": () => import("@/lib/registry/components/simple-repeater-table/SimpleRepeaterTableConfigPanel"),
|
2025-11-14 14:43:53 +09:00
|
|
|
"modal-repeater-table": () => import("@/lib/registry/components/modal-repeater-table/ModalRepeaterTableConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"repeat-screen-modal": () => import("@/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel"),
|
|
|
|
|
"related-data-buttons": () => import("@/lib/registry/components/related-data-buttons/RelatedDataButtonsConfigPanel"),
|
|
|
|
|
|
|
|
|
|
// ========== 검색/선택 ==========
|
|
|
|
|
"autocomplete-search-input": () => import("@/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel"),
|
|
|
|
|
"entity-search-input": () => import("@/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel"),
|
|
|
|
|
"selected-items-detail-input": () => import("@/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputConfigPanel"),
|
|
|
|
|
"customer-item-mapping": () => import("@/lib/registry/components/customer-item-mapping/CustomerItemMappingConfigPanel"),
|
|
|
|
|
"mail-recipient-selector": () => import("@/lib/registry/components/mail-recipient-selector/MailRecipientSelectorConfigPanel"),
|
|
|
|
|
"location-swap-selector": () => import("@/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-location-swap-selector": () => import("@/lib/registry/components/v2-location-swap-selector/LocationSwapSelectorConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
|
|
|
|
|
// ========== 특수 컴포넌트 ==========
|
|
|
|
|
"flow-widget": () => import("@/components/screen/config-panels/FlowWidgetConfigPanel"),
|
2025-11-24 17:24:47 +09:00
|
|
|
"tabs-widget": () => import("@/components/screen/config-panels/TabsConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-tabs-widget": () => import("@/components/screen/config-panels/TabsConfigPanel"),
|
|
|
|
|
"tabs": () => import("@/components/screen/config-panels/TabsConfigPanel"),
|
|
|
|
|
"v2-tabs": () => import("@/components/screen/config-panels/TabsConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"map": () => import("@/lib/registry/components/map/MapConfigPanel"),
|
|
|
|
|
"rack-structure": () => import("@/lib/registry/components/rack-structure/RackStructureConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-rack-structure": () => import("@/lib/registry/components/v2-rack-structure/RackStructureConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"aggregation-widget": () => import("@/lib/registry/components/aggregation-widget/AggregationWidgetConfigPanel"),
|
2026-01-19 16:44:42 +09:00
|
|
|
"v2-aggregation-widget": () => import("@/lib/registry/components/v2-aggregation-widget/AggregationWidgetConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"numbering-rule": () => import("@/lib/registry/components/numbering-rule/NumberingRuleConfigPanel"),
|
2026-01-19 17:28:52 +09:00
|
|
|
"v2-numbering-rule": () => import("@/lib/registry/components/v2-numbering-rule/NumberingRuleConfigPanel"),
|
2026-01-19 09:37:02 +09:00
|
|
|
"category-manager": () => import("@/lib/registry/components/category-manager/CategoryManagerConfigPanel"),
|
|
|
|
|
"universal-form-modal": () => import("@/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel"),
|
2026-02-24 12:37:33 +09:00
|
|
|
"v2-process-work-standard": () => import("@/lib/registry/components/v2-process-work-standard/ProcessWorkStandardConfigPanel"),
|
2026-03-11 14:35:02 +09:00
|
|
|
|
|
|
|
|
// ========== V2 BOM 컴포넌트 ==========
|
|
|
|
|
"v2-bom-item-editor": () => import("@/components/v2/config-panels/V2BomItemEditorConfigPanel"),
|
|
|
|
|
"v2-bom-tree": () => import("@/components/v2/config-panels/V2BomTreeConfigPanel"),
|
|
|
|
|
|
|
|
|
|
// ========== 레거시 위젯 (component/onUpdateProperty props 사용) ==========
|
|
|
|
|
"card": () => import("@/components/screen/config-panels/CardConfigPanel"),
|
|
|
|
|
"dashboard": () => import("@/components/screen/config-panels/DashboardConfigPanel"),
|
|
|
|
|
"stats": () => import("@/components/screen/config-panels/StatsCardConfigPanel"),
|
|
|
|
|
"stats-card": () => import("@/components/screen/config-panels/StatsCardConfigPanel"),
|
|
|
|
|
"progress": () => import("@/components/screen/config-panels/ProgressBarConfigPanel"),
|
|
|
|
|
"progress-bar": () => import("@/components/screen/config-panels/ProgressBarConfigPanel"),
|
|
|
|
|
"chart": () => import("@/components/screen/config-panels/ChartConfigPanel"),
|
|
|
|
|
"chart-basic": () => import("@/components/screen/config-panels/ChartConfigPanel"),
|
|
|
|
|
"alert": () => import("@/components/screen/config-panels/AlertConfigPanel"),
|
|
|
|
|
"alert-info": () => import("@/components/screen/config-panels/AlertConfigPanel"),
|
|
|
|
|
"badge": () => import("@/components/screen/config-panels/BadgeConfigPanel"),
|
|
|
|
|
"badge-status": () => import("@/components/screen/config-panels/BadgeConfigPanel"),
|
2025-09-12 14:24:25 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const configPanelCache = new Map<string, React.ComponentType<any>>();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컴포넌트 ID로 ConfigPanel 컴포넌트를 동적으로 로드
|
|
|
|
|
*/
|
|
|
|
|
export async function getComponentConfigPanel(componentId: string): Promise<React.ComponentType<any> | null> {
|
|
|
|
|
if (configPanelCache.has(componentId)) {
|
|
|
|
|
return configPanelCache.get(componentId)!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const importFn = CONFIG_PANEL_MAP[componentId];
|
|
|
|
|
if (!importFn) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const module = await importFn();
|
2025-09-12 16:47:02 +09:00
|
|
|
|
2026-03-11 14:35:02 +09:00
|
|
|
// 모듈에서 ConfigPanel 컴포넌트 추출 (우선순위):
|
|
|
|
|
// 1차: PascalCase 변환된 이름 (예: text-input -> TextInputConfigPanel)
|
|
|
|
|
// 2차: v2- 접두사 제거 후 PascalCase (예: v2-table-list -> TableListConfigPanel)
|
|
|
|
|
// 3차: *ConfigPanel로 끝나는 첫 번째 named export
|
2026-01-20 14:01:35 +09:00
|
|
|
// 4차: default export
|
2026-01-19 09:37:02 +09:00
|
|
|
const pascalCaseName = `${toPascalCase(componentId)}ConfigPanel`;
|
2026-01-20 14:01:35 +09:00
|
|
|
const baseComponentId = componentId.startsWith("v2-") ? componentId.slice(3) : componentId;
|
|
|
|
|
const basePascalCaseName = `${toPascalCase(baseComponentId)}ConfigPanel`;
|
2026-03-11 14:41:14 +09:00
|
|
|
|
2026-03-11 14:35:02 +09:00
|
|
|
const findConfigPanelExport = () => {
|
|
|
|
|
for (const key of Object.keys(module)) {
|
|
|
|
|
if (key.endsWith("ConfigPanel") && typeof module[key] === "function") {
|
|
|
|
|
return module[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-16 15:05:24 +09:00
|
|
|
const ConfigPanelComponent =
|
2026-01-19 09:37:02 +09:00
|
|
|
module[pascalCaseName] ||
|
2026-01-20 14:01:35 +09:00
|
|
|
module[basePascalCaseName] ||
|
2026-03-11 14:35:02 +09:00
|
|
|
findConfigPanelExport() ||
|
2025-10-16 15:05:24 +09:00
|
|
|
module.default;
|
2025-09-12 16:47:02 +09:00
|
|
|
|
2025-09-12 14:24:25 +09:00
|
|
|
if (!ConfigPanelComponent) {
|
|
|
|
|
console.error(`컴포넌트 "${componentId}"의 ConfigPanel을 모듈에서 찾을 수 없습니다.`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configPanelCache.set(componentId, ConfigPanelComponent);
|
|
|
|
|
return ConfigPanelComponent;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`컴포넌트 "${componentId}"의 ConfigPanel 로드 실패:`, error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function hasComponentConfigPanel(componentId: string): boolean {
|
|
|
|
|
return componentId in CONFIG_PANEL_MAP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getSupportedConfigPanelComponents(): string[] {
|
|
|
|
|
return Object.keys(CONFIG_PANEL_MAP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toPascalCase(str: string): string {
|
|
|
|
|
return str
|
2025-09-12 16:47:02 +09:00
|
|
|
.split("-")
|
|
|
|
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
|
|
|
.join("");
|
2025-09-12 14:24:25 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컴포넌트 설정 패널을 렌더링하는 React 컴포넌트
|
|
|
|
|
*/
|
|
|
|
|
export interface ComponentConfigPanelProps {
|
|
|
|
|
componentId: string;
|
|
|
|
|
config: Record<string, any>;
|
|
|
|
|
onChange: (config: Record<string, any>) => void;
|
2026-03-11 14:41:14 +09:00
|
|
|
screenTableName?: string;
|
|
|
|
|
tableColumns?: any[];
|
|
|
|
|
tables?: any[];
|
|
|
|
|
menuObjid?: number;
|
|
|
|
|
allComponents?: any[];
|
|
|
|
|
currentComponent?: any;
|
2026-03-12 15:01:05 +09:00
|
|
|
componentType?: string;
|
2025-09-12 14:24:25 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DynamicComponentConfigPanel: React.FC<ComponentConfigPanelProps> = ({
|
|
|
|
|
componentId,
|
|
|
|
|
config,
|
|
|
|
|
onChange,
|
2025-09-12 16:47:02 +09:00
|
|
|
screenTableName,
|
|
|
|
|
tableColumns,
|
2025-10-15 17:25:38 +09:00
|
|
|
tables,
|
2025-11-11 18:24:24 +09:00
|
|
|
menuObjid,
|
2025-12-10 13:53:44 +09:00
|
|
|
allComponents,
|
|
|
|
|
currentComponent,
|
2026-03-12 15:01:05 +09:00
|
|
|
componentType,
|
2025-09-12 14:24:25 +09:00
|
|
|
}) => {
|
|
|
|
|
const [ConfigPanelComponent, setConfigPanelComponent] = React.useState<React.ComponentType<any> | null>(null);
|
|
|
|
|
const [loading, setLoading] = React.useState(true);
|
|
|
|
|
const [error, setError] = React.useState<string | null>(null);
|
2025-10-17 15:31:23 +09:00
|
|
|
const [selectedTableColumns, setSelectedTableColumns] = React.useState(tableColumns);
|
|
|
|
|
const [allTablesList, setAllTablesList] = React.useState<any[]>([]);
|
2025-11-17 15:25:08 +09:00
|
|
|
const [sourceTableColumns, setSourceTableColumns] = React.useState<any[]>([]);
|
|
|
|
|
const [targetTableColumns, setTargetTableColumns] = React.useState<any[]>([]);
|
2025-09-12 14:24:25 +09:00
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
loadConfigPanel();
|
2026-03-11 14:41:14 +09:00
|
|
|
return () => { mounted = false; };
|
2025-09-12 14:24:25 +09:00
|
|
|
}, [componentId]);
|
|
|
|
|
|
2025-10-17 15:31:23 +09:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
setSelectedTableColumns(tableColumns);
|
|
|
|
|
}, [tableColumns]);
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// repeater-field-group / selected-items-detail-input에서 전체 테이블 목록 로드
|
2025-10-17 15:31:23 +09:00
|
|
|
React.useEffect(() => {
|
2025-11-17 15:25:08 +09:00
|
|
|
if (componentId === "repeater-field-group" || componentId === "selected-items-detail-input") {
|
2025-10-17 15:31:23 +09:00
|
|
|
const loadAllTables = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const { tableManagementApi } = await import("@/lib/api/tableManagement");
|
|
|
|
|
const response = await tableManagementApi.getTableList();
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
setAllTablesList(response.data);
|
|
|
|
|
}
|
2026-03-11 14:41:14 +09:00
|
|
|
} catch (_) {
|
|
|
|
|
// 전체 테이블 목록 로드 실패 시 무시
|
2025-10-17 15:31:23 +09:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
loadAllTables();
|
|
|
|
|
}
|
|
|
|
|
}, [componentId]);
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// selected-items-detail-input: 초기 sourceTable/targetTable 컬럼 로드
|
2025-11-17 15:25:08 +09:00
|
|
|
React.useEffect(() => {
|
2026-03-11 14:41:14 +09:00
|
|
|
if (componentId !== "selected-items-detail-input") return;
|
|
|
|
|
|
|
|
|
|
const loadColumns = async (tableName: string, setter: React.Dispatch<React.SetStateAction<any[]>>, includeCodeCategory?: boolean) => {
|
|
|
|
|
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,
|
|
|
|
|
...(includeCodeCategory ? { codeCategory: col.codeCategory || col.code_category } : {}),
|
|
|
|
|
}));
|
|
|
|
|
setter(columns);
|
|
|
|
|
} catch (_) {
|
|
|
|
|
setter([]);
|
2025-11-17 15:25:08 +09:00
|
|
|
}
|
2026-03-11 14:41:14 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (config.sourceTable) loadColumns(config.sourceTable, setSourceTableColumns);
|
|
|
|
|
if (config.targetTable) loadColumns(config.targetTable, setTargetTableColumns, true);
|
2025-11-17 15:25:08 +09:00
|
|
|
}, [componentId, config.sourceTable, config.targetTable]);
|
|
|
|
|
|
2026-01-19 17:28:52 +09:00
|
|
|
const screenComponents = React.useMemo(() => {
|
2026-03-11 14:41:14 +09:00
|
|
|
if (!allComponents) return [];
|
|
|
|
|
return allComponents.map((comp: any) => ({
|
|
|
|
|
id: comp.id,
|
|
|
|
|
componentType: comp.componentType || comp.type,
|
|
|
|
|
label: comp.label || comp.name || comp.id,
|
|
|
|
|
tableName: comp.componentConfig?.tableName || comp.tableName,
|
|
|
|
|
columnName: comp.columnName || comp.componentConfig?.columnName || comp.componentConfig?.fieldName,
|
|
|
|
|
}));
|
2026-01-19 17:28:52 +09:00
|
|
|
}, [allComponents]);
|
|
|
|
|
|
2025-09-12 14:24:25 +09:00
|
|
|
if (loading) {
|
|
|
|
|
return (
|
2026-03-09 14:31:59 +09:00
|
|
|
<div className="rounded-md border border-dashed border-input bg-muted p-4 w-full">
|
|
|
|
|
<div className="flex items-center gap-2 text-muted-foreground">
|
2026-03-11 14:41:14 +09:00
|
|
|
<span className="text-sm font-medium">로딩 중...</span>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
2026-03-09 14:31:59 +09:00
|
|
|
<p className="mt-1 text-xs text-muted-foreground">설정 패널을 불러오는 중입니다.</p>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return (
|
2026-03-09 14:31:59 +09:00
|
|
|
<div className="rounded-md border border-dashed border-destructive/30 bg-destructive/10 p-4 w-full">
|
|
|
|
|
<div className="flex items-center gap-2 text-destructive">
|
2026-03-11 14:41:14 +09:00
|
|
|
<span className="text-sm font-medium">로드 실패</span>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
2026-03-09 14:31:59 +09:00
|
|
|
<p className="mt-1 text-xs text-destructive">설정 패널을 불러올 수 없습니다: {error}</p>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ConfigPanelComponent) {
|
|
|
|
|
return (
|
2026-03-09 14:31:59 +09:00
|
|
|
<div className="rounded-md border border-dashed border-amber-300 bg-amber-50 p-4 w-full">
|
|
|
|
|
<div className="flex items-center gap-2 text-amber-600">
|
2026-03-11 14:41:14 +09:00
|
|
|
<span className="text-sm font-medium">설정 패널 없음</span>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
2026-03-11 14:41:14 +09:00
|
|
|
<p className="mt-1 text-xs text-amber-500">컴포넌트 "{componentId}"에 대한 설정 패널이 없습니다.</p>
|
2025-09-12 14:24:25 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// 테이블 변경 핸들러
|
2025-10-17 15:31:23 +09:00
|
|
|
const handleTableChange = async (tableName: string) => {
|
|
|
|
|
try {
|
|
|
|
|
const existingTable = tables?.find((t) => t.tableName === tableName);
|
2026-03-11 14:41:14 +09:00
|
|
|
if (existingTable?.columns?.length > 0) {
|
2025-10-17 15:31:23 +09:00
|
|
|
setSelectedTableColumns(existingTable.columns);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
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);
|
2026-03-11 14:41:14 +09:00
|
|
|
} catch (_) {
|
2025-10-17 15:31:23 +09:00
|
|
|
setSelectedTableColumns([]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-17 15:25:08 +09:00
|
|
|
const handleSourceTableChange = async (tableName: string) => {
|
|
|
|
|
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,
|
2026-03-11 14:41:14 +09:00
|
|
|
inputType: col.inputType || col.input_type,
|
2025-11-17 15:25:08 +09:00
|
|
|
}));
|
|
|
|
|
setSourceTableColumns(columns);
|
2026-03-11 14:41:14 +09:00
|
|
|
} catch (_) {
|
2025-11-17 15:25:08 +09:00
|
|
|
setSourceTableColumns([]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTargetTableChange = async (tableName: string) => {
|
|
|
|
|
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,
|
2026-03-11 14:41:14 +09:00
|
|
|
inputType: col.inputType || col.input_type,
|
|
|
|
|
codeCategory: col.codeCategory || col.code_category,
|
2025-11-17 15:25:08 +09:00
|
|
|
}));
|
|
|
|
|
setTargetTableColumns(columns);
|
2026-03-11 14:41:14 +09:00
|
|
|
} catch (_) {
|
2025-11-17 15:25:08 +09:00
|
|
|
setTargetTableColumns([]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// --- 특수 래퍼: 레거시 위젯 (component/onUpdateProperty props) ---
|
2026-03-11 14:35:02 +09:00
|
|
|
const LEGACY_PANELS = new Set([
|
|
|
|
|
"card", "dashboard", "stats", "stats-card",
|
|
|
|
|
"progress", "progress-bar", "chart", "chart-basic",
|
|
|
|
|
"alert", "alert-info", "badge", "badge-status",
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (LEGACY_PANELS.has(componentId)) {
|
|
|
|
|
const pseudoComponent = {
|
|
|
|
|
id: currentComponent?.id || "temp",
|
|
|
|
|
type: "component",
|
|
|
|
|
componentConfig: config,
|
|
|
|
|
...currentComponent,
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<ConfigPanelComponent
|
|
|
|
|
component={pseudoComponent}
|
|
|
|
|
onUpdateProperty={(path: string, value: any) => {
|
|
|
|
|
if (path.startsWith("componentConfig.")) {
|
|
|
|
|
const key = path.replace("componentConfig.", "");
|
|
|
|
|
onChange({ ...config, [key]: value });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// --- 특수 래퍼: ButtonConfigPanel (component/onUpdateProperty props) ---
|
2026-01-20 14:01:35 +09:00
|
|
|
if (componentId === "button-primary" || componentId === "v2-button-primary") {
|
|
|
|
|
const componentForButton = currentComponent || {
|
|
|
|
|
id: "temp",
|
|
|
|
|
type: "component",
|
|
|
|
|
componentType: componentId,
|
|
|
|
|
componentConfig: config,
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<ConfigPanelComponent
|
|
|
|
|
component={componentForButton}
|
|
|
|
|
onUpdateProperty={(path: string, value: any) => {
|
|
|
|
|
if (path.startsWith("componentConfig.")) {
|
|
|
|
|
const configPath = path.replace("componentConfig.", "");
|
|
|
|
|
const pathParts = configPath.split(".");
|
|
|
|
|
const currentConfig = componentForButton.componentConfig || {};
|
2026-03-11 14:41:14 +09:00
|
|
|
const newConfig = JSON.parse(JSON.stringify(currentConfig));
|
2026-01-20 14:01:35 +09:00
|
|
|
let current: any = newConfig;
|
|
|
|
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
2026-03-11 14:41:14 +09:00
|
|
|
if (!current[pathParts[i]]) current[pathParts[i]] = {};
|
2026-01-20 14:01:35 +09:00
|
|
|
current = current[pathParts[i]];
|
|
|
|
|
}
|
|
|
|
|
current[pathParts[pathParts.length - 1]] = value;
|
|
|
|
|
onChange(newConfig);
|
|
|
|
|
} else {
|
|
|
|
|
const currentConfig = componentForButton.componentConfig || {};
|
|
|
|
|
onChange({ ...currentConfig, [path]: value });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
allComponents={allComponents}
|
|
|
|
|
currentTableName={screenTableName}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 14:41:14 +09:00
|
|
|
// --- 통일된 props: 모든 일반 패널에 동일한 props 전달 ---
|
|
|
|
|
const context: ConfigPanelContext = {
|
|
|
|
|
tables,
|
|
|
|
|
tableColumns: selectedTableColumns,
|
|
|
|
|
screenTableName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
allComponents,
|
|
|
|
|
currentComponent,
|
|
|
|
|
allTables: allTablesList.length > 0 ? allTablesList : tables,
|
|
|
|
|
screenComponents,
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-12 16:47:02 +09:00
|
|
|
return (
|
|
|
|
|
<ConfigPanelComponent
|
|
|
|
|
config={config}
|
|
|
|
|
onChange={onChange}
|
2026-03-11 14:41:14 +09:00
|
|
|
onConfigChange={onChange}
|
|
|
|
|
context={context}
|
2025-09-12 16:47:02 +09:00
|
|
|
screenTableName={screenTableName}
|
2026-03-12 15:01:05 +09:00
|
|
|
tableName={screenTableName}
|
|
|
|
|
columnName={currentComponent?.columnName || config?.columnName || config?.fieldName}
|
2026-03-11 14:41:14 +09:00
|
|
|
tableColumns={selectedTableColumns}
|
|
|
|
|
tables={tables}
|
|
|
|
|
allTables={allTablesList.length > 0 ? allTablesList : tables}
|
|
|
|
|
onTableChange={handleTableChange}
|
|
|
|
|
menuObjid={menuObjid}
|
|
|
|
|
allComponents={allComponents}
|
|
|
|
|
currentComponent={currentComponent}
|
|
|
|
|
screenComponents={screenComponents}
|
|
|
|
|
inputType={currentComponent?.inputType || config?.inputType}
|
2026-03-12 15:01:05 +09:00
|
|
|
componentType={componentType || componentId}
|
2026-03-11 14:41:14 +09:00
|
|
|
sourceTableColumns={sourceTableColumns}
|
|
|
|
|
targetTableColumns={targetTableColumns}
|
|
|
|
|
onSourceTableChange={handleSourceTableChange}
|
|
|
|
|
onTargetTableChange={handleTargetTableChange}
|
2025-09-12 16:47:02 +09:00
|
|
|
/>
|
|
|
|
|
);
|
2025-09-12 14:24:25 +09:00
|
|
|
};
|