"use client"; import React, { useState } from "react"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CalendarIcon } from "lucide-react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { ComponentData, WidgetComponent, DataTableComponent, TextTypeConfig, NumberTypeConfig, DateTypeConfig, SelectTypeConfig, RadioTypeConfig, CheckboxTypeConfig, TextareaTypeConfig, FileTypeConfig, CodeTypeConfig, EntityTypeConfig, ButtonTypeConfig, } from "@/types/screen"; import { InteractiveDataTable } from "./InteractiveDataTable"; interface InteractiveScreenViewerProps { component: ComponentData; allComponents: ComponentData[]; formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; hideLabel?: boolean; } export const InteractiveScreenViewer: React.FC = ({ component, allComponents, formData: externalFormData, onFormDataChange, hideLabel = false, }) => { const [localFormData, setLocalFormData] = useState>({}); const [dateValues, setDateValues] = useState>({}); // 실제 사용할 폼 데이터 (외부에서 제공된 경우 우선 사용) const formData = externalFormData || localFormData; // 폼 데이터 업데이트 const updateFormData = (fieldName: string, value: any) => { if (onFormDataChange) { // 외부 콜백이 있는 경우 사용 onFormDataChange(fieldName, value); } else { // 로컬 상태 업데이트 setLocalFormData((prev) => ({ ...prev, [fieldName]: value, })); } }; // 날짜 값 업데이트 const updateDateValue = (fieldName: string, date: Date | undefined) => { setDateValues((prev) => ({ ...prev, [fieldName]: date, })); updateFormData(fieldName, date ? format(date, "yyyy-MM-dd") : ""); }; // 실제 사용 가능한 위젯 렌더링 const renderInteractiveWidget = (comp: ComponentData) => { // 데이터 테이블 컴포넌트 처리 if (comp.type === "datatable") { return ( ); } const { widgetType, label, placeholder, required, readonly, columnName } = comp; const fieldName = columnName || comp.id; const currentValue = formData[fieldName] || ""; // 스타일 적용 const applyStyles = (element: React.ReactElement) => { if (!comp.style) return element; return React.cloneElement(element, { style: { ...element.props.style, // 기존 스타일 유지 ...comp.style, // 크기는 부모 컨테이너에서 처리하므로 제거 (하지만 다른 스타일은 유지) width: "100%", height: "100%", minHeight: "100%", maxHeight: "100%", boxSizing: "border-box", }, }); }; switch (widgetType) { case "text": case "email": case "tel": { const widget = comp as WidgetComponent; const config = widget.webTypeConfig as TextTypeConfig | undefined; console.log("📝 InteractiveScreenViewer - Text 위젯:", { componentId: widget.id, widgetType: widget.widgetType, config, appliedSettings: { format: config?.format, minLength: config?.minLength, maxLength: config?.maxLength, pattern: config?.pattern, placeholder: config?.placeholder, }, }); // 형식별 패턴 생성 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)) { return; // 유효하지 않은 입력 차단 } } } // 길이 제한 검증 if (config?.maxLength && value.length > config.maxLength) { return; // 최대 길이 초과 차단 } updateFormData(fieldName, value); }; const finalPlaceholder = config?.placeholder || placeholder || "입력하세요..."; const inputType = widgetType === "email" ? "email" : widgetType === "tel" ? "tel" : "text"; return applyStyles( , ); } case "number": case "decimal": { const widget = comp as WidgetComponent; const config = widget.webTypeConfig as NumberTypeConfig | undefined; console.log("🔢 InteractiveScreenViewer - Number 위젯:", { componentId: widget.id, widgetType: widget.widgetType, config, appliedSettings: { format: config?.format, min: config?.min, max: config?.max, step: config?.step, decimalPlaces: config?.decimalPlaces, thousandSeparator: config?.thousandSeparator, prefix: config?.prefix, suffix: config?.suffix, }, }); const step = config?.step || (widgetType === "decimal" ? 0.01 : 1); const finalPlaceholder = config?.placeholder || placeholder || "숫자를 입력하세요..."; return applyStyles( updateFormData(fieldName, e.target.valueAsNumber || 0)} disabled={readonly} required={required} min={config?.min} max={config?.max} step={step} className="w-full" style={{ height: "100%" }} />, ); } case "textarea": case "text_area": { const widget = comp as WidgetComponent; const config = widget.webTypeConfig as TextareaTypeConfig | undefined; console.log("📄 InteractiveScreenViewer - Textarea 위젯:", { componentId: widget.id, widgetType: widget.widgetType, config, appliedSettings: { rows: config?.rows, maxLength: config?.maxLength, minLength: config?.minLength, placeholder: config?.placeholder, defaultValue: config?.defaultValue, resizable: config?.resizable, wordWrap: config?.wordWrap, }, }); const finalPlaceholder = config?.placeholder || placeholder || "내용을 입력하세요..."; const rows = config?.rows || 3; return applyStyles(