diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx index 7a115ea3..cbd2744c 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx @@ -44,7 +44,42 @@ export function AutocompleteSearchInputComponent({ const displayField = config?.displayField || propDisplayField || ""; const displayFields = config?.displayFields || (displayField ? [displayField] : []); // 다중 표시 필드 const displaySeparator = config?.displaySeparator || " → "; // 구분자 - const valueField = config?.valueField || propValueField || ""; + + // valueField 결정: fieldMappings 기반으로 추론 (config.valueField가 fieldMappings에 없으면 무시) + const getValueField = () => { + // fieldMappings가 있으면 그 안에서 추론 (가장 신뢰할 수 있는 소스) + if (config?.fieldMappings && config.fieldMappings.length > 0) { + // config.valueField가 fieldMappings의 sourceField에 있으면 사용 + if (config?.valueField) { + const hasValueFieldInMappings = config.fieldMappings.some( + (m: any) => m.sourceField === config.valueField + ); + if (hasValueFieldInMappings) { + return config.valueField; + } + // fieldMappings에 없으면 무시하고 추론 + } + + // _code 또는 _id로 끝나는 필드 우선 (보통 PK나 코드 필드) + const codeMapping = config.fieldMappings.find( + (m: any) => m.sourceField?.endsWith("_code") || m.sourceField?.endsWith("_id") + ); + if (codeMapping) { + return codeMapping.sourceField; + } + + // 없으면 첫 번째 매핑 사용 + return config.fieldMappings[0].sourceField || ""; + } + + // fieldMappings가 없으면 기존 방식 + if (config?.valueField) return config.valueField; + if (propValueField) return propValueField; + + return ""; + }; + const valueField = getValueField(); + const searchFields = config?.searchFields || propSearchFields || displayFields; // 검색 필드도 다중 표시 필드 사용 const placeholder = config?.placeholder || propPlaceholder || "검색..."; @@ -76,11 +111,39 @@ export function AutocompleteSearchInputComponent({ // 선택된 데이터를 ref로도 유지 (리렌더링 시 초기화 방지) const selectedDataRef = useRef(null); const inputValueRef = useRef(""); + const initialValueLoadedRef = useRef(null); // 초기값 로드 추적 // formData에서 현재 값 가져오기 (isInteractive 모드) - const currentValue = isInteractive && formData && component?.columnName - ? formData[component.columnName] - : value; + // 우선순위: 1) component.columnName, 2) fieldMappings에서 valueField에 매핑된 targetField + const getCurrentValue = () => { + if (!isInteractive || !formData) { + return value; + } + + // 1. component.columnName으로 직접 바인딩된 경우 + if (component?.columnName && formData[component.columnName] !== undefined) { + return formData[component.columnName]; + } + + // 2. fieldMappings에서 valueField와 매핑된 targetField에서 값 가져오기 + if (config?.fieldMappings && Array.isArray(config.fieldMappings)) { + const valueFieldMapping = config.fieldMappings.find( + (mapping: any) => mapping.sourceField === valueField + ); + + if (valueFieldMapping) { + const targetField = valueFieldMapping.targetField || valueFieldMapping.targetColumn; + + if (targetField && formData[targetField] !== undefined) { + return formData[targetField]; + } + } + } + + return value; + }; + + const currentValue = getCurrentValue(); // selectedData 변경 시 ref도 업데이트 useEffect(() => { @@ -98,6 +161,79 @@ export function AutocompleteSearchInputComponent({ } }, []); + // 초기값이 있을 때 해당 값의 표시 텍스트를 조회하여 설정 + useEffect(() => { + const loadInitialDisplayValue = async () => { + // 이미 로드된 값이거나, 값이 없거나, 이미 선택된 데이터가 있으면 스킵 + if (!currentValue || selectedData || selectedDataRef.current) { + return; + } + + // 이미 같은 값을 로드한 적이 있으면 스킵 + if (initialValueLoadedRef.current === currentValue) { + return; + } + + // 테이블명과 필드 정보가 없으면 스킵 + if (!tableName || !valueField) { + return; + } + + console.log("🔄 AutocompleteSearchInput 초기값 로드:", { + currentValue, + tableName, + valueField, + displayFields, + }); + + try { + // API를 통해 해당 값의 표시 텍스트 조회 + const { apiClient } = await import("@/lib/api/client"); + const filterConditionWithValue = { + ...filterCondition, + [valueField]: currentValue, + }; + + const params = new URLSearchParams({ + searchText: "", + searchFields: searchFields.join(","), + filterCondition: JSON.stringify(filterConditionWithValue), + page: "1", + limit: "10", + }); + + const response = await apiClient.get<{ success: boolean; data: EntitySearchResult[] }>( + `/entity-search/${tableName}?${params.toString()}` + ); + + if (response.data.success && response.data.data && response.data.data.length > 0) { + const matchedItem = response.data.data.find((item: EntitySearchResult) => + String(item[valueField]) === String(currentValue) + ); + + if (matchedItem) { + const displayText = getDisplayValue(matchedItem); + console.log("✅ 초기값 표시 텍스트 로드 성공:", { + currentValue, + displayText, + matchedItem, + }); + + setSelectedData(matchedItem); + setInputValue(displayText); + selectedDataRef.current = matchedItem; + inputValueRef.current = displayText; + initialValueLoadedRef.current = currentValue; + } + } + } catch (error) { + console.error("❌ 초기값 표시 텍스트 로드 실패:", error); + } + }; + + loadInitialDisplayValue(); + }, [currentValue, tableName, valueField, displayFields, filterCondition, searchFields, selectedData]); + // value가 변경되면 표시값 업데이트 - 단, selectedData가 있으면 유지 useEffect(() => { // selectedData가 있으면 표시값 유지 (사용자가 방금 선택한 경우) @@ -107,6 +243,7 @@ export function AutocompleteSearchInputComponent({ if (!currentValue) { setInputValue(""); + initialValueLoadedRef.current = null; // 값이 없어지면 초기화 } }, [currentValue, selectedData]);