136 lines
4.1 KiB
TypeScript
136 lines
4.1 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import React from "react";
|
||
|
|
import { ComponentData } from "@/types/screen";
|
||
|
|
|
||
|
|
// 컴포넌트 렌더러 인터페이스
|
||
|
|
export interface ComponentRenderer {
|
||
|
|
(props: {
|
||
|
|
component: ComponentData;
|
||
|
|
isSelected?: boolean;
|
||
|
|
isInteractive?: boolean;
|
||
|
|
formData?: Record<string, any>;
|
||
|
|
onFormDataChange?: (fieldName: string, value: any) => void;
|
||
|
|
onClick?: (e?: React.MouseEvent) => void;
|
||
|
|
onDragStart?: (e: React.DragEvent) => void;
|
||
|
|
onDragEnd?: () => void;
|
||
|
|
children?: React.ReactNode;
|
||
|
|
[key: string]: any;
|
||
|
|
}): React.ReactElement;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 컴포넌트 레지스트리
|
||
|
|
class ComponentRegistry {
|
||
|
|
private renderers: Map<string, ComponentRenderer> = new Map();
|
||
|
|
|
||
|
|
// 컴포넌트 렌더러 등록
|
||
|
|
register(componentType: string, renderer: ComponentRenderer) {
|
||
|
|
this.renderers.set(componentType, renderer);
|
||
|
|
console.log(`🔧 컴포넌트 렌더러 등록: ${componentType}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 컴포넌트 렌더러 조회
|
||
|
|
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);
|
||
|
|
console.log(`🔍 ComponentRegistry.has("${componentType}"):`, {
|
||
|
|
result,
|
||
|
|
availableKeys: Array.from(this.renderers.keys()),
|
||
|
|
mapSize: this.renderers.size,
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 전역 컴포넌트 레지스트리 인스턴스
|
||
|
|
export const componentRegistry = new ComponentRegistry();
|
||
|
|
|
||
|
|
// 동적 컴포넌트 렌더러 컴포넌트
|
||
|
|
export interface DynamicComponentRendererProps {
|
||
|
|
component: ComponentData;
|
||
|
|
isSelected?: boolean;
|
||
|
|
onClick?: (e?: React.MouseEvent) => void;
|
||
|
|
onDragStart?: (e: React.DragEvent) => void;
|
||
|
|
onDragEnd?: () => void;
|
||
|
|
children?: React.ReactNode;
|
||
|
|
[key: string]: any;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> = ({
|
||
|
|
component,
|
||
|
|
isSelected = false,
|
||
|
|
onClick,
|
||
|
|
onDragStart,
|
||
|
|
onDragEnd,
|
||
|
|
children,
|
||
|
|
...props
|
||
|
|
}) => {
|
||
|
|
// component_config에서 실제 컴포넌트 타입 추출
|
||
|
|
const componentType = component.componentConfig?.type || component.type;
|
||
|
|
|
||
|
|
console.log("🎯 DynamicComponentRenderer:", {
|
||
|
|
componentId: component.id,
|
||
|
|
componentType,
|
||
|
|
componentConfig: component.componentConfig,
|
||
|
|
registeredTypes: componentRegistry.getRegisteredTypes(),
|
||
|
|
hasRenderer: componentRegistry.has(componentType),
|
||
|
|
actualRenderer: componentRegistry.get(componentType),
|
||
|
|
mapSize: componentRegistry.getRegisteredTypes().length,
|
||
|
|
});
|
||
|
|
|
||
|
|
// 등록된 렌더러 조회
|
||
|
|
const renderer = componentRegistry.get(componentType);
|
||
|
|
|
||
|
|
if (!renderer) {
|
||
|
|
console.warn(`⚠️ 등록되지 않은 컴포넌트 타입: ${componentType}`);
|
||
|
|
|
||
|
|
// 폴백 렌더링 - 기본 플레이스홀더
|
||
|
|
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;
|