"use client"; import React, { useState, useEffect, useMemo } from "react"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ColumnInfo } from "@/lib/api/dataflow"; import { DataSaveSettings } from "@/types/connectionTypes"; import { ColumnTableSection } from "./ColumnTableSection"; import { getColumnsFromConnection, getTablesFromConnection, ColumnInfo as MultiColumnInfo, } from "@/lib/api/multiConnection"; interface InsertFieldMappingPanelProps { action: DataSaveSettings["actions"][0]; actionIndex: number; settings: DataSaveSettings; onSettingsChange: (settings: DataSaveSettings) => void; fromTableColumns?: ColumnInfo[]; toTableColumns?: ColumnInfo[]; fromTableName?: string; toTableName?: string; // 다중 커넥션 지원 fromConnectionId?: number; toConnectionId?: number; } interface ColumnMapping { toColumnName: string; fromColumnName?: string; defaultValue?: string; } export const InsertFieldMappingPanel: React.FC = ({ action, actionIndex, settings, onSettingsChange, fromTableColumns = [], toTableColumns = [], fromTableName, toTableName, fromConnectionId, toConnectionId, }) => { const [selectedFromColumn, setSelectedFromColumn] = useState(null); const [selectedToColumn, setSelectedToColumn] = useState(null); const [columnMappings, setColumnMappings] = useState([]); // 다중 커넥션에서 로드한 컬럼 정보 const [multiFromColumns, setMultiFromColumns] = useState([]); const [multiToColumns, setMultiToColumns] = useState([]); const [isLoadingColumns, setIsLoadingColumns] = useState(false); // 테이블 라벨명 정보 const [fromTableDisplayName, setFromTableDisplayName] = useState(""); const [toTableDisplayName, setToTableDisplayName] = useState(""); // 검색 및 필터링 상태 (FROM과 TO 독립적) const [fromSearchTerm, setFromSearchTerm] = useState(""); const [toSearchTerm, setToSearchTerm] = useState(""); const [fromDataTypeFilter, setFromDataTypeFilter] = useState(""); const [toDataTypeFilter, setToDataTypeFilter] = useState(""); // FROM 테이블 필터 const [fromShowMappedOnly, setFromShowMappedOnly] = useState(false); const [fromShowUnmappedOnly, setFromShowUnmappedOnly] = useState(false); // TO 테이블 필터 const [toShowMappedOnly, setToShowMappedOnly] = useState(false); const [toShowUnmappedOnly, setToShowUnmappedOnly] = useState(false); // 다중 커넥션에서 컬럼 정보 및 테이블 라벨명 로드 useEffect(() => { const loadColumnsAndTableInfo = async () => { if (fromConnectionId !== undefined && toConnectionId !== undefined && fromTableName && toTableName) { setIsLoadingColumns(true); try { const [fromCols, toCols, fromTables, toTables] = await Promise.all([ getColumnsFromConnection(fromConnectionId, fromTableName), getColumnsFromConnection(toConnectionId, toTableName), getTablesFromConnection(fromConnectionId), getTablesFromConnection(toConnectionId), ]); setMultiFromColumns(fromCols); setMultiToColumns(toCols); // 테이블 라벨명 설정 const fromTable = fromTables.find((t) => t.tableName === fromTableName); const toTable = toTables.find((t) => t.tableName === toTableName); setFromTableDisplayName( fromTable?.displayName && fromTable.displayName !== fromTable.tableName ? fromTable.displayName : fromTableName, ); setToTableDisplayName( toTable?.displayName && toTable.displayName !== toTable.tableName ? toTable.displayName : toTableName, ); } catch (error) { console.error("컬럼 정보 및 테이블 정보 로드 실패:", error); } finally { setIsLoadingColumns(false); } } }; loadColumnsAndTableInfo(); }, [fromConnectionId, toConnectionId, fromTableName, toTableName]); // 사용할 컬럼 데이터 결정 (다중 커넥션 > 기존) const actualFromColumns = useMemo(() => { if (multiFromColumns.length > 0) { return multiFromColumns.map((col) => ({ columnName: col.columnName, displayName: col.displayName, dataType: col.dataType, isNullable: col.isNullable, isPrimaryKey: col.isPrimaryKey, defaultValue: col.defaultValue, maxLength: col.maxLength, description: col.description, })); } return fromTableColumns || []; }, [multiFromColumns.length, fromTableColumns?.length]); const actualToColumns = useMemo(() => { if (multiToColumns.length > 0) { return multiToColumns.map((col) => ({ columnName: col.columnName, displayName: col.displayName, dataType: col.dataType, isNullable: col.isNullable, isPrimaryKey: col.isPrimaryKey, defaultValue: col.defaultValue, maxLength: col.maxLength, description: col.description, })); } return toTableColumns || []; }, [multiToColumns.length, toTableColumns?.length]); // 기존 매핑 데이터를 columnMappings로 변환 useEffect(() => { const columnsToUse = multiToColumns.length > 0 ? multiToColumns : toTableColumns || []; const mappings: ColumnMapping[] = columnsToUse.map((toCol) => { const existingMapping = action.fieldMappings.find((mapping) => mapping.targetField === toCol.columnName); return { toColumnName: toCol.columnName, fromColumnName: existingMapping?.sourceField || undefined, defaultValue: existingMapping?.defaultValue || "", }; }); setColumnMappings(mappings); }, [action.fieldMappings, multiToColumns.length, toTableColumns?.length]); // columnMappings 변경 시 settings 업데이트 const updateSettings = (newMappings: ColumnMapping[]) => { const newActions = [...settings.actions]; // 새로운 fieldMappings 생성 const fieldMappings = newMappings .filter((mapping) => mapping.fromColumnName || (mapping.defaultValue && mapping.defaultValue.trim())) .map((mapping) => ({ sourceTable: mapping.fromColumnName ? fromTableName || "" : "", sourceField: mapping.fromColumnName || "", targetTable: toTableName || "", targetField: mapping.toColumnName, defaultValue: mapping.defaultValue || "", transformFunction: "", })); newActions[actionIndex].fieldMappings = fieldMappings; onSettingsChange({ ...settings, actions: newActions }); }; // FROM 컬럼 클릭 핸들러 const handleFromColumnClick = (columnName: string) => { if (selectedFromColumn === columnName) { setSelectedFromColumn(null); } else { setSelectedFromColumn(columnName); // TO 컬럼이 이미 선택되어 있으면 매핑 시도 if (selectedToColumn) { const fromColumn = fromTableColumns.find((col) => col.columnName === columnName); const toColumn = toTableColumns.find((col) => col.columnName === selectedToColumn); if (fromColumn && toColumn) { // 데이터 타입 호환성 체크 if (fromColumn.dataType !== toColumn.dataType) { alert(`데이터 타입이 다릅니다. FROM: ${fromColumn.dataType}, TO: ${toColumn.dataType}`); return; } // 매핑 생성 createMapping(columnName, selectedToColumn); setSelectedFromColumn(null); setSelectedToColumn(null); } } } }; // 공통 매핑 생성 함수 const createMapping = (fromColumnName: string, toColumnName: string) => { const newMappings = columnMappings.map((mapping) => { if (mapping.toColumnName === toColumnName) { return { ...mapping, fromColumnName: fromColumnName, defaultValue: "", // 매핑이 설정되면 기본값 초기화 }; } return mapping; }); setColumnMappings(newMappings); updateSettings(newMappings); }; // TO 컬럼 클릭 핸들러 const handleToColumnClick = (toColumnName: string) => { const currentMapping = columnMappings.find((m) => m.toColumnName === toColumnName); // 이미 매핑된 컬럼인 경우 처리하지 않음 if (currentMapping?.fromColumnName) return; if (selectedToColumn === toColumnName) { setSelectedToColumn(null); } else { setSelectedToColumn(toColumnName); // FROM 컬럼이 이미 선택되어 있으면 매핑 시도 if (selectedFromColumn) { const fromColumn = fromTableColumns.find((col) => col.columnName === selectedFromColumn); const toColumn = toTableColumns.find((col) => col.columnName === toColumnName); if (fromColumn && toColumn) { // 데이터 타입 호환성 체크 if (fromColumn.dataType !== toColumn.dataType) { alert(`데이터 타입이 다릅니다. FROM: ${fromColumn.dataType}, TO: ${toColumn.dataType}`); return; } // 매핑 생성 createMapping(selectedFromColumn, toColumnName); setSelectedFromColumn(null); setSelectedToColumn(null); } } } }; // 기본값 변경 핸들러 const handleDefaultValueChange = (toColumnName: string, value: string) => { const newMappings = columnMappings.map((mapping) => { if (mapping.toColumnName === toColumnName) { return { ...mapping, fromColumnName: value.trim() ? undefined : mapping.fromColumnName, // 기본값이 있으면 매핑 제거 defaultValue: value, }; } return mapping; }); setColumnMappings(newMappings); updateSettings(newMappings); }; // 매핑 제거 핸들러 const handleRemoveMapping = (toColumnName: string) => { const newMappings = columnMappings.map((mapping) => { if (mapping.toColumnName === toColumnName) { return { ...mapping, fromColumnName: undefined, defaultValue: "", }; } return mapping; }); setColumnMappings(newMappings); updateSettings(newMappings); }; // FROM 컬럼이 클릭 가능한지 확인 (데이터 타입 호환성 + 1대1 매핑 제약) const isFromColumnClickable = (fromColumn: ColumnInfo) => { // 이미 다른 TO 컬럼과 매핑된 FROM 컬럼은 클릭 불가 const isAlreadyMapped = columnMappings.some((mapping) => mapping.fromColumnName === fromColumn.columnName); if (isAlreadyMapped) return false; if (!selectedToColumn) return true; // TO가 선택되지 않았으면 모든 FROM 클릭 가능 const toColumn = actualToColumns.find((col) => col.columnName === selectedToColumn); if (!toColumn) return true; return fromColumn.dataType === toColumn.dataType; }; // TO 컬럼이 클릭 가능한지 확인 (데이터 타입 호환성) const isToColumnClickable = (toColumn: ColumnInfo) => { const currentMapping = columnMappings.find((m) => m.toColumnName === toColumn.columnName); // 이미 매핑된 컬럼은 클릭 불가 if (currentMapping?.fromColumnName) return false; // 기본값이 설정된 컬럼은 클릭 불가 if (currentMapping?.defaultValue && currentMapping.defaultValue.trim()) return false; if (!selectedFromColumn) return true; // FROM이 선택되지 않았으면 모든 TO 클릭 가능 const fromColumn = actualFromColumns.find((col) => col.columnName === selectedFromColumn); if (!fromColumn) return true; return fromColumn.dataType === toColumn.dataType; }; return (
{/* 헤더 섹션 */}

양쪽 테이블의 컬럼을 클릭하여 매핑하거나, 대상 컬럼에 기본값을 입력하세요. 같은 데이터 타입의 컬럼만 매핑 가능합니다. 하나의 FROM 컬럼은 하나의 TO 컬럼에만 매핑 가능합니다.

{/* 빠른 필터 액션 */} 빠른 필터:
FROM: {actualFromColumns.length} TO: {actualToColumns.length}
{/* 매핑 통계 */}
매핑 진행 상황
총 {toTableColumns.length}개 컬럼 중{" "} {columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length} 개 {" "} 완료
{Math.round( (columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length / actualToColumns.length) * 100, )} %
완료율
m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length / actualToColumns.length) * 100 } className="h-2" />
); };