ERP-node/frontend/lib/utils/getComponentConfigPanel.tsx

290 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 컴포넌트 ID로 해당 컴포넌트의 ConfigPanel을 동적으로 로드하는 유틸리티
*/
import React from "react";
// 컴포넌트별 ConfigPanel 동적 import 맵
const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
"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("@/lib/registry/components/button-primary/ButtonPrimaryConfigPanel"),
"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"),
};
// ConfigPanel 컴포넌트 캐시
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)!;
}
// 매핑에서 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.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<string, any>;
onChange: (config: Record<string, any>) => void;
screenTableName?: string; // 화면에서 지정한 테이블명
tableColumns?: any[]; // 테이블 컬럼 정보
tables?: any[]; // 전체 테이블 목록
menuObjid?: number; // 🆕 메뉴 OBJID (코드/카테고리/채번규칙 스코프용)
}
export const DynamicComponentConfigPanel: React.FC<ComponentConfigPanelProps> = ({
componentId,
config,
onChange,
screenTableName,
tableColumns,
tables,
menuObjid,
}) => {
// 모든 useState를 최상단에 선언 (Hooks 규칙)
const [ConfigPanelComponent, setConfigPanelComponent] = React.useState<React.ComponentType<any> | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string | null>(null);
const [selectedTableColumns, setSelectedTableColumns] = React.useState(tableColumns);
const [allTablesList, setAllTablesList] = React.useState<any[]>([]);
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인 경우에만 전체 테이블 목록 로드
React.useEffect(() => {
if (componentId === "repeater-field-group") {
const loadAllTables = async () => {
try {
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
setAllTablesList(response.data);
}
} catch (error) {
console.error("전체 테이블 목록 로드 실패:", error);
}
};
loadAllTables();
}
}, [componentId]);
if (loading) {
return (
<div className="rounded-md border border-dashed border-gray-300 bg-gray-50 p-4">
<div className="flex items-center gap-2 text-gray-600">
<span className="text-sm font-medium"> ...</span>
</div>
<p className="mt-1 text-xs text-gray-500"> .</p>
</div>
);
}
if (error) {
return (
<div className="rounded-md border border-dashed border-red-300 bg-red-50 p-4">
<div className="flex items-center gap-2 text-red-600">
<span className="text-sm font-medium"> </span>
</div>
<p className="mt-1 text-xs text-red-500"> : {error}</p>
</div>
);
}
if (!ConfigPanelComponent) {
console.warn(`⚠️ DynamicComponentConfigPanel: ${componentId} ConfigPanelComponent가 null`);
return (
<div className="rounded-md border border-dashed border-yellow-300 bg-yellow-50 p-4">
<div className="flex items-center gap-2 text-yellow-600">
<span className="text-sm font-medium"> </span>
</div>
<p className="mt-1 text-xs text-yellow-500"> "{componentId}" .</p>
</div>
);
}
// 테이블 변경 핸들러 - 선택된 테이블의 컬럼을 동적으로 로드
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([]);
}
};
// 🆕 수주 등록 관련 컴포넌트들은 간단한 인터페이스 사용
const isSimpleConfigPanel = [
"autocomplete-search-input",
"entity-search-input",
"modal-repeater-table",
"order-registration-modal"
].includes(componentId);
if (isSimpleConfigPanel) {
return (
<ConfigPanelComponent
config={config}
onConfigChange={onChange}
/>
);
}
return (
<ConfigPanelComponent
config={config}
onChange={onChange}
onConfigChange={onChange} // TableListConfigPanel을 위한 추가 prop
screenTableName={screenTableName}
tableColumns={selectedTableColumns} // 동적으로 변경되는 컬럼 전달
tables={tables} // 기본 테이블 목록 (현재 화면의 테이블만)
allTables={componentId === "repeater-field-group" ? allTablesList : tables} // RepeaterConfigPanel만 전체 테이블
onTableChange={handleTableChange} // 테이블 변경 핸들러 전달
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
/>
);
};