entity-search-iniput 수정
This commit is contained in:
parent
e8fc664352
commit
29a4ab7b9d
|
|
@ -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 (
|
||||
<CommandItem
|
||||
key={option[valueField] || index}
|
||||
key={option[valueField] ?? `option-${index}`}
|
||||
value={`${option[displayField] || ""}-${option[valueField] || ""}`}
|
||||
onSelect={() => handleSelectOption(option)}
|
||||
className="text-xs sm:text-sm"
|
||||
|
|
@ -509,7 +542,7 @@ export function EntitySearchInputComponent({
|
|||
<CommandGroup>
|
||||
{effectiveOptions.map((option, index) => (
|
||||
<CommandItem
|
||||
key={option[valueField] || index}
|
||||
key={option[valueField] ?? `select-option-${index}`}
|
||||
value={`${option[displayField] || ""}-${option[valueField] || ""}`}
|
||||
onSelect={() => handleSelectOption(option)}
|
||||
className="text-xs sm:text-sm"
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tableColumns.map((column) => (
|
||||
{tableColumns.map((column, idx) => (
|
||||
<CommandItem
|
||||
key={column.columnName}
|
||||
key={column.columnName || `display-col-${idx}`}
|
||||
value={`${column.displayName || column.columnName}-${column.columnName}`}
|
||||
onSelect={() => {
|
||||
updateConfig({ displayField: column.columnName });
|
||||
|
|
@ -690,9 +691,9 @@ export function EntitySearchInputConfigPanel({
|
|||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">필드를 찾을 수 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tableColumns.map((column) => (
|
||||
{tableColumns.map((column, idx) => (
|
||||
<CommandItem
|
||||
key={column.columnName}
|
||||
key={column.columnName || `value-col-${idx}`}
|
||||
value={`${column.displayName || column.columnName}-${column.columnName}`}
|
||||
onSelect={() => {
|
||||
updateConfig({ valueField: column.columnName });
|
||||
|
|
@ -812,8 +813,8 @@ export function EntitySearchInputConfigPanel({
|
|||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{tableColumns.map((col, colIdx) => (
|
||||
<SelectItem key={col.columnName || `modal-col-${colIdx}`} value={col.columnName}>
|
||||
{col.displayName || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -860,8 +861,8 @@ export function EntitySearchInputConfigPanel({
|
|||
<SelectValue placeholder="필드 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{tableColumns.map((col, colIdx) => (
|
||||
<SelectItem key={col.columnName || `search-col-${colIdx}`} value={col.columnName}>
|
||||
{col.displayName || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -919,8 +920,8 @@ export function EntitySearchInputConfigPanel({
|
|||
<SelectValue placeholder="필드 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{tableColumns.map((col, colIdx) => (
|
||||
<SelectItem key={col.columnName || `additional-col-${colIdx}`} value={col.columnName}>
|
||||
{col.displayName || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -939,6 +940,105 @@ export function EntitySearchInputConfigPanel({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 자동 채움 매핑 설정 */}
|
||||
<div className="border-t pt-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link2 className="h-4 w-4" />
|
||||
<h4 className="text-sm font-medium">자동 채움 매핑</h4>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const mappings = localConfig.autoFillMappings || [];
|
||||
updateConfig({ autoFillMappings: [...mappings, { sourceField: "", targetField: "" }] });
|
||||
}}
|
||||
className="h-7 text-xs"
|
||||
disabled={!localConfig.tableName || isLoadingColumns}
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
추가
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
엔티티를 선택하면 소스 필드의 값이 대상 필드에 자동으로 채워집니다.
|
||||
</p>
|
||||
|
||||
{(localConfig.autoFillMappings || []).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{(localConfig.autoFillMappings || []).map((mapping, index) => (
|
||||
<div key={`autofill-mapping-${index}`} className="flex items-center gap-2 rounded-md border p-2 bg-muted/30">
|
||||
{/* 소스 필드 (선택된 엔티티) */}
|
||||
<div className="flex-1">
|
||||
<Label className="text-[10px] text-muted-foreground mb-1 block">소스 (엔티티)</Label>
|
||||
<Select
|
||||
value={mapping.sourceField || ""}
|
||||
onValueChange={(value) => {
|
||||
const mappings = [...(localConfig.autoFillMappings || [])];
|
||||
mappings[index] = { ...mappings[index], sourceField: value };
|
||||
updateConfig({ autoFillMappings: mappings });
|
||||
}}
|
||||
disabled={!localConfig.tableName || isLoadingColumns}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="필드 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableColumns.map((col, colIdx) => (
|
||||
<SelectItem key={col.columnName || `col-${colIdx}`} value={col.columnName}>
|
||||
{col.displayName || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 화살표 */}
|
||||
<div className="flex items-center justify-center pt-4">
|
||||
<span className="text-muted-foreground text-sm">→</span>
|
||||
</div>
|
||||
|
||||
{/* 대상 필드 (폼) */}
|
||||
<div className="flex-1">
|
||||
<Label className="text-[10px] text-muted-foreground mb-1 block">대상 (폼)</Label>
|
||||
<Input
|
||||
value={mapping.targetField || ""}
|
||||
onChange={(e) => {
|
||||
const mappings = [...(localConfig.autoFillMappings || [])];
|
||||
mappings[index] = { ...mappings[index], targetField: e.target.value };
|
||||
updateConfig({ autoFillMappings: mappings });
|
||||
}}
|
||||
placeholder="폼 필드명"
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 삭제 버튼 */}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
const mappings = [...(localConfig.autoFillMappings || [])];
|
||||
mappings.splice(index, 1);
|
||||
updateConfig({ autoFillMappings: mappings });
|
||||
}}
|
||||
className="h-8 w-8 p-0 mt-4"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(localConfig.autoFillMappings || []).length === 0 && (
|
||||
<div className="text-muted-foreground text-xs text-center py-3 rounded-md border border-dashed">
|
||||
매핑이 없습니다. + 추가 버튼을 클릭하여 매핑을 추가하세요.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ export const EntitySearchInputWrapper: React.FC<WebTypeComponentProps> = ({
|
|||
|
||||
// placeholder
|
||||
const placeholder = config.placeholder || widget?.placeholder || "항목을 선택하세요";
|
||||
|
||||
// 자동 채움 매핑 설정
|
||||
const autoFillMappings = config.autoFillMappings || [];
|
||||
|
||||
console.log("🏢 EntitySearchInputWrapper 렌더링:", {
|
||||
tableName,
|
||||
|
|
@ -44,6 +47,7 @@ export const EntitySearchInputWrapper: React.FC<WebTypeComponentProps> = ({
|
|||
valueField,
|
||||
uiMode,
|
||||
multiple,
|
||||
autoFillMappings,
|
||||
value,
|
||||
config,
|
||||
});
|
||||
|
|
@ -68,6 +72,7 @@ export const EntitySearchInputWrapper: React.FC<WebTypeComponentProps> = ({
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
autoFillMappings={autoFillMappings}
|
||||
component={component}
|
||||
isInteractive={props.isInteractive}
|
||||
onFormDataChange={props.onFormDataChange}
|
||||
|
|
|
|||
|
|
@ -148,9 +148,9 @@ export function EntitySearchModal({
|
|||
선택
|
||||
</th>
|
||||
)}
|
||||
{displayColumns.map((col) => (
|
||||
{displayColumns.map((col, colIdx) => (
|
||||
<th
|
||||
key={col}
|
||||
key={col || `header-${colIdx}`}
|
||||
className="px-4 py-2 text-left font-medium text-muted-foreground"
|
||||
>
|
||||
{col}
|
||||
|
|
@ -179,7 +179,8 @@ export function EntitySearchModal({
|
|||
</tr>
|
||||
) : (
|
||||
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 (
|
||||
<tr
|
||||
|
|
@ -200,8 +201,8 @@ export function EntitySearchModal({
|
|||
/>
|
||||
</td>
|
||||
)}
|
||||
{displayColumns.map((col) => (
|
||||
<td key={`${uniqueKey}-${col}`} className="px-4 py-2">
|
||||
{displayColumns.map((col, colIdx) => (
|
||||
<td key={`${uniqueKey}-${col || colIdx}`} className="px-4 py-2">
|
||||
{item[col] || "-"}
|
||||
</td>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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[]; // 엔티티 선택 시 다른 필드에 자동으로 값 채우기
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue