"use client"; import React, { useState, useCallback } 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 { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { CalendarIcon, File, Upload, X } from "lucide-react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { useAuth } from "@/hooks/useAuth"; import { uploadFilesAndCreateData } from "@/lib/api/file"; import { toast } from "sonner"; import { ComponentData, WidgetComponent, DataTableComponent, FileComponent, TextTypeConfig, NumberTypeConfig, DateTypeConfig, SelectTypeConfig, RadioTypeConfig, CheckboxTypeConfig, TextareaTypeConfig, FileTypeConfig, CodeTypeConfig, EntityTypeConfig, ButtonTypeConfig, } from "@/types"; import { InteractiveDataTable } from "./InteractiveDataTable"; import { FileUpload } from "./widgets/FileUpload"; import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm"; import { useParams } from "next/navigation"; import { screenApi, tableTypeApi } from "@/lib/api/screen"; import { DynamicWebTypeRenderer } from "@/lib/registry/DynamicWebTypeRenderer"; import { enhancedFormService } from "@/lib/services/enhancedFormService"; import { FormValidationIndicator } from "@/components/common/FormValidationIndicator"; import { useFormValidation } from "@/hooks/useFormValidation"; import { UnifiedColumnInfo as ColumnInfo } from "@/types"; import { isFileComponent } from "@/lib/utils/componentTypeUtils"; import { buildGridClasses } from "@/lib/constants/columnSpans"; import { cn } from "@/lib/utils"; import { useScreenPreview } from "@/contexts/ScreenPreviewContext"; import { TableOptionsProvider } from "@/contexts/TableOptionsContext"; import { TableOptionsToolbar } from "./table-options/TableOptionsToolbar"; interface InteractiveScreenViewerProps { component: ComponentData; allComponents: ComponentData[]; formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; hideLabel?: boolean; screenInfo?: { id: number; tableName?: string; }; menuObjid?: number; // ๐Ÿ†• ๋ฉ”๋‰ด OBJID (์ฝ”๋“œ ์Šค์ฝ”ํ”„์šฉ) // ์ƒˆ๋กœ์šด ๊ฒ€์ฆ ๊ด€๋ จ ์˜ต์…˜๋“ค enableEnhancedValidation?: boolean; tableColumns?: ColumnInfo[]; showValidationPanel?: boolean; validationOptions?: { enableRealTimeValidation?: boolean; validationDelay?: number; enableAutoSave?: boolean; showToastMessages?: boolean; }; } export const InteractiveScreenViewer: React.FC = ({ component, allComponents, formData: externalFormData, onFormDataChange, hideLabel = false, screenInfo, menuObjid, // ๐Ÿ†• ๋ฉ”๋‰ด OBJID enableEnhancedValidation = false, tableColumns = [], showValidationPanel = false, validationOptions = {}, }) => { // component๊ฐ€ ์—†์œผ๋ฉด ๋นˆ div ๋ฐ˜ํ™˜ if (!component) { console.warn("โš ๏ธ InteractiveScreenViewer: component๊ฐ€ undefined์ž…๋‹ˆ๋‹ค."); return
; } const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ const { userName, user } = useAuth(); // ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ช…๊ณผ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ const [localFormData, setLocalFormData] = useState>({}); const [dateValues, setDateValues] = useState>({}); // ํŒ์—… ํ™”๋ฉด ์ƒํƒœ const [popupScreen, setPopupScreen] = useState<{ screenId: number; title: string; size: string; } | null>(null); // ํŒ์—… ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์ƒํƒœ const [popupLayout, setPopupLayout] = useState([]); const [popupLoading, setPopupLoading] = useState(false); const [popupScreenResolution, setPopupScreenResolution] = useState<{ width: number; height: number } | null>(null); const [popupScreenInfo, setPopupScreenInfo] = useState<{ id: number; tableName?: string } | null>(null); // ํŒ์—… ์ „์šฉ formData ์ƒํƒœ const [popupFormData, setPopupFormData] = useState>({}); // ํ†ตํ•ฉ๋œ ํผ ๋ฐ์ดํ„ฐ const finalFormData = { ...localFormData, ...externalFormData }; // ๊ฐœ์„ ๋œ ๊ฒ€์ฆ ์‹œ์Šคํ…œ (์„ ํƒ์  ํ™œ์„ฑํ™”) const enhancedValidation = enableEnhancedValidation && screenInfo && tableColumns.length > 0 ? useFormValidation( finalFormData, allComponents.filter(c => c.type === 'widget') as WidgetComponent[], tableColumns, { id: screenInfo.id, screenName: screenInfo.tableName || "unknown", tableName: screenInfo.tableName, screenResolution: { width: 800, height: 600 }, gridSettings: { size: 20, color: "#e0e0e0", opacity: 0.5 }, description: "๋™์  ํ™”๋ฉด" }, { enableRealTimeValidation: true, validationDelay: 300, enableAutoSave: false, showToastMessages: true, ...validationOptions, } ) : null; // ์ž๋™๊ฐ’ ์ƒ์„ฑ ํ•จ์ˆ˜ const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise => { const now = new Date(); switch (autoValueType) { case "current_datetime": return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss case "current_date": return now.toISOString().slice(0, 10); // YYYY-MM-DD case "current_time": return now.toTimeString().slice(0, 8); // HH:mm:ss case "current_user": // ์‹ค์ œ ์ ‘์†์ค‘์ธ ์‚ฌ์šฉ์ž๋ช… ์‚ฌ์šฉ return userName || "์‚ฌ์šฉ์ž"; // ์‚ฌ์šฉ์ž๋ช…์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ case "uuid": return crypto.randomUUID(); case "sequence": return `SEQ_${Date.now()}`; case "numbering_rule": // ์ฑ„๋ฒˆ ๊ทœ์น™ ์‚ฌ์šฉ if (ruleId) { try { const { generateNumberingCode } = await import("@/lib/api/numberingRule"); const response = await generateNumberingCode(ruleId); if (response.success && response.data) { return response.data.generatedCode; } } catch (error) { console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ:", error); } } return ""; default: return ""; } }, [userName]); // userName ์˜์กด์„ฑ ์ถ”๊ฐ€ // ํŒ์—… ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ๋กœ๋“œ React.useEffect(() => { if (popupScreen) { const loadPopupLayout = async () => { try { setPopupLoading(true); // console.log("๐Ÿ” ํŒ์—… ํ™”๋ฉด ๋กœ๋“œ ์‹œ์ž‘:", popupScreen); // ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ๊ณผ ํ™”๋ฉด ์ •๋ณด๋ฅผ ๋ณ‘๋ ฌ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ const [layout, screen] = await Promise.all([ screenApi.getLayout(popupScreen.screenId), screenApi.getScreen(popupScreen.screenId) ]); console.log("๐Ÿ“Š ํŒ์—… ํ™”๋ฉด ๋กœ๋“œ ์™„๋ฃŒ:", { componentsCount: layout.components?.length || 0, screenInfo: { screenId: screen.screenId, tableName: screen.tableName }, popupFormData: {} }); setPopupLayout(layout.components || []); setPopupScreenResolution(layout.screenResolution || null); setPopupScreenInfo({ id: popupScreen.screenId, tableName: screen.tableName }); // ํŒ์—… formData ์ดˆ๊ธฐํ™” setPopupFormData({}); } catch (error) { // console.error("โŒ ํŒ์—… ํ™”๋ฉด ๋กœ๋“œ ์‹คํŒจ:", error); setPopupLayout([]); setPopupScreenInfo(null); } finally { setPopupLoading(false); } }; loadPopupLayout(); } }, [popupScreen]); // ์‹ค์ œ ์‚ฌ์šฉํ•  ํผ ๋ฐ์ดํ„ฐ (์™ธ๋ถ€์™€ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ) const formData = { ...localFormData, ...externalFormData }; console.log("๐Ÿ”„ formData ๊ตฌ์„ฑ:", { external: externalFormData, local: localFormData, merged: formData, hasExternalCallback: !!onFormDataChange }); // ํผ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ const updateFormData = (fieldName: string, value: any) => { // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ํ•˜์ง€ ์•Š์Œ if (isPreviewMode) { return; } // console.log(`๐Ÿ”„ updateFormData: ${fieldName} = "${value}" (์™ธ๋ถ€์ฝœ๋ฐฑ: ${!!onFormDataChange})`); // ํ•ญ์ƒ ๋กœ์ปฌ ์ƒํƒœ๋„ ์—…๋ฐ์ดํŠธ setLocalFormData((prev) => ({ ...prev, [fieldName]: value, })); // console.log(`๐Ÿ’พ ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ: ${fieldName} = "${value}"`); // ์™ธ๋ถ€ ์ฝœ๋ฐฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋„ ์ „๋‹ฌ (๊ฐœ๋ณ„ ํ•„๋“œ ๋‹จ์œ„๋กœ) if (onFormDataChange) { onFormDataChange(fieldName, value); // console.log(`๐Ÿ“ค ์™ธ๋ถ€ ์ฝœ๋ฐฑ์œผ๋กœ ์ „๋‹ฌ: ${fieldName} = "${value}"`); } }; // ์ž๋™์ž…๋ ฅ ํ•„๋“œ๋“ค์˜ ๊ฐ’์„ formData์— ์ดˆ๊ธฐ ์„ค์ • React.useEffect(() => { // console.log("๐Ÿš€ ์ž๋™์ž…๋ ฅ ์ดˆ๊ธฐํ™” useEffect ์‹คํ–‰ - allComponents ๊ฐœ์ˆ˜:", allComponents.length); const initAutoInputFields = async () => { // console.log("๐Ÿ”ง initAutoInputFields ์‹คํ–‰ ์‹œ์ž‘"); for (const comp of allComponents) { // ๐Ÿ†• type: "component" ๋˜๋Š” type: "widget" ๋ชจ๋‘ ์ฒ˜๋ฆฌ if (comp.type === 'widget' || comp.type === 'component') { const widget = comp as WidgetComponent; const fieldName = widget.columnName || widget.id; // ๐Ÿ†• autoFill ์ฒ˜๋ฆฌ (ํ…Œ์ด๋ธ” ์กฐํšŒ ๊ธฐ๋ฐ˜ ์ž๋™ ์ž…๋ ฅ) if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { const autoFillConfig = widget.autoFill || (comp as any).autoFill; const currentValue = formData[fieldName]; if (currentValue === undefined || currentValue === '') { const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; // ์‚ฌ์šฉ์ž ์ •๋ณด์—์„œ ํ•„ํ„ฐ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ const userValue = user?.[userField]; if (userValue && sourceTable && filterColumn && displayColumn) { try { const result = await tableTypeApi.getTableRecord( sourceTable, filterColumn, userValue, displayColumn ); updateFormData(fieldName, result.value); } catch (error) { console.error(`autoFill ์กฐํšŒ ์‹คํŒจ: ${fieldName}`, error); } } } continue; // autoFill์ด ํ™œ์„ฑํ™”๋˜๋ฉด ์ผ๋ฐ˜ ์ž๋™์ž…๋ ฅ์€ ๊ฑด๋„ˆ๋œ€ } // ๊ธฐ์กด widget ํƒ€์ž… ์ „์šฉ ๋กœ์ง์€ widget์ธ ๊ฒฝ์šฐ๋งŒ if (comp.type !== 'widget') continue; // ํ…์ŠคํŠธ ํƒ€์ž… ์œ„์ ฏ์˜ ์ž๋™์ž…๋ ฅ ์ฒ˜๋ฆฌ (๊ธฐ์กด ๋กœ์ง) if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') && widget.webTypeConfig) { const config = widget.webTypeConfig as TextTypeConfig; const isAutoInput = config?.autoInput || false; if (isAutoInput && config?.autoValueType) { // ์ด๋ฏธ ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ const currentValue = formData[fieldName]; console.log(`๐Ÿ” ์ž๋™์ž…๋ ฅ ํ•„๋“œ ์ฒดํฌ: ${fieldName}`, { currentValue, isEmpty: currentValue === undefined || currentValue === '', isAutoInput, autoValueType: config.autoValueType }); if (currentValue === undefined || currentValue === '') { const autoValue = config.autoValueType === "custom" ? config.customValue || "" : generateAutoValue(config.autoValueType); console.log("๐Ÿ”„ ์ž๋™์ž…๋ ฅ ํ•„๋“œ ์ดˆ๊ธฐํ™”:", { fieldName, autoValueType: config.autoValueType, autoValue }); updateFormData(fieldName, autoValue); } else { // console.log(`โญ๏ธ ์ž๋™์ž…๋ ฅ ๊ฑด๋„ˆ๋œ€ (๊ฐ’ ์žˆ์Œ): ${fieldName} = "${currentValue}"`); } } } } } }; // ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ์ž๋™์ž…๋ ฅ ํ•„๋“œ๋“ค ์„ค์ • initAutoInputFields(); }, [allComponents, generateAutoValue, user]); // formData๋Š” ์˜์กด์„ฑ์—์„œ ์ œ์™ธ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€) // ๋‚ ์งœ ๊ฐ’ ์—…๋ฐ์ดํŠธ const updateDateValue = (fieldName: string, date: Date | undefined) => { setDateValues((prev) => ({ ...prev, [fieldName]: date, })); updateFormData(fieldName, date ? format(date, "yyyy-MM-dd") : ""); }; // ์‹ค์ œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์œ„์ ฏ ๋ Œ๋”๋ง const renderInteractiveWidget = (comp: ComponentData) => { console.log("๐ŸŽฏ renderInteractiveWidget ํ˜ธ์ถœ:", { type: comp.type, id: comp.id, componentId: (comp as any).componentId, hasComponentConfig: !!(comp as any).componentConfig, componentConfig: (comp as any).componentConfig, }); // ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ if (comp.type === "datatable") { return ( { // ํ…Œ์ด๋ธ” ๋‚ด๋ถ€์—์„œ loadData ํ˜ธ์ถœํ•˜๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ๋Š” ๋นˆ ํ•จ์ˆ˜ console.log("๐Ÿ”„ InteractiveDataTable ์ƒˆ๋กœ๊ณ ์นจ ํŠธ๋ฆฌ๊ฑฐ๋จ"); }} /> ); } // ํ”Œ๋กœ์šฐ ์œ„์ ฏ ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ if (comp.type === "flow" || (comp.type === "component" && (comp as any).componentConfig?.type === "flow-widget")) { const FlowWidget = require("@/components/screen/widgets/FlowWidget").FlowWidget; // componentConfig์—์„œ flowId ์ถ”์ถœ const flowConfig = (comp as any).componentConfig || {}; console.log("๐Ÿ” InteractiveScreenViewer ํ”Œ๋กœ์šฐ ์œ„์ ฏ ๋ณ€ํ™˜:", { compType: comp.type, hasComponentConfig: !!(comp as any).componentConfig, flowConfig, flowConfigFlowId: flowConfig.flowId, finalFlowId: flowConfig.flowId, }); const flowComponent = { ...comp, type: "flow" as const, flowId: flowConfig.flowId, flowName: flowConfig.flowName, showStepCount: flowConfig.showStepCount !== false, allowDataMove: flowConfig.allowDataMove || false, displayMode: flowConfig.displayMode || "horizontal", }; console.log("๐Ÿ” InteractiveScreenViewer ์ตœ์ข… flowComponent:", flowComponent); return (
); } // ํƒญ ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ const componentType = (comp as any).componentType || (comp as any).componentId; if (comp.type === "tabs" || (comp.type === "component" && componentType === "tabs-widget")) { const TabsWidget = require("@/components/screen/widgets/TabsWidget").TabsWidget; // componentConfig์—์„œ ํƒญ ์ •๋ณด ์ถ”์ถœ const tabsConfig = comp.componentConfig || {}; const tabsComponent = { ...comp, type: "tabs" as const, tabs: tabsConfig.tabs || [], defaultTab: tabsConfig.defaultTab, orientation: tabsConfig.orientation || "horizontal", variant: tabsConfig.variant || "default", allowCloseable: tabsConfig.allowCloseable || false, persistSelection: tabsConfig.persistSelection || false, }; console.log("๐Ÿ” ํƒญ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:", { originalType: comp.type, componentType, componentId: (comp as any).componentId, tabs: tabsComponent.tabs, tabsConfig, }); return (
); } // ๐Ÿ†• ๋ ‰ ๊ตฌ์กฐ ์ปดํฌ๋„ŒํŠธ ์ฒ˜๋ฆฌ if (comp.type === "component" && componentType === "rack-structure") { const { RackStructureComponent } = require("@/lib/registry/components/rack-structure/RackStructureComponent"); const componentConfig = (comp as any).componentConfig || {}; // config๊ฐ€ ์ค‘์ฒฉ๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Œ: componentConfig.config ๋˜๋Š” componentConfig ์ง์ ‘ const rackConfig = componentConfig.config || componentConfig; console.log("๐Ÿ—๏ธ ๋ ‰ ๊ตฌ์กฐ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง:", { componentType, componentConfig, rackConfig, fieldMapping: rackConfig.fieldMapping, formData, }); return (
{ console.log("๐Ÿ“ฆ ๋ ‰ ๊ตฌ์กฐ ์œ„์น˜ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ:", locations.length, "๊ฐœ"); // ์ปดํฌ๋„ŒํŠธ์˜ columnName์„ ํ‚ค๋กœ ์‚ฌ์šฉ const fieldKey = (comp as any).columnName || "_rackStructureLocations"; updateFormData(fieldKey, locations); }} isPreview={false} />
); } 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; // โœ… ๊ฒฉ์ž ์‹œ์Šคํ…œ ์ž”์žฌ ์ œ๊ฑฐ: style.width, style.height๋Š” ๋ฌด์‹œ // size.width, size.height๊ฐ€ ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ ์šฉ๋˜๋ฏ€๋กœ const { width, height, ...styleWithoutSize } = comp.style; return React.cloneElement(element, { style: { ...element.props.style, // ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ ...styleWithoutSize, // width/height ์ œ์™ธํ•œ ์Šคํƒ€์ผ๋งŒ ์ ์šฉ boxSizing: "border-box", }, }); }; switch (widgetType) { case "text": case "email": case "tel": { const widget = comp as WidgetComponent; const config = widget.webTypeConfig as TextTypeConfig | undefined; // ์ž๋™์ž…๋ ฅ ๊ด€๋ จ ์ฒ˜๋ฆฌ const isAutoInput = config?.autoInput || false; const autoValue = isAutoInput && config?.autoValueType ? config.autoValueType === "custom" ? config.customValue || "" : generateAutoValue(config.autoValueType) : ""; // ๊ธฐ๋ณธ๊ฐ’ ๋˜๋Š” ์ž๋™๊ฐ’ ์„ค์ • const displayValue = isAutoInput ? autoValue : currentValue || config?.defaultValue || ""; 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, defaultValue: config?.defaultValue, autoInput: isAutoInput, autoValueType: config?.autoValueType, autoValue, displayValue, }, }); // ํ˜•์‹๋ณ„ ํŒจํ„ด ์ƒ์„ฑ 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; // console.log(`๐Ÿ“ ์ž…๋ ฅ ๋ณ€๊ฒฝ: ${fieldName} = "${value}"`); // ํ˜•์‹๋ณ„ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ฆ if (config?.format && config.format !== "none") { const pattern = getPatternByFormat(config.format); if (pattern) { const regex = new RegExp(`^${pattern}$`); if (value && !regex.test(value)) { // console.log(`โŒ ํ˜•์‹ ๊ฒ€์ฆ ์‹คํŒจ: ${fieldName} = "${value}"`); return; // ์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ ์ฐจ๋‹จ } } } // ๊ธธ์ด ์ œํ•œ ๊ฒ€์ฆ if (config?.maxLength && value.length > config.maxLength) { // console.log(`โŒ ๊ธธ์ด ์ œํ•œ ์ดˆ๊ณผ: ${fieldName} = "${value}" (์ตœ๋Œ€: ${config.maxLength})`); return; // ์ตœ๋Œ€ ๊ธธ์ด ์ดˆ๊ณผ ์ฐจ๋‹จ } // console.log(`โœ… updateFormData ํ˜ธ์ถœ: ${fieldName} = "${value}"`); 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(