From 95b5e3dc7a8404164260654fd9e98c63e0ba6c21 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Thu, 20 Nov 2025 17:47:56 +0900 Subject: [PATCH] =?UTF-8?q?fix(autocomplete-search-input):=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=A7=A4=ED=95=91=20=EC=A0=80=EC=9E=A5=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - types.ts에 targetTable 필드 추가하여 config에 저장되도록 수정 - ConfigPanel에서 targetTable을 localConfig로 관리하여 설정 유지 - Renderer 단순화 (TextInput 패턴 적용) - Component에서 직접 isInteractive 체크 및 필드 매핑 처리 - ComponentRendererProps 상속으로 필수 props 타입 안정성 확보 문제: - ConfigPanel 설정이 초기화되는 문제 - 필드 매핑 데이터가 DB에 저장되지 않는 문제 해결: - 정상 작동하는 TextInput 컴포넌트 패턴 분석 및 적용 - Renderer는 props만 전달, Component가 저장 로직 처리 --- frontend/components/screen/SaveModal.tsx | 20 +- .../AutocompleteSearchInputComponent.tsx | 154 ++-- .../AutocompleteSearchInputConfigPanel.tsx | 845 +++++------------- .../autocomplete-search-input/types.ts | 2 + 4 files changed, 329 insertions(+), 692 deletions(-) diff --git a/frontend/components/screen/SaveModal.tsx b/frontend/components/screen/SaveModal.tsx index d1e942ff..4228f389 100644 --- a/frontend/components/screen/SaveModal.tsx +++ b/frontend/components/screen/SaveModal.tsx @@ -337,12 +337,22 @@ export const SaveModal: React.FC = ({ formData={formData} originalData={originalData} onFormDataChange={(fieldName, value) => { - setFormData((prev) => ({ - ...prev, - [fieldName]: value, - })); + console.log("📝 SaveModal - formData 변경:", { + fieldName, + value, + componentType: component.type, + componentId: component.id, + }); + setFormData((prev) => { + const newData = { + ...prev, + [fieldName]: value, + }; + console.log("📦 새 formData:", newData); + return newData; + }); }} - mode={initialData ? "edit" : "create"} + mode="edit" isInModal={true} isInteractive={true} /> diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx index 42baabdc..263cb294 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx @@ -7,18 +7,23 @@ import { Button } from "@/components/ui/button"; import { useEntitySearch } from "../entity-search-input/useEntitySearch"; import { EntitySearchResult } from "../entity-search-input/types"; import { cn } from "@/lib/utils"; -import { AutocompleteSearchInputConfig, FieldMapping } from "./types"; +import { AutocompleteSearchInputConfig } from "./types"; +import { ComponentRendererProps } from "../../DynamicComponentRenderer"; -interface AutocompleteSearchInputProps extends Partial { +export interface AutocompleteSearchInputProps extends ComponentRendererProps { config?: AutocompleteSearchInputConfig; + tableName?: string; + displayField?: string; + valueField?: string; + searchFields?: string[]; filterCondition?: Record; - disabled?: boolean; - value?: any; - onChange?: (value: any, fullData?: any) => void; - className?: string; + placeholder?: string; + showAdditionalInfo?: boolean; + additionalFields?: string[]; } export function AutocompleteSearchInputComponent({ + component, config, tableName: propTableName, displayField: propDisplayField, @@ -29,9 +34,10 @@ export function AutocompleteSearchInputComponent({ disabled = false, value, onChange, - showAdditionalInfo: propShowAdditionalInfo, - additionalFields: propAdditionalFields, className, + isInteractive = false, + onFormDataChange, + formData, }: AutocompleteSearchInputProps) { // config prop 우선, 없으면 개별 prop 사용 const tableName = config?.tableName || propTableName || ""; @@ -39,8 +45,7 @@ export function AutocompleteSearchInputComponent({ const valueField = config?.valueField || propValueField || ""; const searchFields = config?.searchFields || propSearchFields || [displayField]; const placeholder = config?.placeholder || propPlaceholder || "검색..."; - const showAdditionalInfo = config?.showAdditionalInfo ?? propShowAdditionalInfo ?? false; - const additionalFields = config?.additionalFields || propAdditionalFields || []; + const [inputValue, setInputValue] = useState(""); const [isOpen, setIsOpen] = useState(false); const [selectedData, setSelectedData] = useState(null); @@ -52,15 +57,20 @@ export function AutocompleteSearchInputComponent({ filterCondition, }); + // formData에서 현재 값 가져오기 (isInteractive 모드) + const currentValue = isInteractive && formData && component?.columnName + ? formData[component.columnName] + : value; + // value가 변경되면 표시값 업데이트 useEffect(() => { - if (value && selectedData) { + if (currentValue && selectedData) { setInputValue(selectedData[displayField] || ""); - } else if (!value) { + } else if (!currentValue) { setInputValue(""); setSelectedData(null); } - }, [value, displayField]); + }, [currentValue, displayField, selectedData]); // 외부 클릭 감지 useEffect(() => { @@ -81,45 +91,61 @@ export function AutocompleteSearchInputComponent({ setIsOpen(true); }; - // 필드 자동 매핑 처리 - const applyFieldMappings = (item: EntitySearchResult) => { - if (!config?.enableFieldMapping || !config?.fieldMappings) { - return; - } - - config.fieldMappings.forEach((mapping: FieldMapping) => { - if (!mapping.sourceField || !mapping.targetField) { - return; - } - - const value = item[mapping.sourceField]; - - // DOM에서 타겟 필드 찾기 (id로 검색) - const targetElement = document.getElementById(mapping.targetField); - - if (targetElement) { - // input, textarea 등의 값 설정 - if ( - targetElement instanceof HTMLInputElement || - targetElement instanceof HTMLTextAreaElement - ) { - targetElement.value = value?.toString() || ""; - - // React의 change 이벤트 트리거 - const event = new Event("input", { bubbles: true }); - targetElement.dispatchEvent(event); - } - } - }); - }; - const handleSelect = (item: EntitySearchResult) => { setSelectedData(item); setInputValue(item[displayField] || ""); - onChange?.(item[valueField], item); - // 필드 자동 매핑 실행 - applyFieldMappings(item); + console.log("🔍 AutocompleteSearchInput handleSelect:", { + item, + valueField, + value: item[valueField], + config, + isInteractive, + hasOnFormDataChange: !!onFormDataChange, + columnName: component?.columnName, + }); + + // isInteractive 모드에서만 저장 + if (isInteractive && onFormDataChange) { + // 필드 매핑 처리 + if (config?.fieldMappings && Array.isArray(config.fieldMappings)) { + console.log("📋 필드 매핑 처리 시작:", config.fieldMappings); + + config.fieldMappings.forEach((mapping: any, index: number) => { + const targetField = mapping.targetField || mapping.targetColumn; + + console.log(` 매핑 ${index + 1}:`, { + sourceField: mapping.sourceField, + targetField, + label: mapping.label, + }); + + if (mapping.sourceField && targetField) { + const sourceValue = item[mapping.sourceField]; + + console.log(` 값: ${mapping.sourceField} = ${sourceValue}`); + + if (sourceValue !== undefined) { + console.log(` ✅ 저장: ${targetField} = ${sourceValue}`); + onFormDataChange(targetField, sourceValue); + } else { + console.warn(` ⚠️ sourceField "${mapping.sourceField}"의 값이 undefined입니다`); + } + } else { + console.warn(` ⚠️ 매핑 불완전: sourceField=${mapping.sourceField}, targetField=${targetField}`); + } + }); + } + + // 기본 필드 저장 (columnName이 설정된 경우) + if (component?.columnName) { + console.log(`💾 기본 필드 저장: ${component.columnName} = ${item[valueField]}`); + onFormDataChange(component.columnName, item[valueField]); + } + } + + // onChange 콜백 호출 (호환성) + onChange?.(item[valueField], item); setIsOpen(false); }; @@ -149,9 +175,9 @@ export function AutocompleteSearchInputComponent({ onFocus={handleInputFocus} placeholder={placeholder} disabled={disabled} - className="h-8 text-xs sm:h-10 sm:text-sm pr-16" + className="h-8 pr-16 text-xs sm:h-10 sm:text-sm" /> -
+
{loading && ( )} @@ -172,10 +198,10 @@ export function AutocompleteSearchInputComponent({ {/* 드롭다운 결과 */} {isOpen && (results.length > 0 || loading) && ( -
+
{loading && results.length === 0 ? (
- + 검색 중...
) : results.length === 0 ? ( @@ -189,37 +215,15 @@ export function AutocompleteSearchInputComponent({ key={index} type="button" onClick={() => handleSelect(item)} - className="w-full text-left px-3 py-2 hover:bg-accent text-xs sm:text-sm transition-colors" + className="w-full px-3 py-2 text-left text-xs transition-colors hover:bg-accent sm:text-sm" >
{item[displayField]}
- {additionalFields.length > 0 && ( -
- {additionalFields.map((field) => ( -
- {field}: {item[field] || "-"} -
- ))} -
- )} ))}
)}
)} - - {/* 추가 정보 표시 */} - {showAdditionalInfo && selectedData && additionalFields.length > 0 && ( -
- {additionalFields.map((field) => ( -
- {field}: - {selectedData[field] || "-"} -
- ))} -
- )}
); } - diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx index 96a212b1..e6942704 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx @@ -6,10 +6,9 @@ import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Plus, X, Check, ChevronsUpDown } from "lucide-react"; -import { AutocompleteSearchInputConfig, FieldMapping, ValueFieldStorage } from "./types"; +import { AutocompleteSearchInputConfig } from "./types"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { cn } from "@/lib/utils"; @@ -24,83 +23,14 @@ export function AutocompleteSearchInputConfigPanel({ }: AutocompleteSearchInputConfigPanelProps) { const [localConfig, setLocalConfig] = useState(config); const [allTables, setAllTables] = useState([]); - const [tableColumns, setTableColumns] = useState([]); + const [sourceTableColumns, setSourceTableColumns] = useState([]); + const [targetTableColumns, setTargetTableColumns] = useState([]); const [isLoadingTables, setIsLoadingTables] = useState(false); - const [isLoadingColumns, setIsLoadingColumns] = useState(false); - const [openTableCombo, setOpenTableCombo] = useState(false); + const [isLoadingSourceColumns, setIsLoadingSourceColumns] = useState(false); + const [isLoadingTargetColumns, setIsLoadingTargetColumns] = useState(false); + const [openSourceTableCombo, setOpenSourceTableCombo] = useState(false); + const [openTargetTableCombo, setOpenTargetTableCombo] = useState(false); const [openDisplayFieldCombo, setOpenDisplayFieldCombo] = useState(false); - const [openValueFieldCombo, setOpenValueFieldCombo] = useState(false); - const [openStorageTableCombo, setOpenStorageTableCombo] = useState(false); - const [openStorageColumnCombo, setOpenStorageColumnCombo] = useState(false); - const [storageTableColumns, setStorageTableColumns] = useState([]); - const [isLoadingStorageColumns, setIsLoadingStorageColumns] = useState(false); - - // 전체 테이블 목록 로드 - useEffect(() => { - const loadTables = async () => { - setIsLoadingTables(true); - try { - const response = await tableManagementApi.getTableList(); - if (response.success && response.data) { - setAllTables(response.data); - } - } catch (error) { - console.error("테이블 목록 로드 실패:", error); - } finally { - setIsLoadingTables(false); - } - }; - loadTables(); - }, []); - - // 선택된 테이블의 컬럼 목록 로드 - useEffect(() => { - const loadColumns = async () => { - if (!localConfig.tableName) { - setTableColumns([]); - return; - } - - setIsLoadingColumns(true); - try { - const response = await tableManagementApi.getColumnList(localConfig.tableName); - if (response.success && response.data) { - setTableColumns(response.data.columns); - } - } catch (error) { - console.error("컬럼 목록 로드 실패:", error); - setTableColumns([]); - } finally { - setIsLoadingColumns(false); - } - }; - loadColumns(); - }, [localConfig.tableName]); - - // 저장 대상 테이블의 컬럼 목록 로드 - useEffect(() => { - const loadStorageColumns = async () => { - const storageTable = localConfig.valueFieldStorage?.targetTable; - if (!storageTable) { - setStorageTableColumns([]); - return; - } - - setIsLoadingStorageColumns(true); - try { - const response = await tableManagementApi.getColumnList(storageTable); - if (response.success && response.data) { - setStorageTableColumns(response.data.columns); - } - } catch (error) { - console.error("저장 테이블 컬럼 로드 실패:", error); - setStorageTableColumns([]); - } finally { - setIsLoadingStorageColumns(false); - } - }; - loadStorageColumns(); - }, [localConfig.valueFieldStorage?.targetTable]); useEffect(() => { setLocalConfig(config); @@ -112,52 +42,76 @@ export function AutocompleteSearchInputConfigPanel({ onConfigChange(newConfig); }; - const addSearchField = () => { - const fields = localConfig.searchFields || []; - updateConfig({ searchFields: [...fields, ""] }); - }; + // 테이블 목록 로드 + useEffect(() => { + const loadTables = async () => { + setIsLoadingTables(true); + try { + const response = await tableManagementApi.getTableList(); + if (response.success && response.data) { + setAllTables(response.data); + } + } catch (error) { + setAllTables([]); + } finally { + setIsLoadingTables(false); + } + }; + loadTables(); + }, []); - const updateSearchField = (index: number, value: string) => { - const fields = [...(localConfig.searchFields || [])]; - fields[index] = value; - updateConfig({ searchFields: fields }); - }; + // 외부 테이블 컬럼 로드 + useEffect(() => { + const loadColumns = async () => { + if (!localConfig.tableName) { + setSourceTableColumns([]); + return; + } + setIsLoadingSourceColumns(true); + try { + const response = await tableManagementApi.getColumnList(localConfig.tableName); + if (response.success && response.data) { + setSourceTableColumns(response.data.columns); + } + } catch (error) { + setSourceTableColumns([]); + } finally { + setIsLoadingSourceColumns(false); + } + }; + loadColumns(); + }, [localConfig.tableName]); - const removeSearchField = (index: number) => { - const fields = [...(localConfig.searchFields || [])]; - fields.splice(index, 1); - updateConfig({ searchFields: fields }); - }; + // 저장 테이블 컬럼 로드 + useEffect(() => { + const loadTargetColumns = async () => { + if (!localConfig.targetTable) { + setTargetTableColumns([]); + return; + } + setIsLoadingTargetColumns(true); + try { + const response = await tableManagementApi.getColumnList(localConfig.targetTable); + if (response.success && response.data) { + setTargetTableColumns(response.data.columns); + } + } catch (error) { + setTargetTableColumns([]); + } finally { + setIsLoadingTargetColumns(false); + } + }; + loadTargetColumns(); + }, [localConfig.targetTable]); - const addAdditionalField = () => { - const fields = localConfig.additionalFields || []; - updateConfig({ additionalFields: [...fields, ""] }); - }; - - const updateAdditionalField = (index: number, value: string) => { - const fields = [...(localConfig.additionalFields || [])]; - fields[index] = value; - updateConfig({ additionalFields: fields }); - }; - - const removeAdditionalField = (index: number) => { - const fields = [...(localConfig.additionalFields || [])]; - fields.splice(index, 1); - updateConfig({ additionalFields: fields }); - }; - - // 필드 매핑 관리 함수 const addFieldMapping = () => { const mappings = localConfig.fieldMappings || []; updateConfig({ - fieldMappings: [ - ...mappings, - { sourceField: "", targetField: "", label: "" }, - ], + fieldMappings: [...mappings, { sourceField: "", targetField: "", label: "" }], }); }; - const updateFieldMapping = (index: number, updates: Partial) => { + const updateFieldMapping = (index: number, updates: any) => { const mappings = [...(localConfig.fieldMappings || [])]; mappings[index] = { ...mappings[index], ...updates }; updateConfig({ fieldMappings: mappings }); @@ -170,21 +124,22 @@ export function AutocompleteSearchInputConfigPanel({ }; return ( -
+
+ {/* 1. 외부 테이블 선택 */}
- - + + @@ -200,7 +155,7 @@ export function AutocompleteSearchInputConfigPanel({ value={table.tableName} onSelect={() => { updateConfig({ tableName: table.tableName }); - setOpenTableCombo(false); + setOpenSourceTableCombo(false); }} className="text-xs sm:text-sm" > @@ -216,13 +171,11 @@ export function AutocompleteSearchInputConfigPanel({ -

- 검색할 데이터가 저장된 테이블을 선택하세요 -

+ {/* 2. 표시 필드 선택 */}
- + @@ -244,7 +197,7 @@ export function AutocompleteSearchInputConfigPanel({ 필드를 찾을 수 없습니다. - {tableColumns.map((column) => ( + {sourceTableColumns.map((column) => ( -

- 사용자에게 보여줄 필드 (예: 거래처명) -

+ {/* 3. 저장 대상 테이블 선택 */}
- - + + - + - 필드를 찾을 수 없습니다. + 테이블을 찾을 수 없습니다. - {tableColumns.map((column) => ( + {allTables.map((table) => ( { - updateConfig({ valueField: column.columnName }); - setOpenValueFieldCombo(false); + updateConfig({ targetTable: table.tableName }); + setOpenTargetTableCombo(false); }} className="text-xs sm:text-sm" > - +
- {column.displayName || column.columnName} - {column.displayName && {column.columnName}} + {table.displayName || table.tableName} + {table.displayName && {table.tableName}}
))} @@ -316,11 +267,124 @@ export function AutocompleteSearchInputConfigPanel({
-

- 검색 테이블에서 가져올 값의 컬럼 (예: customer_code) -

+ {/* 4. 필드 매핑 */} +
+
+ + +
+ + {(localConfig.fieldMappings || []).length === 0 && ( +
+

+ 매핑 추가 버튼을 눌러 필드 매핑을 설정하세요 +

+
+ )} + +
+ {(localConfig.fieldMappings || []).map((mapping, index) => ( +
+
+ + 매핑 #{index + 1} + + +
+ +
+ + + updateFieldMapping(index, { label: e.target.value }) + } + placeholder="예: 거래처 코드" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+ +
+ + +
+ +
+ + +
+ + {mapping.sourceField && mapping.targetField && ( +
+

+ + {localConfig.tableName}.{mapping.sourceField} + + {" → "} + + {localConfig.targetTable}.{mapping.targetField} + +

+
+ )} +
+ ))} +
+
+ + {/* 플레이스홀더 */}
- {/* 값 필드 저장 위치 설정 */} -
-
-

값 필드 저장 위치 (고급)

-

- 위에서 선택한 "값 필드"의 데이터를 어느 테이블/컬럼에 저장할지 지정합니다. -
- 미설정 시 화면의 연결 테이블에 컴포넌트의 바인딩 필드로 자동 저장됩니다. -

-
- - {/* 저장 테이블 선택 */} -
- - - - - - - - - - 테이블을 찾을 수 없습니다. - - {/* 기본값 옵션 */} - { - updateConfig({ - valueFieldStorage: { - ...localConfig.valueFieldStorage, - targetTable: undefined, - targetColumn: undefined, - }, - }); - setOpenStorageTableCombo(false); - }} - className="text-xs sm:text-sm" - > - -
- 기본값 - 화면의 연결 테이블 사용 -
-
- {allTables.map((table) => ( - { - updateConfig({ - valueFieldStorage: { - ...localConfig.valueFieldStorage, - targetTable: table.tableName, - targetColumn: undefined, // 테이블 변경 시 컬럼 초기화 - }, - }); - setOpenStorageTableCombo(false); - }} - className="text-xs sm:text-sm" - > - -
- {table.displayName || table.tableName} - {table.displayName && {table.tableName}} -
-
- ))} -
-
-
-
-
-

- 값을 저장할 테이블 (기본값: 화면 연결 테이블) -

-
- - {/* 저장 컬럼 선택 */} - {localConfig.valueFieldStorage?.targetTable && ( -
- - - - - - - - - - 컬럼을 찾을 수 없습니다. - - {storageTableColumns.map((column) => ( - { - updateConfig({ - valueFieldStorage: { - ...localConfig.valueFieldStorage, - targetColumn: column.columnName, - }, - }); - setOpenStorageColumnCombo(false); - }} - className="text-xs sm:text-sm" - > - -
- {column.displayName || column.columnName} - {column.displayName && {column.columnName}} -
-
- ))} -
-
-
-
-
-

- 값을 저장할 컬럼명 + {/* 설정 요약 */} + {localConfig.tableName && localConfig.targetTable && (localConfig.fieldMappings || []).length > 0 && ( +

+

+ 설정 요약 +

+
+

+ 외부 테이블: {localConfig.tableName} +

+

+ 표시 필드: {localConfig.displayField} +

+

+ 저장 테이블: {localConfig.targetTable} +

+

+ 매핑 개수: {(localConfig.fieldMappings || []).length}개

-
- )} - - {/* 설명 박스 */} -
-

- 저장 위치 동작 -

-
- {localConfig.valueFieldStorage?.targetTable ? ( - <> -

- 선택한 값({localConfig.valueField})을 -

-

- - {localConfig.valueFieldStorage.targetTable} - {" "} - 테이블의{" "} - - {localConfig.valueFieldStorage.targetColumn || "(컬럼 미지정)"} - {" "} - 컬럼에 저장합니다. -

- - ) : ( -

기본값: 화면의 연결 테이블에 컴포넌트의 바인딩 필드로 저장됩니다.

- )} -
-
-
- -
-
- - -
-
- {(localConfig.searchFields || []).map((field, index) => ( -
- - -
- ))} -
-
- -
-
- - - updateConfig({ showAdditionalInfo: checked }) - } - /> -
-
- - {localConfig.showAdditionalInfo && ( -
-
- - -
-
- {(localConfig.additionalFields || []).map((field, index) => ( -
- - -
- ))}
)} - - {/* 필드 자동 매핑 설정 */} -
-
-

필드 자동 매핑

-

- 선택한 항목의 필드를 화면의 다른 입력 필드에 자동으로 채워넣습니다 -

-
- -
-
- - - updateConfig({ enableFieldMapping: checked }) - } - /> -
-

- 활성화하면 항목 선택 시 설정된 필드들이 자동으로 채워집니다 -

-
- - {localConfig.enableFieldMapping && ( -
-
- - -
- -
- {(localConfig.fieldMappings || []).map((mapping, index) => ( -
-
- - 매핑 #{index + 1} - - -
- - {/* 표시명 */} -
- - - updateFieldMapping(index, { label: e.target.value }) - } - placeholder="예: 거래처명" - className="h-8 text-xs sm:h-10 sm:text-sm" - /> -

- 이 매핑의 설명 (선택사항) -

-
- - {/* 소스 필드 (테이블의 컬럼) */} -
- - -

- 가져올 데이터의 컬럼명 -

-
- - {/* 타겟 필드 (화면의 input ID) */} -
- - - updateFieldMapping(index, { targetField: e.target.value }) - } - placeholder="예: customer_name_input" - className="h-8 text-xs sm:h-10 sm:text-sm" - /> -

- 값을 채울 화면 컴포넌트의 ID (예: input의 id 속성) -

-
- - {/* 예시 설명 */} -
-

- {mapping.sourceField && mapping.targetField ? ( - <> - {mapping.label || "이 필드"}: 테이블의{" "} - - {mapping.sourceField} - {" "} - 값을 화면의{" "} - - {mapping.targetField} - {" "} - 컴포넌트에 자동으로 채웁니다 - - ) : ( - "소스 필드와 타겟 필드를 모두 선택하세요" - )} -

-
-
- ))} -
- - {/* 사용 안내 */} - {localConfig.fieldMappings && localConfig.fieldMappings.length > 0 && ( -
-

- 사용 방법 -

-
    -
  • 화면에서 이 검색 컴포넌트로 항목을 선택하면
  • -
  • 설정된 매핑에 따라 다른 입력 필드들이 자동으로 채워집니다
  • -
  • 타겟 필드 ID는 화면 디자이너에서 설정한 컴포넌트 ID와 일치해야 합니다
  • -
-
- )} -
- )} -
); } - diff --git a/frontend/lib/registry/components/autocomplete-search-input/types.ts b/frontend/lib/registry/components/autocomplete-search-input/types.ts index 802f27c7..85101e89 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/types.ts +++ b/frontend/lib/registry/components/autocomplete-search-input/types.ts @@ -27,5 +27,7 @@ export interface AutocompleteSearchInputConfig { // 필드 자동 매핑 설정 enableFieldMapping?: boolean; // 필드 자동 매핑 활성화 여부 fieldMappings?: FieldMapping[]; // 매핑할 필드 목록 + // 저장 대상 테이블 (간소화 버전) + targetTable?: string; }