"use client"; import React from "react"; import { ComponentData, WebType, WidgetComponent, FileComponent, AreaComponent, AreaLayoutType } from "@/types/screen"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { FileUpload } from "./widgets/FileUpload"; import { useAuth } from "@/hooks/useAuth"; import { DynamicWebTypeRenderer, WebTypeRegistry } from "@/lib/registry"; import { Database, Type, Hash, List, AlignLeft, CheckSquare, Radio, Calendar, Code, Building, File, Group, ChevronDown, ChevronRight, Search, RotateCcw, Plus, Edit, Trash2, Upload, Square, CreditCard, Layout, Grid3x3, Columns, Rows, SidebarOpen, Folder, ChevronUp, } from "lucide-react"; interface RealtimePreviewProps { component: ComponentData; isSelected?: boolean; onClick?: (e?: React.MouseEvent) => void; onDragStart?: (e: React.DragEvent) => void; onDragEnd?: () => void; onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기 children?: React.ReactNode; // 그룹 내 자식 컴포넌트들 } // 영역 레이아웃에 따른 아이콘 반환 const getAreaIcon = (layoutType: AreaLayoutType) => { switch (layoutType) { case "flex-row": return ; case "grid": return ; case "flex-column": return ; case "panel": return ; case "sidebar": return ; case "tabs": return ; default: return ; } }; // 영역 렌더링 const renderArea = (component: ComponentData, children?: React.ReactNode) => { const area = component as AreaComponent; const { layoutType, title } = area; const renderPlaceholder = () => (
{getAreaIcon(layoutType)}

{title || `${layoutType} 영역`}

컴포넌트를 드래그해서 추가하세요

); return (
{children && React.Children.count(children) > 0 ? children : renderPlaceholder()}
); }; // 동적 웹 타입 위젯 렌더링 컴포넌트 const WidgetRenderer: React.FC<{ component: ComponentData }> = ({ component }) => { // 위젯 컴포넌트가 아닌 경우 빈 div 반환 if (component.type !== "widget") { return
위젯이 아닙니다
; } const widget = component as WidgetComponent; const { widgetType, label, placeholder, required, readonly, columnName, style } = widget; // 디버깅: 실제 widgetType 값 확인 console.log("RealtimePreviewDynamic - widgetType:", widgetType, "columnName:", columnName); // 사용자가 테두리를 설정했는지 확인 const hasCustomBorder = style && (style.borderWidth || style.borderStyle || style.borderColor || style.border); // 기본 테두리 제거 여부 결정 - Shadcn UI 기본 border 클래스를 덮어쓰기 const borderClass = hasCustomBorder ? "!border-0" : ""; const commonProps = { placeholder: placeholder || "입력하세요...", disabled: readonly, required: required, className: `w-full h-full ${borderClass}`, }; // 동적 웹타입 렌더링 사용 if (widgetType) { try { return ( ); } catch (error) { console.error(`웹타입 "${widgetType}" 렌더링 실패:`, error); // 오류 발생 시 폴백으로 기본 input 렌더링 return ; } } // 웹타입이 없는 경우 기본 input 렌더링 (하위 호환성) return ; }; // 동적 위젯 타입 아이콘 (레지스트리에서 조회) const getWidgetIcon = (widgetType: WebType | undefined) => { if (!widgetType) { return ; } // 레지스트리에서 웹타입 정의 조회 const webTypeDefinition = WebTypeRegistry.getWebType(widgetType); if (webTypeDefinition && webTypeDefinition.icon) { const IconComponent = webTypeDefinition.icon; return ; } // 기본 아이콘 매핑 (하위 호환성) switch (widgetType) { case "text": case "email": case "tel": return ; case "number": case "decimal": return ; case "date": case "datetime": return ; case "select": case "dropdown": return ; case "textarea": case "text_area": return ; case "boolean": case "checkbox": return ; case "radio": return ; case "code": return ; case "entity": return ; case "file": return ; default: return ; } }; export const RealtimePreviewDynamic: React.FC = ({ component, isSelected = false, onClick, onDragStart, onDragEnd, onGroupToggle, children, }) => { const { user } = useAuth(); const { type, id, position, size, style = {} } = component; // 컴포넌트 스타일 계산 const componentStyle = { position: "absolute" as const, left: position?.x || 0, top: position?.y || 0, width: size?.width || 200, height: size?.height || 40, zIndex: position?.z || 1, ...style, }; // 선택된 컴포넌트 스타일 const selectionStyle = isSelected ? { outline: "2px solid #3b82f6", outlineOffset: "2px", } : {}; const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); onClick?.(e); }; const handleDragStart = (e: React.DragEvent) => { e.stopPropagation(); onDragStart?.(e); }; const handleDragEnd = () => { onDragEnd?.(); }; return (
{/* 컴포넌트 타입별 렌더링 */}
{/* 영역 타입 */} {type === "area" && renderArea(component, children)} {/* 데이터 테이블 타입 */} {type === "datatable" && (() => { const dataTableComponent = component as any; // DataTableComponent 타입 // 메모이제이션 없이 직접 계산 const visibleColumns = dataTableComponent.columns?.filter((col: any) => col.visible) || []; const filters = dataTableComponent.filters || []; return (
{/* 테이블 제목 */} {dataTableComponent.title && (

{dataTableComponent.title}

)} {/* 검색 및 필터 영역 */} {(dataTableComponent.showSearchButton || filters.length > 0) && (
{dataTableComponent.showSearchButton && (
)} {filters.length > 0 && (
필터: {filters.slice(0, 2).map((filter: any, index: number) => ( {filter.label || filter.columnName} ))} {filters.length > 2 && ( +{filters.length - 2} )}
)}
)} {/* 테이블 본체 */}
{visibleColumns.length > 0 ? ( visibleColumns.map((col: any, index: number) => ( {col.label || col.columnName} {col.sortable && } )) ) : ( <> 컬럼 1 컬럼 2 컬럼 3 )} {/* 샘플 데이터 행들 */} {[1, 2, 3].map((rowIndex) => ( {visibleColumns.length > 0 ? ( visibleColumns.map((col: any, colIndex: number) => ( {col.widgetType === "checkbox" ? ( ) : col.widgetType === "select" ? ( `옵션 ${rowIndex}` ) : col.widgetType === "date" ? ( "2024-01-01" ) : col.widgetType === "number" ? ( `${rowIndex * 100}` ) : ( `데이터 ${rowIndex}-${colIndex + 1}` )} )) ) : ( <> 데이터 {rowIndex}-1 데이터 {rowIndex}-2 데이터 {rowIndex}-3 )} ))}
{/* 페이지네이션 */} {dataTableComponent.pagination && (
총 3개 항목
1 / 1
)}
); })()} {/* 그룹 타입 */} {type === "group" && (
{children}
)} {/* 위젯 타입 - 동적 렌더링 */} {type === "widget" && (
)} {/* 파일 타입 */} {type === "file" && (

파일 업로드 영역

미리보기 모드

)}
{/* 선택된 컴포넌트 정보 표시 */} {isSelected && (
{type === "widget" && (
{getWidgetIcon((component as WidgetComponent).widgetType)} {(component as WidgetComponent).widgetType || "widget"}
)} {type !== "widget" && type}
)}
); }; // 기존 RealtimePreview와의 호환성을 위한 export export { RealtimePreviewDynamic as RealtimePreview }; RealtimePreviewDynamic.displayName = "RealtimePreviewDynamic";