"use client"; import React from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Button } from "@/components/ui/button"; import { Check, ChevronsUpDown, Plus, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { QuickInsertConfigSection } from "../QuickInsertConfigSection"; import { ComponentData } from "@/types/screen"; export interface DataTabProps { config: any; onChange: (key: string, value: any) => void; component: ComponentData; allComponents: ComponentData[]; currentTableName?: string; availableTables: Array<{ name: string; label: string }>; mappingTargetColumns: Array<{ name: string; label: string }>; mappingSourceColumnsMap: Record>; currentTableColumns: Array<{ name: string; label: string }>; mappingSourcePopoverOpen: Record; setMappingSourcePopoverOpen: React.Dispatch>>; mappingTargetPopoverOpen: Record; setMappingTargetPopoverOpen: React.Dispatch>>; activeMappingGroupIndex: number; setActiveMappingGroupIndex: React.Dispatch>; loadMappingColumns: (tableName: string) => Promise>; setMappingSourceColumnsMap: React.Dispatch< React.SetStateAction>> >; } export const DataTab: React.FC = ({ config, onChange, component, allComponents, currentTableName, availableTables, mappingTargetColumns, mappingSourceColumnsMap, currentTableColumns, mappingSourcePopoverOpen, setMappingSourcePopoverOpen, mappingTargetPopoverOpen, setMappingTargetPopoverOpen, activeMappingGroupIndex, setActiveMappingGroupIndex, loadMappingColumns, setMappingSourceColumnsMap, }) => { const actionType = config.action?.type; const onUpdateProperty = (path: string, value: any) => onChange(path, value); if (actionType === "quickInsert") { return (
); } if (actionType !== "transferData") { return (
데이터 전달 또는 즉시 저장 액션을 선택하면 설정할 수 있습니다.
); } return (

데이터 전달 설정

레이어별로 다른 테이블이 있을 경우 "자동 탐색"을 선택하면 현재 활성화된 레이어의 테이블을 자동으로 사용합니다

{config.action?.dataTransfer?.targetType === "splitPanel" && (

이 버튼이 분할 패널 내부에 있어야 합니다. 좌측 화면에서 우측으로, 또는 우측에서 좌측으로 데이터가 전달됩니다.

)}
{config.action?.dataTransfer?.targetType === "component" && (

테이블, 반복 필드 그룹 등 데이터를 받는 컴포넌트

)} {config.action?.dataTransfer?.targetType === "splitPanel" && (
onUpdateProperty("componentConfig.action.dataTransfer.targetComponentId", e.target.value) } placeholder="비워두면 첫 번째 수신 가능 컴포넌트로 전달" className="h-8 text-xs" />

반대편 화면의 특정 컴포넌트 ID를 지정하거나, 비워두면 자동으로 첫 번째 수신 가능 컴포넌트로 전달됩니다.

)}

기존 데이터를 어떻게 처리할지 선택

데이터 전달 후 소스의 선택을 해제합니다

onUpdateProperty("componentConfig.action.dataTransfer.clearAfterTransfer", checked) } />

데이터 전달 전 확인 다이얼로그를 표시합니다

onUpdateProperty("componentConfig.action.dataTransfer.confirmBeforeTransfer", checked) } />
{config.action?.dataTransfer?.confirmBeforeTransfer && (
onUpdateProperty("componentConfig.action.dataTransfer.confirmMessage", e.target.value)} className="h-8 text-xs" />
)}
onUpdateProperty( "componentConfig.action.dataTransfer.validation.minSelection", parseInt(e.target.value) || 0, ) } className="h-8 w-20 text-xs" />
onUpdateProperty( "componentConfig.action.dataTransfer.validation.maxSelection", parseInt(e.target.value) || undefined, ) } className="h-8 w-20 text-xs" />

조건부 컨테이너의 카테고리 값 등 추가 데이터를 함께 전달할 수 있습니다

조건부 컨테이너, 셀렉트박스 등 (카테고리 값 전달용)

컬럼을 찾을 수 없습니다. { const currentSources = config.action?.dataTransfer?.additionalSources || []; const newSources = [...currentSources]; if (newSources.length === 0) { newSources.push({ componentId: "", fieldName: "" }); } else { newSources[0] = { ...newSources[0], fieldName: "" }; } onUpdateProperty("componentConfig.action.dataTransfer.additionalSources", newSources); }} className="text-xs" > 선택 안 함 (전체 데이터 병합) {(mappingTargetColumns.length > 0 ? mappingTargetColumns : currentTableColumns).map((col) => ( { const currentSources = config.action?.dataTransfer?.additionalSources || []; const newSources = [...currentSources]; if (newSources.length === 0) { newSources.push({ componentId: "", fieldName: col.name }); } else { newSources[0] = { ...newSources[0], fieldName: col.name }; } onUpdateProperty("componentConfig.action.dataTransfer.additionalSources", newSources); }} className="text-xs" > {col.label || col.name} {col.label && col.label !== col.name && ( ({col.name}) )} ))}

추가 데이터가 저장될 타겟 테이블 컬럼

테이블을 찾을 수 없습니다 {availableTables.map((table) => ( { onUpdateProperty("componentConfig.action.dataTransfer.targetTable", table.name); }} className="text-xs" > {table.label} ({table.name}) ))}

여러 소스 테이블에서 데이터를 전달할 때, 각 테이블별로 매핑 규칙을 설정합니다. 런타임에 소스 테이블을 자동 감지합니다.

{!config.action?.dataTransfer?.targetTable ? (

먼저 타겟 테이블을 선택하세요.

) : !(config.action?.dataTransfer?.multiTableMappings || []).length ? (

매핑 그룹이 없습니다. 소스 테이블을 추가하세요.

) : (
{(config.action?.dataTransfer?.multiTableMappings || []).map((group: any, gIdx: number) => (
))}
{(() => { const multiMappings = config.action?.dataTransfer?.multiTableMappings || []; const activeGroup = multiMappings[activeMappingGroupIndex]; if (!activeGroup) return null; const activeSourceTable = activeGroup.sourceTable || ""; const activeSourceColumns = mappingSourceColumnsMap[activeSourceTable] || []; const activeRules: any[] = activeGroup.mappingRules || []; const updateGroupField = (field: string, value: any) => { const mappings = [...multiMappings]; mappings[activeMappingGroupIndex] = { ...mappings[activeMappingGroupIndex], [field]: value }; onUpdateProperty("componentConfig.action.dataTransfer.multiTableMappings", mappings); }; return (
테이블을 찾을 수 없습니다 {availableTables.map((table) => ( { updateGroupField("sourceTable", table.name); if (!mappingSourceColumnsMap[table.name]) { const cols = await loadMappingColumns(table.name); setMappingSourceColumnsMap((prev) => ({ ...prev, [table.name]: cols })); } }} className="text-xs" > {table.label} ({table.name}) ))}
{!activeSourceTable ? (

소스 테이블을 먼저 선택하세요.

) : activeRules.length === 0 ? (

매핑 없음 (동일 필드명 자동 매핑)

) : ( activeRules.map((rule: any, rIdx: number) => { const popoverKeyS = `${activeMappingGroupIndex}-${rIdx}-s`; const popoverKeyT = `${activeMappingGroupIndex}-${rIdx}-t`; return (
setMappingSourcePopoverOpen((prev) => ({ ...prev, [popoverKeyS]: open })) } > 컬럼 없음 {activeSourceColumns.map((col) => ( { const newRules = [...activeRules]; newRules[rIdx] = { ...newRules[rIdx], sourceField: col.name }; updateGroupField("mappingRules", newRules); setMappingSourcePopoverOpen((prev) => ({ ...prev, [popoverKeyS]: false, })); }} className="text-xs" > {col.label} {col.label !== col.name && ( ({col.name}) )} ))}
setMappingTargetPopoverOpen((prev) => ({ ...prev, [popoverKeyT]: open })) } > 컬럼 없음 {mappingTargetColumns.map((col) => ( { const newRules = [...activeRules]; newRules[rIdx] = { ...newRules[rIdx], targetField: col.name }; updateGroupField("mappingRules", newRules); setMappingTargetPopoverOpen((prev) => ({ ...prev, [popoverKeyT]: false, })); }} className="text-xs" > {col.label} {col.label !== col.name && ( ({col.name}) )} ))}
); }) )}
); })()}
)}

사용 방법:
1. 소스 컴포넌트에서 데이터를 선택합니다
2. 소스 테이블별로 필드 매핑 규칙을 설정합니다
3. 이 버튼을 클릭하면 소스 테이블을 자동 감지하여 매핑된 데이터가 타겟으로 전달됩니다

); };