"use client"; import React, { useCallback, useEffect } from "react"; import toast from "react-hot-toast"; import { ReactFlow, Controls, Background, BackgroundVariant, SelectionMode, Node, Edge } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { TableNode } from "./TableNode"; import { ConnectionSetupModal } from "./ConnectionSetupModal"; import { DataFlowSidebar } from "./DataFlowSidebar"; import { SelectedTablesPanel } from "./SelectedTablesPanel"; import { RelationshipListModal } from "./RelationshipListModal"; import { EdgeInfoPanel } from "./EdgeInfoPanel"; import SaveDiagramModal from "./SaveDiagramModal"; import { TableDefinition, DataFlowAPI, JsonRelationship, TableRelationship, CreateDiagramRequest, } from "@/lib/api/dataflow"; import { useAuth } from "@/hooks/useAuth"; import { useDataFlowDesigner } from "@/hooks/useDataFlowDesigner"; import { DataFlowDesignerProps, TableNodeData } from "@/types/dataflowTypes"; import { extractTableNames, extractNodePositions } from "@/utils/dataflowUtils"; // 노드 및 엣지 타입 정의 const nodeTypes = { tableNode: TableNode, }; const edgeTypes = {}; export const DataFlowDesigner: React.FC = ({ companyCode: propCompanyCode = "*", diagramId, }) => { const { user: authUser } = useAuth(); // 실제 사용자 회사 코드 사용 (prop보다 사용자 정보 우선) const companyCode = authUser?.company_code || authUser?.companyCode || propCompanyCode; // 커스텀 훅 사용 const { nodes, setNodes, onNodesChange, edges, setEdges, onEdgesChange, selectedColumns, setSelectedColumns, selectedNodes, setSelectedNodes, pendingConnection, setPendingConnection, currentDiagramId, setCurrentDiagramId, currentDiagramName, setCurrentDiagramName, currentDiagramCategory, tempRelationships, setTempRelationships, hasUnsavedChanges, setHasUnsavedChanges, showSaveModal, setShowSaveModal, isSaving, setIsSaving, showRelationshipListModal, setShowRelationshipListModal, selectedTablePairRelationships, setSelectedTablePairRelationships, selectedEdgeInfo, setSelectedEdgeInfo, setSelectedEdgeForEdit, showEdgeActions, setShowEdgeActions, edgeActionPosition, editingRelationshipId, setEditingRelationshipId, } = useDataFlowDesigner(); // 컬럼 클릭 처리 비활성화 (테이블 노드 선택 방식으로 변경) const handleColumnClick = useCallback((tableName: string, columnName: string) => { // 컬럼 클릭으로는 더 이상 선택하지 않음 console.log(`컬럼 클릭 무시됨: ${tableName}.${columnName}`); return; }, []); // 편집 모드일 때 관계 데이터 로드 useEffect(() => { const loadDiagramData = async () => { if (diagramId && diagramId > 0) { try { // 편집 모드일 때 currentDiagramId 설정 setCurrentDiagramId(diagramId); const jsonDiagram = await DataFlowAPI.getJsonDataFlowDiagramById(diagramId, companyCode); if (jsonDiagram) { // 관계 이름 설정 if (jsonDiagram.diagram_name) { setCurrentDiagramName(jsonDiagram.diagram_name); } // 관계 데이터 로드 if (jsonDiagram.relationships?.relationships && Array.isArray(jsonDiagram.relationships.relationships)) { const loadedRelationships = jsonDiagram.relationships.relationships.map((rel) => ({ id: rel.id || `rel-${Date.now()}-${Math.random()}`, fromTable: rel.fromTable, toTable: rel.toTable, fromColumns: Array.isArray(rel.fromColumns) ? rel.fromColumns : [], toColumns: Array.isArray(rel.toColumns) ? rel.toColumns : [], connectionType: rel.connectionType || "simple-key", relationshipName: rel.relationshipName || "", note: rel.note || "", // 🔥 연결 설명 로드 })); setTempRelationships(loadedRelationships); // 관계 데이터로부터 테이블 노드들을 생성 const tableNames = new Set(); loadedRelationships.forEach((rel) => { tableNames.add(rel.fromTable); tableNames.add(rel.toTable); }); // 각 테이블의 정보를 API에서 가져와서 노드 생성 const loadedNodes = await Promise.all( Array.from(tableNames).map(async (tableName) => { try { const columns = await DataFlowAPI.getTableColumns(tableName); return { id: `table-${tableName}`, type: "tableNode", position: jsonDiagram.node_positions?.[tableName] || { x: Math.random() * 300, y: Math.random() * 200, }, data: { table: { tableName, displayName: tableName, description: "", columns: Array.isArray(columns) ? columns.map((col) => ({ name: col.columnName || "unknown", type: col.dataType || "varchar", description: col.description || "", })) : [], }, onColumnClick: handleColumnClick, selectedColumns: [], connectedColumns: {}, }, selected: false, }; } catch (error) { console.warn(`테이블 ${tableName} 정보 로드 실패:`, error); return { id: `table-${tableName}`, type: "tableNode", position: jsonDiagram.node_positions?.[tableName] || { x: Math.random() * 300, y: Math.random() * 200, }, data: { table: { tableName, displayName: tableName, description: "", columns: [], }, onColumnClick: handleColumnClick, selectedColumns: [], connectedColumns: {}, }, selected: false, }; } }), ); setNodes(loadedNodes); // 관계 데이터로부터 엣지 생성 const loadedEdges = loadedRelationships.map((rel) => ({ id: `edge-${rel.fromTable}-${rel.toTable}-${rel.id}`, source: `table-${rel.fromTable}`, target: `table-${rel.toTable}`, type: "step", data: { relationshipId: rel.id, fromTable: rel.fromTable, toTable: rel.toTable, connectionType: rel.connectionType, relationshipName: rel.relationshipName, }, style: { stroke: "#3b82f6", strokeWidth: 2, }, animated: false, })); setEdges(loadedEdges); } } } catch (error) { console.error("관계도 데이터 로드 실패:", error); toast.error("관계도를 불러오는데 실패했습니다."); } } else { // 신규 생성 모드 setCurrentDiagramName(""); setNodes([]); setEdges([]); setTempRelationships([]); } }; loadDiagramData(); }, [ diagramId, companyCode, setCurrentDiagramId, setCurrentDiagramName, setNodes, setEdges, setTempRelationships, handleColumnClick, ]); // 키보드 이벤트 핸들러 (Del 키로 선택된 노드 삭제) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Delete" && selectedNodes.length > 0) { // 선택된 노드들 삭제 setNodes((prevNodes) => prevNodes.filter((node) => !selectedNodes.includes(node.id))); // 삭제된 노드들과 관련된 선택된 컬럼들도 정리 const deletedTableNames = selectedNodes .filter((nodeId) => nodeId.startsWith("table-")) .map((nodeId) => nodeId.replace("table-", "")); setSelectedColumns((prev) => { const newColumns = { ...prev }; deletedTableNames.forEach((tableName) => { delete newColumns[tableName]; }); return newColumns; }); // 선택된 노드 초기화 setSelectedNodes([]); toast.success(`${selectedNodes.length}개 테이블이 삭제되었습니다.`); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [selectedNodes, setNodes, setSelectedColumns, setSelectedNodes]); // 현재 추가된 테이블명 목록 가져오기 const getSelectedTableNames = useCallback(() => { return extractTableNames(nodes); }, [nodes]); // 실제 테이블 노드 추가 const addTableNode = useCallback( async (table: TableDefinition) => { try { const newNode = { id: `table-${table.tableName}`, type: "tableNode", position: { x: Math.random() * 300, y: Math.random() * 200 }, data: { table: { tableName: table.tableName, displayName: table.displayName || table.tableName, description: "", // 새로 추가된 노드는 description 없이 통일 columns: Array.isArray(table.columns) ? table.columns.map((col) => ({ columnName: col.columnName || "unknown", name: col.columnName || "unknown", // 호환성을 위해 유지 displayName: col.displayName, // 한국어 라벨 columnLabel: col.columnLabel, // 한국어 라벨 type: col.dataType || "varchar", dataType: col.dataType || "varchar", description: col.description || "", })) : [], }, onColumnClick: handleColumnClick, selectedColumns: selectedColumns[table.tableName] || [], connectedColumns: {}, // 새로 추가된 노드는 연결 정보 없음 }, }; setNodes((nds) => nds.concat(newNode)); } catch (error) { console.error("테이블 노드 추가 실패:", error); toast.error("테이블 정보를 불러오는데 실패했습니다."); } }, [handleColumnClick, selectedColumns, setNodes], ); // 노드 클릭 핸들러 (커스텀 다중 선택 구현) const onNodeClick = useCallback( (event: React.MouseEvent, node: Node) => { event.stopPropagation(); const nodeId = node.id; const isCurrentlySelected = selectedNodes.includes(nodeId); if (isCurrentlySelected) { // 이미 선택된 노드를 클릭하면 선택 해제 const newSelection = selectedNodes.filter((id) => id !== nodeId); setSelectedNodes(newSelection); // React Flow 노드 상태 업데이트 setNodes((prevNodes) => prevNodes.map((n) => ({ ...n, selected: newSelection.includes(n.id), })), ); } else { // 새로운 노드 선택 let newSelection: string[]; if (selectedNodes.length >= 2) { // 이미 2개가 선택되어 있으면 첫 번째를 제거하고 새로운 것을 추가 (FIFO) newSelection = [selectedNodes[1], nodeId]; } else { // 2개 미만이면 추가 newSelection = [...selectedNodes, nodeId]; } setSelectedNodes(newSelection); // React Flow 노드 상태 업데이트 setNodes((prevNodes) => prevNodes.map((n) => ({ ...n, selected: newSelection.includes(n.id), })), ); } }, [selectedNodes, setNodes, setSelectedNodes], ); // 노드 선택 변경 핸들러 (React Flow 자체 선택 이벤트는 무시) const onSelectionChange = useCallback(() => { // React Flow의 자동 선택 변경은 무시하고 우리의 커스텀 로직만 사용 // 이 함수는 비워두거나 최소한의 동기화만 수행 }, []); // 캔버스 클릭 시 엣지 정보 섹션 닫기 const onPaneClick = useCallback(() => { if (selectedEdgeInfo) { setSelectedEdgeInfo(null); } if (showEdgeActions) { setShowEdgeActions(false); setSelectedEdgeForEdit(null); } // 컬럼 선택 해제 setSelectedColumns({}); }, [ selectedEdgeInfo, showEdgeActions, setSelectedEdgeInfo, setShowEdgeActions, setSelectedEdgeForEdit, setSelectedColumns, ]); // 빈 onConnect 함수 (드래그 연결 비활성화) const onConnect = useCallback(() => { // 드래그로 연결하는 것을 방지 return; }, []); // 엣지 클릭 시 연결 정보 표시 및 관련 컬럼 하이라이트 const onEdgeClick = useCallback( (event: React.MouseEvent, edge: Edge) => { event.stopPropagation(); const edgeData = edge.data; if (edgeData) { // 해당 테이블 쌍의 모든 관계 찾기 const fromTable = edgeData.fromTable; const toTable = edgeData.toTable; const tablePairRelationships = tempRelationships.filter( (rel) => (rel.fromTable === fromTable && rel.toTable === toTable) || (rel.fromTable === toTable && rel.toTable === fromTable), ); // 관계가 1개든 여러 개든 항상 관계 목록 모달 표시 setSelectedTablePairRelationships(tablePairRelationships); setShowRelationshipListModal(true); } }, [tempRelationships, setSelectedTablePairRelationships, setShowRelationshipListModal], ); // 엣지 마우스 엔터 시 색상 변경 const onEdgeMouseEnter = useCallback( (event: React.MouseEvent, edge: Edge) => { setEdges((eds) => eds.map((e) => e.id === edge.id ? { ...e, style: { ...e.style, stroke: "#1d4ed8", // hover 색상 strokeWidth: 3, }, } : e, ), ); }, [setEdges], ); // 엣지 마우스 리브 시 원래 색상으로 복원 const onEdgeMouseLeave = useCallback( (event: React.MouseEvent, edge: Edge) => { setEdges((eds) => eds.map((e) => e.id === edge.id ? { ...e, style: { ...e.style, stroke: "#3b82f6", // 기본 색상 strokeWidth: 2, }, } : e, ), ); }, [setEdges], ); // 연결 가능한 상태인지 확인 const canCreateConnection = () => { return selectedNodes.length >= 2; }; // 테이블 노드 연결 설정 모달 열기 const openConnectionModal = () => { if (selectedNodes.length < 2) return; // 선택된 첫 번째와 두 번째 노드 찾기 const firstNode = nodes.find((node) => node.id === selectedNodes[0]); const secondNode = nodes.find((node) => node.id === selectedNodes[1]); if (!firstNode || !secondNode) return; setPendingConnection({ fromNode: { id: firstNode.id, tableName: firstNode.data.table.tableName, displayName: firstNode.data.table.displayName, }, toNode: { id: secondNode.id, tableName: secondNode.data.table.tableName, displayName: secondNode.data.table.displayName, }, }); }; // 연결 설정 확인 const handleConfirmConnection = useCallback( (relationshipData: TableRelationship) => { if (!pendingConnection || !relationshipData) return; if (editingRelationshipId) { // 편집 모드: 기존 관계 업데이트 const updatedRelationship = { id: editingRelationshipId, fromTable: relationshipData.from_table_name, toTable: relationshipData.to_table_name, fromColumns: relationshipData.from_column_name ? relationshipData.from_column_name.split(",") : [], toColumns: relationshipData.to_column_name ? relationshipData.to_column_name.split(",") : [], connectionType: relationshipData.connection_type as "simple-key" | "data-save" | "external-call", relationshipName: relationshipData.relationship_name, note: (relationshipData.settings as any)?.notes || "", // 🔥 notes를 note로 변환 settings: relationshipData.settings || {}, }; // tempRelationships에서 기존 관계 업데이트 setTempRelationships((prev) => prev.map((rel) => (rel.id === editingRelationshipId ? updatedRelationship : rel)), ); // 기존 엣지 업데이트 setEdges((prevEdges) => prevEdges.map((edge) => edge.data?.relationshipId === editingRelationshipId ? { ...edge, data: { ...edge.data, relationshipId: editingRelationshipId, fromTable: relationshipData.from_table_name, toTable: relationshipData.to_table_name, connectionType: relationshipData.connection_type, relationshipName: relationshipData.relationship_name, }, } : edge, ), ); // 편집 모드 종료 setEditingRelationshipId(null); } else { // 새로 생성 모드: 새로운 관계 추가 const newRelationship = { id: `rel-${Date.now()}`, fromTable: relationshipData.from_table_name, toTable: relationshipData.to_table_name, fromColumns: relationshipData.from_column_name ? relationshipData.from_column_name.split(",") : [], toColumns: relationshipData.to_column_name ? relationshipData.to_column_name.split(",") : [], connectionType: relationshipData.connection_type as "simple-key" | "data-save" | "external-call", relationshipName: relationshipData.relationship_name, note: (relationshipData.settings as any)?.notes || "", // 🔥 notes를 note로 변환 settings: relationshipData.settings || {}, }; // tempRelationships 상태 업데이트 setTempRelationships((prev) => [...prev, newRelationship]); // 새로운 엣지 생성 const newEdge = { id: `edge-${relationshipData.from_table_name}-${relationshipData.to_table_name}-${Date.now()}`, source: `table-${relationshipData.from_table_name}`, target: `table-${relationshipData.to_table_name}`, type: "step", data: { relationshipId: newRelationship.id, fromTable: relationshipData.from_table_name, toTable: relationshipData.to_table_name, connectionType: relationshipData.connection_type, relationshipName: relationshipData.relationship_name, }, style: { stroke: "#3b82f6", strokeWidth: 2, }, animated: false, }; // 엣지 추가 setEdges((prevEdges) => [...prevEdges, newEdge]); } setPendingConnection(null); setHasUnsavedChanges(true); }, [ pendingConnection, setPendingConnection, setHasUnsavedChanges, setTempRelationships, setEdges, editingRelationshipId, setEditingRelationshipId, ], ); // 연결 설정 취소 const handleCancelConnection = useCallback(() => { setPendingConnection(null); if (editingRelationshipId) { setEditingRelationshipId(null); setSelectedColumns({}); } }, [editingRelationshipId, setPendingConnection, setEditingRelationshipId, setSelectedColumns]); // 저장 모달 열기 const handleOpenSaveModal = useCallback(() => { setShowSaveModal(true); }, [setShowSaveModal]); // 저장 모달 닫기 const handleCloseSaveModal = useCallback(() => { if (!isSaving) { setShowSaveModal(false); } }, [isSaving, setShowSaveModal]); // 관계도 저장 함수 const handleSaveDiagram = useCallback( async (diagramName: string): Promise<{ success: boolean; error?: string }> => { if (nodes.length === 0) { toast.error("저장할 테이블이 없습니다."); return { success: false, error: "저장할 테이블이 없습니다." }; } setIsSaving(true); try { // 노드 위치 정보 추출 const nodePositions = extractNodePositions(nodes); // 연결된 테이블 목록 추출 const tableNames = extractTableNames(nodes); // 관계 데이터를 JsonRelationship 형태로 변환 (note 필드 포함) const jsonRelationships: JsonRelationship[] = tempRelationships.map((rel) => ({ id: rel.id, relationshipName: rel.relationshipName, // 🔥 핵심: 관계 이름 포함 fromTable: rel.fromTable, toTable: rel.toTable, fromColumns: rel.fromColumns, toColumns: rel.toColumns, connectionType: rel.connectionType, note: rel.note, // 🔥 연결 설명 포함 })); // 저장 요청 데이터 구성 const saveRequest: CreateDiagramRequest = { diagram_name: diagramName, relationships: { relationships: jsonRelationships, tables: tableNames, }, node_positions: nodePositions, // 카테고리 정보 추가 category: tempRelationships.map((rel) => ({ id: rel.id, category: rel.connectionType, })), // 조건부 연결 설정이 있는 경우 추가 control: tempRelationships .filter((rel) => rel.settings?.control) .map((rel) => ({ id: rel.id, triggerType: rel.settings?.control?.triggerType || "insert", conditions: (rel.settings?.control?.conditionTree || []).map((condition: Record) => ({ ...condition, logicalOperator: condition.logicalOperator === "AND" || condition.logicalOperator === "OR" ? condition.logicalOperator : undefined, })), })), // 데이터 저장 액션이 있는 경우 추가 (transformFunction 제거) plan: tempRelationships .filter((rel) => rel.settings?.actions && Array.isArray(rel.settings.actions)) .map((rel) => ({ id: rel.id, sourceTable: rel.fromTable, actions: (rel.settings?.actions || []).map((action: Record) => ({ id: action.id as string, name: action.name as string, actionType: action.actionType as "insert" | "update" | "delete" | "upsert", logicalOperator: action.logicalOperator as "AND" | "OR" | undefined, // 논리 연산자 추가 fieldMappings: ((action.fieldMappings as Record[]) || []).map( (mapping: Record) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { transformFunction, ...cleanMapping } = mapping; return cleanMapping as any; // transformFunction 제거 후 타입 캐스팅 }, ), splitConfig: action.splitConfig, conditions: action.conditions, })), })) as any, // plan 전체를 any로 캐스팅 }; if ((diagramId && diagramId > 0) || (currentDiagramId && currentDiagramId > 0)) { // 기존 관계도 수정 (prop diagramId 또는 내부 currentDiagramId 사용) const targetDiagramId = diagramId || currentDiagramId; await DataFlowAPI.updateJsonDataFlowDiagram( targetDiagramId!, saveRequest, companyCode, authUser?.userId || "SYSTEM", ); } else { // 새로운 관계도 생성 const newDiagram = await DataFlowAPI.createJsonDataFlowDiagram( saveRequest, companyCode, authUser?.userId || "SYSTEM", ); // 새로 생성된 다이어그램 ID를 내부 상태에 저장 (다음 저장부터는 업데이트 모드) setCurrentDiagramId(newDiagram.diagram_id); setCurrentDiagramName(newDiagram.diagram_name); } setHasUnsavedChanges(false); // 성공 모달은 SaveDiagramModal에서 처리하므로 여기서는 toast 제거 return { success: true }; } catch (error) { // 에러 메시지 분석 let errorMessage = "관계도 저장 중 오류가 발생했습니다."; let isDuplicateError = false; // Axios 에러 처리 if (error && typeof error === "object" && "response" in error) { const axiosError = error as any; if (axiosError.response?.status === 409) { // 중복 이름 에러 (409 Conflict) errorMessage = "중복된 이름입니다."; isDuplicateError = true; } else if (axiosError.response?.data?.message) { // 백엔드에서 제공한 에러 메시지 사용 if (axiosError.response.data.message.includes("중복된 이름입니다")) { errorMessage = "중복된 이름입니다."; isDuplicateError = true; } else { errorMessage = axiosError.response.data.message; } } } else if (error instanceof Error) { if ( error.message.includes("중복") || error.message.includes("duplicate") || error.message.includes("already exists") ) { errorMessage = "중복된 이름입니다."; isDuplicateError = true; } else { errorMessage = error.message; } } // 중복 에러가 아닌 경우만 콘솔에 로그 출력 if (!isDuplicateError) { console.error("관계도 저장 실패:", error); } return { success: false, error: errorMessage }; } finally { setIsSaving(false); } }, [ nodes, tempRelationships, diagramId, currentDiagramId, companyCode, authUser?.userId, setIsSaving, setHasUnsavedChanges, setShowSaveModal, setCurrentDiagramId, setCurrentDiagramName, ], ); // 고립된 노드 제거 함수 const removeOrphanedNodes = useCallback(() => { toast.success("고립된 노드가 정리되었습니다."); }, []); // 전체 삭제 핸들러 const clearNodes = useCallback(() => { setNodes([]); setEdges([]); setTempRelationships([]); setSelectedColumns({}); setSelectedNodes([]); setPendingConnection(null); setSelectedEdgeInfo(null); setShowEdgeActions(false); setSelectedEdgeForEdit(null); setHasUnsavedChanges(true); toast.success("모든 테이블과 관계가 삭제되었습니다."); }, [ setNodes, setEdges, setTempRelationships, setSelectedColumns, setSelectedNodes, setPendingConnection, setSelectedEdgeInfo, setShowEdgeActions, setSelectedEdgeForEdit, setHasUnsavedChanges, ]); return (
{/* 사이드바 */} {/* React Flow 캔버스 */}
{/* 관계 목록 모달 */} { // 최신 tempRelationships에서 해당 관계 찾기 const updatedRel = tempRelationships.find((tempRel) => tempRel.id === rel.id); return updatedRel || rel; // 업데이트된 관계가 있으면 사용, 없으면 원본 사용 })} nodes={nodes} diagramId={diagramId} companyCode={companyCode} editingRelationshipId={editingRelationshipId} onClose={() => setShowRelationshipListModal(false)} onEdit={() => {}} onDelete={(relationshipId) => { setTempRelationships((prev) => prev.filter((rel) => rel.id !== relationshipId)); setEdges((prev) => prev.filter((edge) => edge.data?.relationshipId !== relationshipId)); setHasUnsavedChanges(true); }} onSetEditingId={setEditingRelationshipId} onSetSelectedColumns={setSelectedColumns} onSetPendingConnection={setPendingConnection} /> {/* 선택된 테이블 노드 팝업 */} {selectedNodes.length > 0 && !showEdgeActions && !pendingConnection && !editingRelationshipId && ( setSelectedNodes([])} onOpenConnectionModal={openConnectionModal} onClear={() => { setSelectedColumns({}); setSelectedNodes([]); }} canCreateConnection={canCreateConnection()} /> )} {/* 안내 메시지 */} {nodes.length === 0 && (
📊
테이블 간 데이터 관계 설정을 시작하세요
왼쪽 사이드바에서 테이블을 더블클릭하여 추가하세요
테이블 선택 후 Del 키로 삭제 가능
)}
{/* 연결 설정 모달 */} {/* 엣지 정보 및 액션 버튼 */} { setSelectedEdgeInfo(null); setShowEdgeActions(false); setSelectedEdgeForEdit(null); setSelectedColumns({}); }} onEdit={() => { if (selectedEdgeInfo) { // 기존 관계 찾기 const existingRelationship = tempRelationships.find((rel) => rel.id === selectedEdgeInfo.relationshipId); if (existingRelationship) { // 편집 모드로 설정 setEditingRelationshipId(selectedEdgeInfo.relationshipId); // 연결 설정 모달 열기 const fromTable = nodes.find((node) => node.data?.table?.tableName === selectedEdgeInfo.fromTable); const toTable = nodes.find((node) => node.data?.table?.tableName === selectedEdgeInfo.toTable); if (fromTable && toTable) { setPendingConnection({ fromNode: { id: fromTable.id, tableName: selectedEdgeInfo.fromTable, displayName: fromTable.data?.table?.displayName || selectedEdgeInfo.fromTable, }, toNode: { id: toTable.id, tableName: selectedEdgeInfo.toTable, displayName: toTable.data?.table?.displayName || selectedEdgeInfo.toTable, }, selectedColumnsData: { [selectedEdgeInfo.fromTable]: { displayName: fromTable.data?.table?.displayName || selectedEdgeInfo.fromTable, columns: selectedEdgeInfo.fromColumns || [], }, [selectedEdgeInfo.toTable]: { displayName: toTable.data?.table?.displayName || selectedEdgeInfo.toTable, columns: selectedEdgeInfo.toColumns || [], }, }, existingRelationship: { relationshipName: existingRelationship.relationshipName, connectionType: existingRelationship.connectionType, settings: existingRelationship.settings, }, }); // 패널 닫기 setSelectedEdgeInfo(null); setShowEdgeActions(false); setSelectedEdgeForEdit(null); } } } }} onDelete={() => { if (selectedEdgeInfo) { // 관계 삭제 setTempRelationships((prev) => prev.filter((rel) => rel.id !== selectedEdgeInfo.relationshipId)); // 엣지 삭제 setEdges((prev) => prev.filter((edge) => edge.data?.relationshipId !== selectedEdgeInfo.relationshipId)); // 변경사항 표시 setHasUnsavedChanges(true); // 패널 닫기 setSelectedEdgeInfo(null); setShowEdgeActions(false); setSelectedEdgeForEdit(null); setSelectedColumns({}); toast.success("관계가 삭제되었습니다."); } }} /> {/* 관계도 저장 모달 */} 0 && currentDiagramName ? currentDiagramName // 편집 모드: 기존 관계도 이름 : `관계도 ${new Date().toLocaleDateString()}` // 신규 생성 모드: 새로운 이름 } isLoading={isSaving} />
); };