데이터 수정이 안되는 문제 해결
This commit is contained in:
@@ -124,7 +124,7 @@ export class DynamicFormApi {
|
||||
* @returns 업데이트 결과
|
||||
*/
|
||||
static async updateFormDataPartial(
|
||||
id: number,
|
||||
id: string | number, // 🔧 UUID 문자열도 지원
|
||||
originalData: Record<string, any>,
|
||||
newData: Record<string, any>,
|
||||
tableName: string,
|
||||
|
||||
@@ -337,6 +337,11 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
|
||||
// onChange 핸들러 - 컴포넌트 타입에 따라 다르게 처리
|
||||
const handleChange = (value: any) => {
|
||||
// autocomplete-search-input, entity-search-input은 자체적으로 onFormDataChange를 호출하므로 중복 저장 방지
|
||||
if (componentType === "autocomplete-search-input" || componentType === "entity-search-input") {
|
||||
return;
|
||||
}
|
||||
|
||||
// React 이벤트 객체인 경우 값 추출
|
||||
let actualValue = value;
|
||||
if (value && typeof value === "object" && value.nativeEvent && value.target) {
|
||||
|
||||
@@ -57,20 +57,42 @@ export function AutocompleteSearchInputComponent({
|
||||
filterCondition,
|
||||
});
|
||||
|
||||
// 선택된 데이터를 ref로도 유지 (리렌더링 시 초기화 방지)
|
||||
const selectedDataRef = useRef<EntitySearchResult | null>(null);
|
||||
const inputValueRef = useRef<string>("");
|
||||
|
||||
// formData에서 현재 값 가져오기 (isInteractive 모드)
|
||||
const currentValue = isInteractive && formData && component?.columnName
|
||||
? formData[component.columnName]
|
||||
: value;
|
||||
|
||||
// value가 변경되면 표시값 업데이트
|
||||
// selectedData 변경 시 ref도 업데이트
|
||||
useEffect(() => {
|
||||
if (currentValue && selectedData) {
|
||||
setInputValue(selectedData[displayField] || "");
|
||||
} else if (!currentValue) {
|
||||
setInputValue("");
|
||||
setSelectedData(null);
|
||||
if (selectedData) {
|
||||
selectedDataRef.current = selectedData;
|
||||
inputValueRef.current = inputValue;
|
||||
}
|
||||
}, [currentValue, displayField, selectedData]);
|
||||
}, [selectedData, inputValue]);
|
||||
|
||||
// 리렌더링 시 ref에서 값 복원
|
||||
useEffect(() => {
|
||||
if (!selectedData && selectedDataRef.current) {
|
||||
setSelectedData(selectedDataRef.current);
|
||||
setInputValue(inputValueRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// value가 변경되면 표시값 업데이트 - 단, selectedData가 있으면 유지
|
||||
useEffect(() => {
|
||||
// selectedData가 있으면 표시값 유지 (사용자가 방금 선택한 경우)
|
||||
if (selectedData || selectedDataRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentValue) {
|
||||
setInputValue("");
|
||||
}
|
||||
}, [currentValue, selectedData]);
|
||||
|
||||
// 외부 클릭 감지
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
@@ -21,7 +21,9 @@ export function AutocompleteSearchInputConfigPanel({
|
||||
config,
|
||||
onConfigChange,
|
||||
}: AutocompleteSearchInputConfigPanelProps) {
|
||||
const [localConfig, setLocalConfig] = useState(config);
|
||||
// 초기화 여부 추적 (첫 마운트 시에만 config로 초기화)
|
||||
const isInitialized = useRef(false);
|
||||
const [localConfig, setLocalConfig] = useState<AutocompleteSearchInputConfig>(config);
|
||||
const [allTables, setAllTables] = useState<any[]>([]);
|
||||
const [sourceTableColumns, setSourceTableColumns] = useState<any[]>([]);
|
||||
const [targetTableColumns, setTargetTableColumns] = useState<any[]>([]);
|
||||
@@ -32,12 +34,21 @@ export function AutocompleteSearchInputConfigPanel({
|
||||
const [openTargetTableCombo, setOpenTargetTableCombo] = useState(false);
|
||||
const [openDisplayFieldCombo, setOpenDisplayFieldCombo] = useState(false);
|
||||
|
||||
// 첫 마운트 시에만 config로 초기화 (이후에는 localConfig 유지)
|
||||
useEffect(() => {
|
||||
setLocalConfig(config);
|
||||
if (!isInitialized.current && config) {
|
||||
setLocalConfig(config);
|
||||
isInitialized.current = true;
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const updateConfig = (updates: Partial<AutocompleteSearchInputConfig>) => {
|
||||
const newConfig = { ...localConfig, ...updates };
|
||||
console.log("🔧 [AutocompleteConfigPanel] updateConfig:", {
|
||||
updates,
|
||||
localConfig,
|
||||
newConfig,
|
||||
});
|
||||
setLocalConfig(newConfig);
|
||||
onConfigChange(newConfig);
|
||||
};
|
||||
@@ -325,10 +336,11 @@ export function AutocompleteSearchInputConfigPanel({
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">외부 테이블 컬럼 *</Label>
|
||||
<Select
|
||||
value={mapping.sourceField}
|
||||
onValueChange={(value) =>
|
||||
updateFieldMapping(index, { sourceField: value })
|
||||
}
|
||||
value={mapping.sourceField || undefined}
|
||||
onValueChange={(value) => {
|
||||
console.log("🔧 [Select] sourceField 변경:", value);
|
||||
updateFieldMapping(index, { sourceField: value });
|
||||
}}
|
||||
disabled={!localConfig.tableName || isLoadingSourceColumns}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
||||
@@ -347,10 +359,11 @@ export function AutocompleteSearchInputConfigPanel({
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">저장 테이블 컬럼 *</Label>
|
||||
<Select
|
||||
value={mapping.targetField}
|
||||
onValueChange={(value) =>
|
||||
updateFieldMapping(index, { targetField: value })
|
||||
}
|
||||
value={mapping.targetField || undefined}
|
||||
onValueChange={(value) => {
|
||||
console.log("🔧 [Select] targetField 변경:", value);
|
||||
updateFieldMapping(index, { targetField: value });
|
||||
}}
|
||||
disabled={!localConfig.targetTable || isLoadingTargetColumns}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
||||
|
||||
@@ -694,7 +694,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
|
||||
const context: ButtonActionContext = {
|
||||
formData: formData || {},
|
||||
originalData: originalData || {}, // 부분 업데이트용 원본 데이터 추가
|
||||
originalData: originalData, // 🔧 빈 객체 대신 undefined 유지 (UPDATE 판단에 사용)
|
||||
screenId: effectiveScreenId, // 🆕 ScreenContext에서 가져온 값 사용
|
||||
tableName: effectiveTableName, // 🆕 ScreenContext에서 가져온 값 사용
|
||||
userId, // 🆕 사용자 ID
|
||||
|
||||
@@ -414,9 +414,20 @@ export class ButtonActionExecutor {
|
||||
const primaryKeys = primaryKeyResult.data || [];
|
||||
const primaryKeyValue = this.extractPrimaryKeyValueFromDB(formData, primaryKeys);
|
||||
|
||||
// 단순히 기본키 값 존재 여부로 판단 (임시)
|
||||
// TODO: 실제 테이블에서 기본키로 레코드 존재 여부 확인하는 API 필요
|
||||
const isUpdate = false; // 현재는 항상 INSERT로 처리
|
||||
// 🔧 수정: originalData가 있고 실제 데이터가 있으면 UPDATE 모드로 처리
|
||||
// originalData는 수정 버튼 클릭 시 editData로 전달되어 context.originalData로 설정됨
|
||||
// 빈 객체 {}도 truthy이므로 Object.keys로 실제 데이터 유무 확인
|
||||
const hasRealOriginalData = originalData && Object.keys(originalData).length > 0;
|
||||
const isUpdate = hasRealOriginalData && !!primaryKeyValue;
|
||||
|
||||
console.log("🔍 [handleSave] INSERT/UPDATE 판단:", {
|
||||
hasOriginalData: !!originalData,
|
||||
hasRealOriginalData,
|
||||
originalDataKeys: originalData ? Object.keys(originalData) : [],
|
||||
primaryKeyValue,
|
||||
isUpdate,
|
||||
primaryKeys,
|
||||
});
|
||||
|
||||
let saveResult;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user