diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 72eab245..92cd31dd 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -200,12 +200,21 @@ export const DynamicComponentRenderer: React.FC = // onChange 핸들러 - 컴포넌트 타입에 따라 다르게 처리 const handleChange = (value: any) => { + // React 이벤트 객체인 경우 값 추출 + let actualValue = value; + if (value && typeof value === "object" && value.nativeEvent && value.target) { + // SyntheticEvent인 경우 target.value 추출 + actualValue = value.target.value; + console.log("⚠️ DynamicComponentRenderer: 이벤트 객체 감지, value 추출:", actualValue); + } + console.log("🔄 DynamicComponentRenderer handleChange 호출:", { componentType, fieldName, - value, - valueType: typeof value, - isArray: Array.isArray(value), + originalValue: value, + actualValue, + valueType: typeof actualValue, + isArray: Array.isArray(actualValue), }); if (onFormDataChange) { @@ -213,11 +222,11 @@ export const DynamicComponentRenderer: React.FC = // 단순 input 컴포넌트는 (fieldName, value) 형태로 전달받음 if (componentType === "repeater-field-group" || componentType === "repeater") { // fieldName과 함께 전달 - console.log("💾 RepeaterInput 데이터 저장:", fieldName, value); - onFormDataChange(fieldName, value); + console.log("💾 RepeaterInput 데이터 저장:", fieldName, actualValue); + onFormDataChange(fieldName, actualValue); } else { // 이미 fieldName이 포함된 경우는 그대로 전달 - onFormDataChange(fieldName, value); + onFormDataChange(fieldName, actualValue); } } }; diff --git a/frontend/lib/registry/components/text-input/TextInputComponent.tsx b/frontend/lib/registry/components/text-input/TextInputComponent.tsx index bdf41276..fcb88455 100644 --- a/frontend/lib/registry/components/text-input/TextInputComponent.tsx +++ b/frontend/lib/registry/components/text-input/TextInputComponent.tsx @@ -325,11 +325,8 @@ export const TextInputComponent: React.FC = ({ setEmailUsername(newUsername); const fullEmail = `${newUsername}@${emailDomain}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullEmail, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullEmail); } }} className={`h-full flex-1 rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -367,11 +364,8 @@ export const TextInputComponent: React.FC = ({ setEmailDomain(value); const fullEmail = `${emailUsername}@${value}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullEmail, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullEmail); } }} /> @@ -388,11 +382,8 @@ export const TextInputComponent: React.FC = ({ setEmailDomain(currentValue); const fullEmail = `${emailUsername}@${currentValue}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullEmail, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullEmail); } setEmailDomainOpen(false); }} @@ -437,11 +428,8 @@ export const TextInputComponent: React.FC = ({ setTelPart1(value); const fullTel = `${value}-${telPart2}-${telPart3}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullTel, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullTel); } }} className={`h-full flex-1 rounded-md border px-3 py-2 text-center text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -462,11 +450,8 @@ export const TextInputComponent: React.FC = ({ setTelPart2(value); const fullTel = `${telPart1}-${value}-${telPart3}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullTel, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullTel); } }} className={`h-full flex-1 rounded-md border px-3 py-2 text-center text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -487,11 +472,8 @@ export const TextInputComponent: React.FC = ({ setTelPart3(value); const fullTel = `${telPart1}-${telPart2}-${value}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullTel, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullTel); } }} className={`h-full flex-1 rounded-md border px-3 py-2 text-center text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -523,11 +505,8 @@ export const TextInputComponent: React.FC = ({ setUrlProtocol(newProtocol); const fullUrl = `${newProtocol}${urlDomain}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullUrl, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullUrl); } }} className={`h-full w-[100px] cursor-pointer rounded-md border px-2 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -548,11 +527,8 @@ export const TextInputComponent: React.FC = ({ setUrlDomain(newDomain); const fullUrl = `${urlProtocol}${newDomain}`; - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: fullUrl, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, fullUrl); } }} className={`h-full flex-1 rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -595,11 +571,8 @@ export const TextInputComponent: React.FC = ({ required={componentConfig.required || false} readOnly={componentConfig.readonly || false} onChange={(e) => { - if (isInteractive && formData && onFormDataChange && component.columnName) { - onFormDataChange({ - ...formData, - [component.columnName]: e.target.value, - }); + if (isInteractive && onFormDataChange && component.columnName) { + onFormDataChange(component.columnName, e.target.value); } }} className={`box-border h-full w-full max-w-full resize-none rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`} @@ -625,10 +598,13 @@ export const TextInputComponent: React.FC = ({ if (isInteractive && formData && component.columnName) { // 인터랙티브 모드: formData 우선, 없으면 자동생성 값 - displayValue = formData[component.columnName] || autoGeneratedValue || ""; + const rawValue = formData[component.columnName] || autoGeneratedValue || ""; + // 객체인 경우 빈 문자열로 변환 (에러 방지) + displayValue = typeof rawValue === "object" ? "" : String(rawValue); } else { // 디자인 모드: component.value 우선, 없으면 자동생성 값 - displayValue = component.value || autoGeneratedValue || ""; + const rawValue = component.value || autoGeneratedValue || ""; + displayValue = typeof rawValue === "object" ? "" : String(rawValue); } console.log("📄 Input 값 계산:", { @@ -636,9 +612,11 @@ export const TextInputComponent: React.FC = ({ hasFormData: !!formData, columnName: component.columnName, formDataValue: formData?.[component.columnName], + formDataValueType: typeof formData?.[component.columnName], componentValue: component.value, autoGeneratedValue, finalDisplayValue: displayValue, + isObject: typeof displayValue === "object", }); return displayValue; @@ -670,23 +648,28 @@ export const TextInputComponent: React.FC = ({ // isInteractive 모드에서는 formData 업데이트 if (isInteractive && onFormDataChange && component.columnName) { - // console.log(`📤 TextInputComponent -> onFormDataChange 호출: ${component.columnName} = "${newValue}"`); - console.log("🔍 onFormDataChange 함수 정보:", { - functionName: onFormDataChange.name, - functionString: onFormDataChange.toString().substring(0, 200), + console.log(`✅ TextInputComponent onChange 조건 충족:`, { + columnName: component.columnName, + newValue, + valueType: typeof newValue, + isInteractive, + hasOnFormDataChange: !!onFormDataChange, + onFormDataChangeType: typeof onFormDataChange, }); onFormDataChange(component.columnName, newValue); + console.log(`✅ onFormDataChange 호출 완료`); } else { console.log("❌ TextInputComponent onFormDataChange 조건 미충족:", { isInteractive, hasOnFormDataChange: !!onFormDataChange, hasColumnName: !!component.columnName, + columnName: component.columnName, }); } - // 기존 onChange 핸들러도 호출 + // props.onChange는 DynamicComponentRenderer의 handleChange + // 이벤트 객체 감지 및 값 추출 로직이 있으므로 안전하게 호출 가능 if (props.onChange) { - // console.log(`📤 TextInputComponent -> props.onChange 호출: "${newValue}"`); props.onChange(newValue); } }}