"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Link } from "lucide-react"; import { DataFlowAPI, TableRelationship, TableInfo, ColumnInfo, ConditionNode } from "@/lib/api/dataflow"; import { ConnectionConfig, SimpleKeySettings, DataSaveSettings, ExternalCallSettings, ConnectionSetupModalProps, } from "@/types/connectionTypes"; import { isConditionalConnection } from "@/utils/connectionUtils"; import { useConditionManager } from "@/hooks/useConditionManager"; import { ConditionalSettings } from "./condition/ConditionalSettings"; import { ConnectionTypeSelector } from "./connection/ConnectionTypeSelector"; import { SimpleKeySettings as SimpleKeySettingsComponent } from "./connection/SimpleKeySettings"; import { DataSaveSettings as DataSaveSettingsComponent } from "./connection/DataSaveSettings"; import { ExternalCallSettings as ExternalCallSettingsComponent } from "./connection/ExternalCallSettings"; import toast from "react-hot-toast"; export const ConnectionSetupModal: React.FC = ({ isOpen, connection, companyCode, onConfirm, onCancel, }) => { const [config, setConfig] = useState({ relationshipName: "", connectionType: "simple-key", fromColumnName: "", toColumnName: "", settings: {}, }); // 연결 종류별 설정 상태 const [simpleKeySettings, setSimpleKeySettings] = useState({ notes: "", }); const [dataSaveSettings, setDataSaveSettings] = useState({ actions: [], }); const [externalCallSettings, setExternalCallSettings] = useState({ callType: "rest-api", apiUrl: "", httpMethod: "POST", headers: "{}", bodyTemplate: "{}", }); // 테이블 및 컬럼 선택을 위한 상태들 const [availableTables, setAvailableTables] = useState([]); const [selectedFromTable, setSelectedFromTable] = useState(""); const [selectedToTable, setSelectedToTable] = useState(""); const [fromTableColumns, setFromTableColumns] = useState([]); const [toTableColumns, setToTableColumns] = useState([]); const [selectedFromColumns, setSelectedFromColumns] = useState([]); const [selectedToColumns, setSelectedToColumns] = useState([]); const [tableColumnsCache, setTableColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({}); // 조건 관리 훅 사용 const { conditions, setConditions, addCondition, addGroupStart, addGroupEnd, updateCondition, removeCondition, getCurrentGroupLevel, } = useConditionManager(); // 기존 설정 로드 함수 const loadExistingSettings = useCallback( (settings: Record, connectionType: string) => { if (connectionType === "simple-key" && settings.notes) { setSimpleKeySettings({ notes: settings.notes as string, }); } else if (connectionType === "data-save" && settings.actions) { // data-save 설정 로드 - 안전하게 처리 const actionsData = Array.isArray(settings.actions) ? settings.actions : []; setDataSaveSettings({ actions: actionsData.map((action: Record) => ({ id: (action.id as string) || `action-${Date.now()}`, name: (action.name as string) || "새 액션", actionType: (action.actionType as "insert" | "update" | "delete" | "upsert") || "insert", conditions: Array.isArray(action.conditions) ? (action.conditions as ConditionNode[]).map((condition) => ({ ...condition, operator: condition.operator || "=", // 기본값 보장 })) : [], fieldMappings: Array.isArray(action.fieldMappings) ? action.fieldMappings.map((mapping: Record) => ({ sourceTable: (mapping.sourceTable as string) || "", sourceField: (mapping.sourceField as string) || "", targetTable: (mapping.targetTable as string) || "", targetField: (mapping.targetField as string) || "", defaultValue: (mapping.defaultValue as string) || "", transformFunction: (mapping.transformFunction as string) || "", })) : [], splitConfig: action.splitConfig ? { sourceField: ((action.splitConfig as Record).sourceField as string) || "", delimiter: ((action.splitConfig as Record).delimiter as string) || ",", targetField: ((action.splitConfig as Record).targetField as string) || "", } : undefined, })), }); // 전체 실행 조건 로드 if (settings.control) { const controlSettings = settings.control as { conditionTree?: ConditionNode[] }; if (Array.isArray(controlSettings.conditionTree)) { setConditions(controlSettings.conditionTree || []); } } } else if (connectionType === "external-call") { setExternalCallSettings({ callType: (settings.callType as "rest-api" | "webhook") || "rest-api", apiUrl: (settings.apiUrl as string) || "", httpMethod: (settings.httpMethod as "GET" | "POST" | "PUT" | "DELETE") || "POST", headers: (settings.headers as string) || "{}", bodyTemplate: (settings.bodyTemplate as string) || "{}", }); } }, [setConditions, setSimpleKeySettings, setDataSaveSettings, setExternalCallSettings], ); // 테이블 목록 로드 useEffect(() => { const loadTables = async () => { try { const tables = await DataFlowAPI.getTables(); setAvailableTables(tables); } catch (error) { console.error("테이블 목록 로드 실패:", error); toast.error("테이블 목록을 불러오는데 실패했습니다."); } }; if (isOpen) { loadTables(); } }, [isOpen]); // 모달이 열릴 때 기본값 설정 useEffect(() => { if (isOpen && connection) { const fromTableName = connection.fromNode.tableName; const toTableName = connection.toNode.tableName; const fromDisplayName = connection.fromNode.displayName; const toDisplayName = connection.toNode.displayName; // 테이블 선택 설정 setSelectedFromTable(fromTableName); setSelectedToTable(toTableName); // 기존 관계 정보가 있으면 사용, 없으면 기본값 설정 const existingRel = connection.existingRelationship; const connectionType = (existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key"; setConfig({ relationshipName: existingRel?.relationshipName || `${fromDisplayName} → ${toDisplayName}`, connectionType, fromColumnName: "", toColumnName: "", settings: existingRel?.settings || {}, }); // 기존 설정 데이터 로드 if (existingRel?.settings) { loadExistingSettings(existingRel.settings, connectionType); } else { // 기본값 설정 setSimpleKeySettings({ notes: `${fromDisplayName}과 ${toDisplayName} 간의 키값 연결`, }); setDataSaveSettings({ actions: [] }); } // 필드 선택 상태 초기화 setSelectedFromColumns([]); setSelectedToColumns([]); // 선택된 컬럼 정보가 있다면 설정 if (connection.selectedColumnsData) { const fromColumns = connection.selectedColumnsData[fromTableName]?.columns || []; const toColumns = connection.selectedColumnsData[toTableName]?.columns || []; setSelectedFromColumns(fromColumns); setSelectedToColumns(toColumns); setConfig((prev) => ({ ...prev, fromColumnName: fromColumns.join(", "), toColumnName: toColumns.join(", "), })); } } }, [isOpen, connection, setConditions, loadExistingSettings]); // From 테이블 선택 시 컬럼 로드 useEffect(() => { const loadFromColumns = async () => { if (selectedFromTable) { try { const columns = await DataFlowAPI.getTableColumns(selectedFromTable); setFromTableColumns(columns); } catch (error) { console.error("From 테이블 컬럼 로드 실패:", error); toast.error("From 테이블 컬럼을 불러오는데 실패했습니다."); } } }; loadFromColumns(); }, [selectedFromTable]); // To 테이블 선택 시 컬럼 로드 useEffect(() => { const loadToColumns = async () => { if (selectedToTable) { try { const columns = await DataFlowAPI.getTableColumns(selectedToTable); setToTableColumns(columns); } catch (error) { console.error("To 테이블 컬럼 로드 실패:", error); toast.error("To 테이블 컬럼을 불러오는데 실패했습니다."); } } }; loadToColumns(); }, [selectedToTable]); // 선택된 컬럼들이 변경될 때 config 업데이트 useEffect(() => { setConfig((prev) => ({ ...prev, fromColumnName: selectedFromColumns.join(", "), toColumnName: selectedToColumns.join(", "), })); }, [selectedFromColumns, selectedToColumns]); // 테이블 컬럼 로드 함수 (캐시 활용) const loadTableColumns = async (tableName: string): Promise => { if (tableColumnsCache[tableName]) { return tableColumnsCache[tableName]; } try { const columns = await DataFlowAPI.getTableColumns(tableName); setTableColumnsCache((prev) => ({ ...prev, [tableName]: columns, })); return columns; } catch (error) { console.error(`${tableName} 컬럼 로드 실패:`, error); return []; } }; // 테이블 선택 시 컬럼 로드 useEffect(() => { const loadColumns = async () => { const tablesToLoad = new Set(); // 필드 매핑에서 사용되는 모든 테이블 수집 dataSaveSettings.actions?.forEach((action) => { action.fieldMappings?.forEach((mapping) => { if (mapping.sourceTable && !tableColumnsCache[mapping.sourceTable]) { tablesToLoad.add(mapping.sourceTable); } if (mapping.targetTable && !tableColumnsCache[mapping.targetTable]) { tablesToLoad.add(mapping.targetTable); } }); }); // 필요한 테이블들의 컬럼만 로드 for (const tableName of tablesToLoad) { await loadTableColumns(tableName); } }; loadColumns(); }, [dataSaveSettings.actions, tableColumnsCache]); // eslint-disable-line react-hooks/exhaustive-deps const handleConfirm = () => { if (!config.relationshipName || !connection) { toast.error("필수 정보를 모두 입력해주세요."); return; } // 연결 종류별 설정을 준비 let settings = {}; switch (config.connectionType) { case "simple-key": settings = simpleKeySettings; break; case "data-save": settings = dataSaveSettings; break; case "external-call": settings = externalCallSettings; break; } // 단순 키값 연결일 때만 컬럼 선택 검증 if (config.connectionType === "simple-key") { if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); return; } } // 선택된 테이블과 컬럼 정보 사용 const fromTableName = selectedFromTable || connection.fromNode.tableName; const toTableName = selectedToTable || connection.toNode.tableName; // 조건부 연결 설정 데이터 준비 const conditionalSettings = isConditionalConnection(config.connectionType) ? { control: { triggerType: "insert", conditionTree: conditions.length > 0 ? conditions : null, }, category: { type: config.connectionType, }, plan: { sourceTable: fromTableName, targetActions: config.connectionType === "data-save" ? dataSaveSettings.actions.map((action) => ({ id: action.id, actionType: action.actionType, enabled: true, conditions: action.conditions?.map((condition) => { // 모든 조건 타입에 대해 operator 필드 보장 const baseCondition = { ...condition }; if (condition.type === "condition") { baseCondition.operator = condition.operator || "="; } return baseCondition; }) || [], fieldMappings: action.fieldMappings.map((mapping) => ({ sourceTable: mapping.sourceTable, sourceField: mapping.sourceField, targetTable: mapping.targetTable, targetField: mapping.targetField, defaultValue: mapping.defaultValue, transformFunction: mapping.transformFunction, })), splitConfig: action.splitConfig, })) : [], }, } : {}; // 컬럼 정보는 단순 키값 연결일 때만 사용 const finalFromColumns = config.connectionType === "simple-key" ? selectedFromColumns : []; const finalToColumns = config.connectionType === "simple-key" ? selectedToColumns : []; // 메모리 기반 시스템: 관계 데이터만 생성하여 부모로 전달 const relationshipData: TableRelationship = { relationship_name: config.relationshipName, from_table_name: fromTableName, to_table_name: toTableName, from_column_name: finalFromColumns.join(","), // 여러 컬럼을 콤마로 구분 to_column_name: finalToColumns.join(","), // 여러 컬럼을 콤마로 구분 connection_type: config.connectionType, company_code: companyCode, settings: { ...settings, ...conditionalSettings, // 조건부 연결 설정 추가 }, }; toast.success("관계가 생성되었습니다!"); // 부모 컴포넌트로 관계 데이터 전달 (DB 저장 없이) onConfirm(relationshipData); handleCancel(); // 모달 닫기 }; const handleCancel = () => { setConfig({ relationshipName: "", connectionType: "simple-key", fromColumnName: "", toColumnName: "", }); onCancel(); }; // 연결 종류별 설정 패널 렌더링 const renderConnectionTypeSettings = () => { switch (config.connectionType) { case "simple-key": return ( ); case "data-save": return ( ); case "external-call": return ( ); default: return null; } }; const isButtonDisabled = () => { const hasRelationshipName = !!config.relationshipName; const isDataSave = config.connectionType === "data-save"; const hasActions = dataSaveSettings.actions.length > 0; const allActionsHaveMappings = dataSaveSettings.actions.every((action) => action.fieldMappings.length > 0); const allMappingsComplete = dataSaveSettings.actions.every((action) => action.fieldMappings.every((mapping) => { // 타겟은 항상 필요 if (!mapping.targetTable || !mapping.targetField) return false; // 소스와 기본값 중 하나는 있어야 함 const hasSource = mapping.sourceTable && mapping.sourceField; const hasDefault = mapping.defaultValue && mapping.defaultValue.trim(); // FROM 테이블이 비어있으면 기본값이 필요 if (!mapping.sourceTable) { return !!hasDefault; } // FROM 테이블이 있으면 소스 매핑 완성 또는 기본값 필요 return hasSource || hasDefault; }), ); return !hasRelationshipName || (isDataSave && (!hasActions || !allActionsHaveMappings || !allMappingsComplete)); }; if (!connection) return null; return ( 필드 연결 설정
{/* 기본 연결 설정 */}
setConfig({ ...config, relationshipName: e.target.value })} placeholder="employee_id_department_id_연결" className="text-sm" />
{/* 연결 종류 선택 */} {/* 조건부 연결을 위한 조건 설정 */} {isConditionalConnection(config.connectionType) && ( )} {/* 연결 종류별 상세 설정 */} {renderConnectionTypeSettings()}
); };