From 29a4ab7b9df0bfaf7ea03bdfa9ef3d3adc1dc1f2 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 21 Jan 2026 11:40:47 +0900 Subject: [PATCH] =?UTF-8?q?entity-search-iniput=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EntitySearchInputComponent.tsx | 37 +++++- .../EntitySearchInputConfigPanel.tsx | 122 ++++++++++++++++-- .../EntitySearchInputWrapper.tsx | 5 + .../entity-search-input/EntitySearchModal.tsx | 11 +- .../components/entity-search-input/config.ts | 9 ++ 5 files changed, 166 insertions(+), 18 deletions(-) diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchInputComponent.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchInputComponent.tsx index 5045a43b..f1604337 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchInputComponent.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchInputComponent.tsx @@ -11,6 +11,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { dynamicFormApi } from "@/lib/api/dynamicForm"; import { cascadingRelationApi } from "@/lib/api/cascadingRelation"; +import { AutoFillMapping } from "./config"; export function EntitySearchInputComponent({ tableName, @@ -37,6 +38,8 @@ export function EntitySearchInputComponent({ formData, // 다중선택 props multiple: multipleProp, + // 자동 채움 매핑 props + autoFillMappings: autoFillMappingsProp, // 추가 props component, isInteractive, @@ -47,6 +50,7 @@ export function EntitySearchInputComponent({ isInteractive?: boolean; onFormDataChange?: (fieldName: string, value: any) => void; webTypeConfig?: any; // 웹타입 설정 (연쇄관계 등) + autoFillMappings?: AutoFillMapping[]; // 자동 채움 매핑 }) { // uiMode가 있으면 우선 사용, 없으면 modeProp 사용, 기본값 "combo" const mode = (uiMode || modeProp || "combo") as "select" | "modal" | "combo" | "autocomplete"; @@ -54,6 +58,18 @@ export function EntitySearchInputComponent({ // 다중선택 및 연쇄관계 설정 (props > webTypeConfig > componentConfig 순서) const config = component?.componentConfig || component?.webTypeConfig || {}; const isMultiple = multipleProp ?? config.multiple ?? false; + + // 자동 채움 매핑 설정 (props > config) + const autoFillMappings: AutoFillMapping[] = autoFillMappingsProp ?? config.autoFillMappings ?? []; + + // 디버그: 자동 채움 매핑 설정 확인 + console.log("🔧 [EntitySearchInput] 자동 채움 매핑 설정:", { + autoFillMappingsProp, + configAutoFillMappings: config.autoFillMappings, + effectiveAutoFillMappings: autoFillMappings, + isInteractive, + hasOnFormDataChange: !!onFormDataChange, + }); // 연쇄관계 설정 추출 const effectiveCascadingRelationCode = cascadingRelationCode || config.cascadingRelationCode; @@ -309,6 +325,23 @@ export function EntitySearchInputComponent({ console.log("📤 EntitySearchInput -> onFormDataChange:", component.columnName, newValue); } } + + // 🆕 자동 채움 매핑 적용 + if (autoFillMappings.length > 0 && isInteractive && onFormDataChange && fullData) { + console.log("🔄 자동 채움 매핑 적용:", { mappings: autoFillMappings, fullData }); + + for (const mapping of autoFillMappings) { + if (mapping.sourceField && mapping.targetField) { + const sourceValue = fullData[mapping.sourceField]; + if (sourceValue !== undefined) { + onFormDataChange(mapping.targetField, sourceValue); + console.log(` ✅ ${mapping.sourceField} → ${mapping.targetField}:`, sourceValue); + } else { + console.log(` ⚠️ ${mapping.sourceField} 값이 없음`); + } + } + } + } }; // 다중선택 모드에서 개별 항목 제거 @@ -436,7 +469,7 @@ export function EntitySearchInputComponent({ const isSelected = selectedValues.includes(String(option[valueField])); return ( handleSelectOption(option)} className="text-xs sm:text-sm" @@ -509,7 +542,7 @@ export function EntitySearchInputComponent({ {effectiveOptions.map((option, index) => ( handleSelectOption(option)} className="text-xs sm:text-sm" diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel.tsx index fb75daa4..22a52aab 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchInputConfigPanel.tsx @@ -10,7 +10,7 @@ import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Plus, X, Check, ChevronsUpDown, Database, Info, Link2, ExternalLink } from "lucide-react"; // allComponents는 현재 사용되지 않지만 향후 확장을 위해 props에 유지 -import { EntitySearchInputConfig } from "./config"; +import { EntitySearchInputConfig, AutoFillMapping } from "./config"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { tableTypeApi } from "@/lib/api/screen"; import { cascadingRelationApi, CascadingRelation } from "@/lib/api/cascadingRelation"; @@ -236,6 +236,7 @@ export function EntitySearchInputConfigPanel({ const newConfig = { ...localConfig, ...updates }; setLocalConfig(newConfig); onConfigChange(newConfig); + console.log("📝 [EntitySearchInput] 설정 업데이트:", { updates, newConfig }); }; // 연쇄 드롭다운 활성화/비활성화 @@ -636,9 +637,9 @@ export function EntitySearchInputConfigPanel({ 필드를 찾을 수 없습니다. - {tableColumns.map((column) => ( + {tableColumns.map((column, idx) => ( { updateConfig({ displayField: column.columnName }); @@ -690,9 +691,9 @@ export function EntitySearchInputConfigPanel({ 필드를 찾을 수 없습니다. - {tableColumns.map((column) => ( + {tableColumns.map((column, idx) => ( { updateConfig({ valueField: column.columnName }); @@ -812,8 +813,8 @@ export function EntitySearchInputConfigPanel({ - {tableColumns.map((col) => ( - + {tableColumns.map((col, colIdx) => ( + {col.displayName || col.columnName} ))} @@ -860,8 +861,8 @@ export function EntitySearchInputConfigPanel({ - {tableColumns.map((col) => ( - + {tableColumns.map((col, colIdx) => ( + {col.displayName || col.columnName} ))} @@ -919,8 +920,8 @@ export function EntitySearchInputConfigPanel({ - {tableColumns.map((col) => ( - + {tableColumns.map((col, colIdx) => ( + {col.displayName || col.columnName} ))} @@ -939,6 +940,105 @@ export function EntitySearchInputConfigPanel({ )} + + {/* 자동 채움 매핑 설정 */} +
+
+
+ +

자동 채움 매핑

+
+ +
+

+ 엔티티를 선택하면 소스 필드의 값이 대상 필드에 자동으로 채워집니다. +

+ + {(localConfig.autoFillMappings || []).length > 0 && ( +
+ {(localConfig.autoFillMappings || []).map((mapping, index) => ( +
+ {/* 소스 필드 (선택된 엔티티) */} +
+ + +
+ + {/* 화살표 */} +
+ +
+ + {/* 대상 필드 (폼) */} +
+ + { + const mappings = [...(localConfig.autoFillMappings || [])]; + mappings[index] = { ...mappings[index], targetField: e.target.value }; + updateConfig({ autoFillMappings: mappings }); + }} + placeholder="폼 필드명" + className="h-8 text-xs" + /> +
+ + {/* 삭제 버튼 */} + +
+ ))} +
+ )} + + {(localConfig.autoFillMappings || []).length === 0 && ( +
+ 매핑이 없습니다. + 추가 버튼을 클릭하여 매핑을 추가하세요. +
+ )} +
); } diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchInputWrapper.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchInputWrapper.tsx index dd6ed5c4..f8a3a22e 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchInputWrapper.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchInputWrapper.tsx @@ -37,6 +37,9 @@ export const EntitySearchInputWrapper: React.FC = ({ // placeholder const placeholder = config.placeholder || widget?.placeholder || "항목을 선택하세요"; + + // 자동 채움 매핑 설정 + const autoFillMappings = config.autoFillMappings || []; console.log("🏢 EntitySearchInputWrapper 렌더링:", { tableName, @@ -44,6 +47,7 @@ export const EntitySearchInputWrapper: React.FC = ({ valueField, uiMode, multiple, + autoFillMappings, value, config, }); @@ -68,6 +72,7 @@ export const EntitySearchInputWrapper: React.FC = ({ value={value} onChange={onChange} multiple={multiple} + autoFillMappings={autoFillMappings} component={component} isInteractive={props.isInteractive} onFormDataChange={props.onFormDataChange} diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx index 555efe9b..422dfbfa 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx @@ -148,9 +148,9 @@ export function EntitySearchModal({ 선택 )} - {displayColumns.map((col) => ( + {displayColumns.map((col, colIdx) => ( {col} @@ -179,7 +179,8 @@ export function EntitySearchModal({ ) : ( results.map((item, index) => { - const uniqueKey = item[valueField] !== undefined ? `${item[valueField]}` : `row-${index}`; + // null과 undefined 모두 체크하여 유니크 키 생성 + const uniqueKey = item[valueField] != null ? `${item[valueField]}` : `row-${index}`; const isSelected = isItemSelected(item); return ( )} - {displayColumns.map((col) => ( - + {displayColumns.map((col, colIdx) => ( + {item[col] || "-"} ))} diff --git a/frontend/lib/registry/components/entity-search-input/config.ts b/frontend/lib/registry/components/entity-search-input/config.ts index fab81c9f..3dae8779 100644 --- a/frontend/lib/registry/components/entity-search-input/config.ts +++ b/frontend/lib/registry/components/entity-search-input/config.ts @@ -1,3 +1,9 @@ +// 자동 채움 매핑 타입 +export interface AutoFillMapping { + sourceField: string; // 선택된 엔티티의 필드 (예: customer_name) + targetField: string; // 폼의 필드 (예: partner_name) +} + export interface EntitySearchInputConfig { tableName: string; displayField: string; @@ -18,5 +24,8 @@ export interface EntitySearchInputConfig { cascadingRelationCode?: string; // 연쇄관계 코드 (WAREHOUSE_LOCATION 등) cascadingRole?: "parent" | "child"; // 역할 (부모/자식) cascadingParentField?: string; // 부모 필드의 컬럼명 (자식 역할일 때만 사용) + + // 자동 채움 매핑 설정 + autoFillMappings?: AutoFillMapping[]; // 엔티티 선택 시 다른 필드에 자동으로 값 채우기 }