"use client"; import React from "react"; import { ComponentData, WebType, WidgetComponent, FileComponent, AreaComponent, AreaLayoutType, DateTypeConfig, NumberTypeConfig, SelectTypeConfig, TextTypeConfig, TextareaTypeConfig, CheckboxTypeConfig, RadioTypeConfig, FileTypeConfig, CodeTypeConfig, EntityTypeConfig, } from "@/types/screen"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; 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 { Checkbox } from "@/components/ui/checkbox"; // import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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 "box": return ; case "card": return ; case "panel": return ; case "section": return ; case "grid": return ; case "flex-row": return ; case "flex-column": return ; case "sidebar": return ; case "header-content": return ; case "tabs": return ; case "accordion": return ; default: return ; } }; // 영역 컴포넌트 렌더링 const renderArea = (component: AreaComponent, children?: React.ReactNode) => { const { layoutType, title, description, layoutConfig, areaStyle } = component; // 기본 스타일 const baseStyle: React.CSSProperties = { width: "100%", height: "100%", position: "relative", backgroundColor: areaStyle?.backgroundColor || "#ffffff", border: areaStyle?.borderWidth ? `${areaStyle.borderWidth}px ${areaStyle.borderStyle || "solid"} ${areaStyle.borderColor || "#e5e7eb"}` : "1px solid #e5e7eb", borderRadius: `${areaStyle?.borderRadius || 8}px`, padding: `${areaStyle?.padding || 16}px`, margin: `${areaStyle?.margin || 0}px`, ...(areaStyle?.shadow && areaStyle.shadow !== "none" && { boxShadow: { sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)", md: "0 4px 6px -1px rgba(0, 0, 0, 0.1)", lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1)", xl: "0 20px 25px -5px rgba(0, 0, 0, 0.1)", }[areaStyle.shadow] || "0 4px 6px -1px rgba(0, 0, 0, 0.1)", }), }; // 레이아웃별 컨테이너 스타일 const getLayoutStyle = (): React.CSSProperties => { switch (layoutType) { case "grid": return { display: "grid", gridTemplateColumns: `repeat(${layoutConfig?.gridColumns || 3}, 1fr)`, gridTemplateRows: layoutConfig?.gridRows ? `repeat(${layoutConfig.gridRows}, 1fr)` : "auto", gap: `${layoutConfig?.gridGap || 16}px`, }; case "flex-row": return { display: "flex", flexDirection: "row", justifyContent: layoutConfig?.justifyContent || "flex-start", alignItems: layoutConfig?.alignItems || "stretch", gap: `${layoutConfig?.gap || 16}px`, flexWrap: layoutConfig?.flexWrap || "nowrap", }; case "flex-column": return { display: "flex", flexDirection: "column", justifyContent: layoutConfig?.justifyContent || "flex-start", alignItems: layoutConfig?.alignItems || "stretch", gap: `${layoutConfig?.gap || 16}px`, }; case "sidebar": return { display: "flex", flexDirection: layoutConfig?.sidebarPosition === "right" ? "row-reverse" : "row", }; default: return {}; } }; // 헤더 렌더링 (panel, section 타입용) const renderHeader = () => { if (!title || (layoutType !== "panel" && layoutType !== "section")) return null; const headerStyle: React.CSSProperties = { backgroundColor: areaStyle?.headerBackgroundColor || "#f3f4f6", color: areaStyle?.headerTextColor || "#374151", height: `${areaStyle?.headerHeight || 48}px`, padding: `${areaStyle?.headerPadding || 16}px`, borderBottom: layoutType === "panel" ? "1px solid #e5e7eb" : "none", borderTopLeftRadius: `${areaStyle?.borderRadius || 8}px`, borderTopRightRadius: `${areaStyle?.borderRadius || 8}px`, display: "flex", alignItems: "center", fontWeight: "600", fontSize: "14px", }; return (
{title} {description && {description}}
); }; // 컨텐츠 영역 스타일 const contentStyle: React.CSSProperties = { ...getLayoutStyle(), flex: 1, minHeight: 0, }; // 자식 컴포넌트가 없을 때 표시할 플레이스홀더 const renderPlaceholder = () => (
{getAreaIcon(layoutType)}
{title || `${layoutType} 영역`}
{description || "컴포넌트를 이 영역에 드래그하세요"}
); return (
{renderHeader()}
{children && React.Children.count(children) > 0 ? children : renderPlaceholder()}
); }; // 웹 타입에 따른 위젯 렌더링 const renderWidget = (component: ComponentData) => { // 위젯 컴포넌트가 아닌 경우 빈 div 반환 if (component.type !== "widget") { return
위젯이 아닙니다
; } const widget = component as WidgetComponent; const { widgetType, label, placeholder, required, readonly, columnName, style } = widget; // 디버깅: 실제 widgetType 값 확인 console.log("RealtimePreview - 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}`, }; switch (widgetType) { case "text": case "email": case "tel": { const config = widget.webTypeConfig as TextTypeConfig | undefined; // 입력 타입에 따른 처리 const isAutoInput = widget.inputType === "auto"; // 자동 값 생성 함수 const getAutoValue = (autoValueType: string) => { switch (autoValueType) { case "current_datetime": return new Date().toLocaleString("ko-KR"); case "current_date": return new Date().toLocaleDateString("ko-KR"); case "current_time": return new Date().toLocaleTimeString("ko-KR"); case "current_user": return "현재사용자"; case "uuid": return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); case "sequence": return "SEQ_001"; case "user_defined": return "사용자정의값"; default: return "자동생성값"; } }; // 자동 값 플레이스홀더 생성 함수 const getAutoPlaceholder = (autoValueType: string) => { switch (autoValueType) { case "current_datetime": return "현재 날짜시간"; case "current_date": return "현재 날짜"; case "current_time": return "현재 시간"; case "current_user": return "현재 사용자"; case "uuid": return "UUID"; case "sequence": return "시퀀스"; case "user_defined": return "사용자 정의"; default: return "자동 생성됨"; } }; // 플레이스홀더 처리 const finalPlaceholder = isAutoInput ? getAutoPlaceholder(widget.autoValueType || "current_datetime") : config?.placeholder || placeholder || "텍스트를 입력하세요"; // 자동 값 처리 const autoValue = isAutoInput ? getAutoValue(widget.autoValueType || "current_datetime") : ""; const inputType = widgetType === "email" ? "email" : widgetType === "tel" ? "tel" : "text"; // 형식별 패턴 생성 const getPatternByFormat = (format: string) => { switch (format) { case "korean": return "[가-힣\\s]*"; case "english": return "[a-zA-Z\\s]*"; case "alphanumeric": return "[a-zA-Z0-9]*"; case "numeric": return "[0-9]*"; case "email": return "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"; case "phone": return "\\d{3}-\\d{4}-\\d{4}"; case "url": return "https?://[\\w\\-]+(\\.[\\w\\-]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?"; default: return config?.pattern || undefined; } }; // 입력 검증 함수 const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value; // 형식별 실시간 검증 if (config?.format && config.format !== "none") { const pattern = getPatternByFormat(config.format); if (pattern) { const regex = new RegExp(`^${pattern}$`); if (value && !regex.test(value)) { // 유효하지 않은 입력은 무시 e.preventDefault(); return; } } } // 길이 제한 검증 if (config?.maxLength && value.length > config.maxLength) { e.preventDefault(); return; } }; const inputProps = { ...commonProps, placeholder: finalPlaceholder, value: isAutoInput ? autoValue : undefined, // 자동입력인 경우 자동 값 표시 minLength: config?.minLength, maxLength: config?.maxLength, pattern: getPatternByFormat(config?.format || "none"), onChange: () => {}, // 읽기 전용으로 처리 readOnly: readonly || isAutoInput, // 자동입력인 경우 읽기 전용 className: `w-full h-full ${borderClass} ${isAutoInput ? "bg-gray-50 text-gray-600" : ""}`, }; // multiline이면 Textarea로 렌더링 if (config?.multiline) { return