"use client"; import React, { forwardRef } from "react"; import { cn } from "@/lib/utils"; import { PopComponentDefinition, PopComponentType, GridPosition, } from "../types/pop-layout"; // ======================================== // Props 정의 // ======================================== interface ComponentRendererProps { /** 컴포넌트 정의 (타입, 라벨, 설정 등) */ component: PopComponentDefinition; /** 컴포넌트의 그리드 위치 (섹션 내부 그리드 기준) */ position: GridPosition; /** 디자인 모드 여부 (true: 편집 가능, false: 뷰어 모드) */ isDesignMode?: boolean; /** 선택된 상태인지 */ isSelected?: boolean; /** 컴포넌트 클릭 시 호출 */ onClick?: () => void; /** 추가 className */ className?: string; } // ======================================== // 컴포넌트 렌더러 // // 역할: // - 관리자가 설정한 GridPosition(col, row, colSpan, rowSpan)을 // 그대로 CSS Grid에 반영 // - 디자이너/뷰어 모두에서 동일한 렌더링 보장 // - 디자인 모드에서는 선택 상태 표시 // ======================================== export const ComponentRenderer = forwardRef( function ComponentRenderer( { component, position, isDesignMode = false, isSelected = false, onClick, className, }, ref ) { const { type, label, config } = component; return (
{ e.stopPropagation(); onClick?.(); }} > {/* 컴포넌트 타입별 미리보기 렌더링 */}
); } ); // ======================================== // 컴포넌트 타입별 미리보기 // ======================================== interface ComponentPreviewProps { type: PopComponentType; label?: string; config?: any; } function ComponentPreview({ type, label, config }: ComponentPreviewProps) { switch (type) { case "pop-field": return ; case "pop-button": return ; case "pop-list": return ; case "pop-indicator": return ; case "pop-scanner": return ; case "pop-numpad": return ; default: return (
{label || type}
); } } // ======================================== // 개별 컴포넌트 미리보기 // ======================================== function FieldPreview({ label, config }: { label?: string; config?: any }) { const fieldType = config?.fieldType || "text"; const placeholder = config?.placeholder || "입력하세요"; const required = config?.required || false; return (
{/* 라벨 */} {label || "필드"} {required && *} {/* 입력 필드 미리보기 */}
{placeholder}
); } function ButtonPreview({ label, config }: { label?: string; config?: any }) { const buttonType = config?.buttonType || "action"; const variant = buttonType === "submit" ? "bg-primary text-white" : "bg-gray-100 text-gray-700"; return (
{label || "버튼"}
); } function ListPreview({ label, config }: { label?: string; config?: any }) { const itemCount = config?.itemsPerPage || 5; return (
{/* 라벨 */} {label || "리스트"} {/* 리스트 아이템 미리보기 */}
{Array.from({ length: Math.min(3, itemCount) }).map((_, i) => (
))} {itemCount > 3 && (
+{itemCount - 3} more
)}
); } function IndicatorPreview({ label, config }: { label?: string; config?: any }) { const indicatorType = config?.indicatorType || "kpi"; const unit = config?.unit || ""; return (
{/* 라벨 */} {label || "KPI"} {/* 값 미리보기 */} 0{unit && {unit}}
); } function ScannerPreview({ label, config }: { label?: string; config?: any }) { const scannerType = config?.scannerType || "camera"; return (
{/* QR 아이콘 */}
QR
{/* 라벨 */} {label || "스캐너"}
); } function NumpadPreview({ label, config }: { label?: string; config?: any }) { const keys = [1, 2, 3, 4, 5, 6, 7, 8, 9, "C", 0, "OK"]; return (
{/* 라벨 */} {label && ( {label} )} {/* 넘패드 미리보기 */}
{keys.map((key) => (
{key}
))}
); } export default ComponentRenderer;