104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import React, { Suspense } from "react";
|
||
|
|
import { WebTypeRegistry } from "./WebTypeRegistry";
|
||
|
|
import { WebTypeComponentProps } from "./types";
|
||
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||
|
|
import { Loader2 } from "lucide-react";
|
||
|
|
|
||
|
|
interface DynamicWebTypeRendererProps extends WebTypeComponentProps {
|
||
|
|
widgetType: string;
|
||
|
|
fallback?: React.ComponentType<WebTypeComponentProps>;
|
||
|
|
showError?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 동적 웹타입 렌더러
|
||
|
|
* 등록된 웹타입에 따라 적절한 컴포넌트를 동적으로 렌더링
|
||
|
|
*/
|
||
|
|
export const DynamicWebTypeRenderer: React.FC<DynamicWebTypeRendererProps> =
|
||
|
|
React.memo(
|
||
|
|
({
|
||
|
|
widgetType,
|
||
|
|
component,
|
||
|
|
fallback: FallbackComponent,
|
||
|
|
showError = true,
|
||
|
|
...props
|
||
|
|
}) => {
|
||
|
|
// 웹타입 정의 조회
|
||
|
|
const definition = WebTypeRegistry.get(widgetType);
|
||
|
|
|
||
|
|
// 웹타입이 등록되지 않은 경우
|
||
|
|
if (!definition) {
|
||
|
|
console.warn(`Unknown web type: ${widgetType}`);
|
||
|
|
|
||
|
|
// Fallback 컴포넌트가 있으면 사용
|
||
|
|
if (FallbackComponent) {
|
||
|
|
return <FallbackComponent component={component} {...props} />;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 에러 표시를 원하지 않으면 빈 div 반환
|
||
|
|
if (!showError) {
|
||
|
|
return <div className="w-full h-full" />;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 에러 메시지 표시
|
||
|
|
return (
|
||
|
|
<Alert variant="destructive" className="w-full">
|
||
|
|
<AlertDescription>
|
||
|
|
알 수 없는 웹타입: <code>{widgetType}</code>
|
||
|
|
</AlertDescription>
|
||
|
|
</Alert>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 비활성화된 웹타입인 경우
|
||
|
|
if (definition.isActive === false) {
|
||
|
|
console.warn(`Inactive web type: ${widgetType}`);
|
||
|
|
|
||
|
|
if (showError) {
|
||
|
|
return (
|
||
|
|
<Alert variant="secondary" className="w-full">
|
||
|
|
<AlertDescription>
|
||
|
|
비활성화된 웹타입: <code>{widgetType}</code>
|
||
|
|
</AlertDescription>
|
||
|
|
</Alert>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return <div className="w-full h-full opacity-50" />;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 등록된 컴포넌트 렌더링
|
||
|
|
const Component = definition.component;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Suspense
|
||
|
|
fallback={
|
||
|
|
<div className="flex items-center justify-center w-full h-full p-2">
|
||
|
|
<Loader2 className="h-4 w-4 animate-spin text-gray-400" />
|
||
|
|
<span className="ml-2 text-xs text-gray-500">로딩 중...</span>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
>
|
||
|
|
<Component component={component} {...props} />
|
||
|
|
</Suspense>
|
||
|
|
);
|
||
|
|
},
|
||
|
|
(prevProps, nextProps) => {
|
||
|
|
// 메모이제이션을 위한 얕은 비교
|
||
|
|
return (
|
||
|
|
prevProps.widgetType === nextProps.widgetType &&
|
||
|
|
prevProps.component.id === nextProps.component.id &&
|
||
|
|
prevProps.value === nextProps.value &&
|
||
|
|
prevProps.readonly === nextProps.readonly &&
|
||
|
|
JSON.stringify(prevProps.component.webTypeConfig) ===
|
||
|
|
JSON.stringify(nextProps.component.webTypeConfig)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
DynamicWebTypeRenderer.displayName = "DynamicWebTypeRenderer";
|
||
|
|
|
||
|
|
|