제어관리 외부 커넥션 설정기능
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
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";
|
||||
@@ -8,16 +8,24 @@ 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[];
|
||||
fromTableColumns?: ColumnInfo[];
|
||||
toTableColumns?: ColumnInfo[];
|
||||
fromTableName?: string;
|
||||
toTableName?: string;
|
||||
// 다중 커넥션 지원
|
||||
fromConnectionId?: number;
|
||||
toConnectionId?: number;
|
||||
}
|
||||
|
||||
interface ColumnMapping {
|
||||
@@ -31,15 +39,26 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
actionIndex,
|
||||
settings,
|
||||
onSettingsChange,
|
||||
fromTableColumns,
|
||||
toTableColumns,
|
||||
fromTableColumns = [],
|
||||
toTableColumns = [],
|
||||
fromTableName,
|
||||
toTableName,
|
||||
fromConnectionId,
|
||||
toConnectionId,
|
||||
}) => {
|
||||
const [selectedFromColumn, setSelectedFromColumn] = useState<string | null>(null);
|
||||
const [selectedToColumn, setSelectedToColumn] = useState<string | null>(null);
|
||||
const [columnMappings, setColumnMappings] = useState<ColumnMapping[]>([]);
|
||||
|
||||
// 다중 커넥션에서 로드한 컬럼 정보
|
||||
const [multiFromColumns, setMultiFromColumns] = useState<MultiColumnInfo[]>([]);
|
||||
const [multiToColumns, setMultiToColumns] = useState<MultiColumnInfo[]>([]);
|
||||
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
|
||||
|
||||
// 테이블 라벨명 정보
|
||||
const [fromTableDisplayName, setFromTableDisplayName] = useState<string>("");
|
||||
const [toTableDisplayName, setToTableDisplayName] = useState<string>("");
|
||||
|
||||
// 검색 및 필터링 상태 (FROM과 TO 독립적)
|
||||
const [fromSearchTerm, setFromSearchTerm] = useState("");
|
||||
const [toSearchTerm, setToSearchTerm] = useState("");
|
||||
@@ -54,9 +73,84 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
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 mappings: ColumnMapping[] = toTableColumns.map((toCol) => {
|
||||
const columnsToUse = multiToColumns.length > 0 ? multiToColumns : toTableColumns || [];
|
||||
|
||||
const mappings: ColumnMapping[] = columnsToUse.map((toCol) => {
|
||||
const existingMapping = action.fieldMappings.find((mapping) => mapping.targetField === toCol.columnName);
|
||||
|
||||
return {
|
||||
@@ -67,7 +161,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
});
|
||||
|
||||
setColumnMappings(mappings);
|
||||
}, [action.fieldMappings, toTableColumns]);
|
||||
}, [action.fieldMappings, multiToColumns.length, toTableColumns?.length]);
|
||||
|
||||
// columnMappings 변경 시 settings 업데이트
|
||||
const updateSettings = (newMappings: ColumnMapping[]) => {
|
||||
@@ -209,7 +303,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
|
||||
if (!selectedToColumn) return true; // TO가 선택되지 않았으면 모든 FROM 클릭 가능
|
||||
|
||||
const toColumn = toTableColumns.find((col) => col.columnName === selectedToColumn);
|
||||
const toColumn = actualToColumns.find((col) => col.columnName === selectedToColumn);
|
||||
if (!toColumn) return true;
|
||||
|
||||
return fromColumn.dataType === toColumn.dataType;
|
||||
@@ -227,7 +321,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
|
||||
if (!selectedFromColumn) return true; // FROM이 선택되지 않았으면 모든 TO 클릭 가능
|
||||
|
||||
const fromColumn = fromTableColumns.find((col) => col.columnName === selectedFromColumn);
|
||||
const fromColumn = actualFromColumns.find((col) => col.columnName === selectedFromColumn);
|
||||
if (!fromColumn) return true;
|
||||
|
||||
return fromColumn.dataType === toColumn.dataType;
|
||||
@@ -244,8 +338,8 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<ColumnTableSection
|
||||
type="from"
|
||||
tableName={fromTableName || "소스 테이블"}
|
||||
columns={fromTableColumns}
|
||||
tableName={fromTableDisplayName || fromTableName || "소스 테이블"}
|
||||
columns={actualFromColumns}
|
||||
selectedColumn={selectedFromColumn}
|
||||
onColumnClick={handleFromColumnClick}
|
||||
searchTerm={fromSearchTerm}
|
||||
@@ -259,13 +353,13 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
columnMappings={columnMappings}
|
||||
isColumnClickable={isFromColumnClickable}
|
||||
oppositeSelectedColumn={selectedToColumn}
|
||||
oppositeColumns={toTableColumns}
|
||||
oppositeColumns={actualToColumns}
|
||||
/>
|
||||
|
||||
<ColumnTableSection
|
||||
type="to"
|
||||
tableName={toTableName || "대상 테이블"}
|
||||
columns={toTableColumns}
|
||||
tableName={toTableDisplayName || toTableName || "대상 테이블"}
|
||||
columns={actualToColumns}
|
||||
selectedColumn={selectedToColumn}
|
||||
onColumnClick={handleToColumnClick}
|
||||
searchTerm={toSearchTerm}
|
||||
@@ -281,7 +375,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
onRemoveMapping={handleRemoveMapping}
|
||||
isColumnClickable={isToColumnClickable}
|
||||
oppositeSelectedColumn={selectedFromColumn}
|
||||
oppositeColumns={fromTableColumns}
|
||||
oppositeColumns={actualFromColumns}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -336,10 +430,10 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
</Button>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
FROM: {fromTableColumns.length}
|
||||
FROM: {actualFromColumns.length}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
TO: {toTableColumns.length}
|
||||
TO: {actualToColumns.length}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -366,7 +460,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
<div className="text-2xl font-bold text-gray-800">
|
||||
{Math.round(
|
||||
(columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length /
|
||||
toTableColumns.length) *
|
||||
actualToColumns.length) *
|
||||
100,
|
||||
)}
|
||||
%
|
||||
@@ -378,7 +472,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
||||
<Progress
|
||||
value={
|
||||
(columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length /
|
||||
toTableColumns.length) *
|
||||
actualToColumns.length) *
|
||||
100
|
||||
}
|
||||
className="h-2"
|
||||
|
||||
Reference in New Issue
Block a user