ERP-node/frontend/lib/registry/DynamicComponentRenderer.tsx

217 lines
7.0 KiB
TypeScript

"use client";
import React from "react";
import { ComponentData } from "@/types/screen";
import { DynamicLayoutRenderer } from "./DynamicLayoutRenderer";
import { ComponentRegistry } from "./ComponentRegistry";
// 컴포넌트 렌더러 인터페이스
export interface ComponentRenderer {
(props: {
component: ComponentData;
isSelected?: boolean;
isInteractive?: boolean;
formData?: Record<string, any>;
originalData?: Record<string, any>; // 부분 업데이트용 원본 데이터
onFormDataChange?: (fieldName: string, value: any) => void;
onClick?: (e?: React.MouseEvent) => void;
onDragStart?: (e: React.DragEvent) => void;
onDragEnd?: () => void;
children?: React.ReactNode;
onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void;
onZoneClick?: (zoneId: string) => void;
// 버튼 액션을 위한 추가 props
screenId?: number;
tableName?: string;
onRefresh?: () => void;
onClose?: () => void;
// 테이블 선택된 행 정보 (다중 선택 액션용)
selectedRows?: any[];
selectedRowsData?: any[];
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void;
// 테이블 새로고침 키
refreshKey?: number;
// 편집 모드
mode?: "view" | "edit";
[key: string]: any;
}): React.ReactElement;
}
// 레거시 렌더러 레지스트리 (기존 컴포넌트들용)
class LegacyComponentRegistry {
private renderers: Map<string, ComponentRenderer> = new Map();
// 컴포넌트 렌더러 등록
register(componentType: string, renderer: ComponentRenderer) {
this.renderers.set(componentType, renderer);
}
// 컴포넌트 렌더러 조회
get(componentType: string): ComponentRenderer | undefined {
return this.renderers.get(componentType);
}
// 등록된 모든 컴포넌트 타입 조회
getRegisteredTypes(): string[] {
return Array.from(this.renderers.keys());
}
// 컴포넌트 타입이 등록되어 있는지 확인
has(componentType: string): boolean {
const result = this.renderers.has(componentType);
return result;
}
}
// 전역 레거시 레지스트리 인스턴스
export const legacyComponentRegistry = new LegacyComponentRegistry();
// 하위 호환성을 위한 기존 이름 유지
export const componentRegistry = legacyComponentRegistry;
// 동적 컴포넌트 렌더러 컴포넌트
export interface DynamicComponentRendererProps {
component: ComponentData;
isSelected?: boolean;
onClick?: (e?: React.MouseEvent) => void;
onDragStart?: (e: React.DragEvent) => void;
onDragEnd?: () => void;
children?: React.ReactNode;
// 폼 데이터 관련
formData?: Record<string, any>;
originalData?: Record<string, any>; // 부분 업데이트용 원본 데이터
onFormDataChange?: (fieldName: string, value: any) => void;
// 버튼 액션을 위한 추가 props
screenId?: number;
tableName?: string;
onRefresh?: () => void;
onClose?: () => void;
// 편집 모드
mode?: "view" | "edit";
// 모달 내에서 렌더링 여부
isInModal?: boolean;
[key: string]: any;
}
export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> = ({
component,
isSelected = false,
onClick,
onDragStart,
onDragEnd,
children,
...props
}) => {
// 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용
const componentType = (component as any).componentType || component.type;
// 레이아웃 컴포넌트 처리
if (componentType === "layout") {
return (
<DynamicLayoutRenderer
layout={component as any}
allComponents={props.allComponents || []}
isSelected={isSelected}
onClick={onClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onUpdateLayout={props.onUpdateLayout}
// onComponentDrop 제거 - 일반 캔버스 드롭만 사용
onZoneClick={props.onZoneClick}
{...props}
/>
);
}
// 1. 새 컴포넌트 시스템에서 먼저 조회
const newComponent = ComponentRegistry.getComponent(componentType);
if (newComponent) {
// 새 컴포넌트 시스템으로 렌더링
try {
const NewComponentRenderer = newComponent.component;
if (NewComponentRenderer) {
// React 전용 props 필터링
const { isInteractive, formData, onFormDataChange, ...safeProps } = props;
return (
<NewComponentRenderer
{...safeProps}
component={component}
isSelected={isSelected}
onClick={onClick}
isInteractive={isInteractive}
formData={formData}
onFormDataChange={onFormDataChange}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
size={component.size || newComponent.defaultSize}
position={component.position}
style={component.style}
config={component.componentConfig}
componentConfig={component.componentConfig}
screenId={props.screenId}
tableName={props.tableName}
onRefresh={props.onRefresh}
onClose={props.onClose}
/>
);
}
} catch (error) {
console.error(`❌ 새 컴포넌트 렌더링 실패 (${componentType}):`, error);
}
}
// 2. 레거시 시스템에서 조회
const renderer = legacyComponentRegistry.get(componentType);
if (!renderer) {
console.error(`⚠️ 등록되지 않은 컴포넌트 타입: ${componentType}`, {
component: component,
componentType: componentType,
componentConfig: component.componentConfig,
availableNewComponents: ComponentRegistry.getAllComponents().map((c) => c.id),
availableLegacyComponents: legacyComponentRegistry.getRegisteredTypes(),
});
// 폴백 렌더링 - 기본 플레이스홀더
return (
<div className="flex h-full w-full items-center justify-center rounded border-2 border-dashed border-gray-300 bg-gray-50 p-4">
<div className="text-center">
<div className="mb-2 text-sm font-medium text-gray-600">{component.label || component.id}</div>
<div className="text-xs text-gray-400"> : {componentType}</div>
</div>
</div>
);
}
// 동적 렌더링 실행
try {
return renderer({
component,
isSelected,
onClick,
onDragStart,
onDragEnd,
children,
...props,
});
} catch (error) {
console.error(`❌ 컴포넌트 렌더링 실패 (${componentType}):`, error);
// 오류 발생 시 폴백 렌더링
return (
<div className="flex h-full w-full items-center justify-center rounded border-2 border-red-300 bg-red-50 p-4">
<div className="text-center">
<div className="mb-2 text-sm font-medium text-red-600"> </div>
<div className="text-xs text-red-400">
{componentType}: {error instanceof Error ? error.message : "알 수 없는 오류"}
</div>
</div>
</div>
);
}
};
export default DynamicComponentRenderer;