From 104faf487cee7803ca26320a938300b9e8a7d162 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Tue, 16 Sep 2025 16:49:59 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/dataflow/DataFlowDesigner.tsx | 284 ++++++++++++++++-- .../dataflow/RelationshipListModal.tsx | 2 +- .../components/dataflow/SaveDiagramModal.tsx | 8 +- 3 files changed, 264 insertions(+), 30 deletions(-) diff --git a/frontend/components/dataflow/DataFlowDesigner.tsx b/frontend/components/dataflow/DataFlowDesigner.tsx index 9c529700..421446b8 100644 --- a/frontend/components/dataflow/DataFlowDesigner.tsx +++ b/frontend/components/dataflow/DataFlowDesigner.tsx @@ -13,11 +13,17 @@ import { RelationshipListModal } from "./RelationshipListModal"; import { EdgeInfoPanel } from "./EdgeInfoPanel"; import SaveDiagramModal from "./SaveDiagramModal"; -import { TableDefinition, DataFlowAPI, JsonRelationship } from "@/lib/api/dataflow"; +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 } from "@/utils/dataflowUtils"; +import { extractTableNames, extractNodePositions } from "@/utils/dataflowUtils"; // 노드 및 엣지 타입 정의 const nodeTypes = { @@ -30,10 +36,10 @@ export const DataFlowDesigner: React.FC = ({ companyCode: propCompanyCode = "*", diagramId, }) => { - const { user } = useAuth(); + const { user: authUser } = useAuth(); // 실제 사용자 회사 코드 사용 (prop보다 사용자 정보 우선) - const companyCode = user?.company_code || user?.companyCode || propCompanyCode; + const companyCode = authUser?.company_code || authUser?.companyCode || propCompanyCode; // 커스텀 훅 사용 const { @@ -196,11 +202,6 @@ export const DataFlowDesigner: React.FC = ({ })); setEdges(loadedEdges); - - console.log("✅ 관계도 데이터 로드 완료:", { - relationships: jsonDiagram.relationships?.relationships?.length || 0, - tables: Array.from(new Set(loadedRelationships.flatMap((rel) => [rel.fromTable, rel.toTable]))).length, - }); } } } catch (error) { @@ -386,8 +387,6 @@ export const DataFlowDesigner: React.FC = ({ (rel.fromTable === toTable && rel.toTable === fromTable), ); - console.log(`🔗 ${fromTable} ↔ ${toTable} 간의 관계:`, tablePairRelationships); - // 관계가 1개든 여러 개든 항상 관계 목록 모달 표시 setSelectedTablePairRelationships(tablePairRelationships); setShowRelationshipListModal(true); @@ -468,15 +467,102 @@ export const DataFlowDesigner: React.FC = ({ }; // 연결 설정 확인 - const handleConfirmConnection = useCallback(() => { - if (!pendingConnection) return; + const handleConfirmConnection = useCallback( + (relationshipData: TableRelationship) => { + if (!pendingConnection || !relationshipData) return; - // 관계 생성 로직은 여기서 구현... - // 현재는 간단히 성공 메시지만 표시 - toast.success("관계가 생성되었습니다."); - setPendingConnection(null); - setHasUnsavedChanges(true); - }, [pendingConnection, setPendingConnection, setHasUnsavedChanges]); + 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, + 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, + 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(() => { @@ -499,7 +585,7 @@ export const DataFlowDesigner: React.FC = ({ } }, [isSaving, setShowSaveModal]); - // 관계도 저장 함수 (간단한 구현) + // 관계도 저장 함수 const handleSaveDiagram = useCallback( async (diagramName: string) => { if (nodes.length === 0) { @@ -509,7 +595,74 @@ export const DataFlowDesigner: React.FC = ({ setIsSaving(true); try { - // 여기서 실제 저장 로직 구현 + // 노드 위치 정보 추출 + const nodePositions = extractNodePositions(nodes); + + // 연결된 테이블 목록 추출 + const tableNames = extractTableNames(nodes); + + // 관계 데이터를 JsonRelationship 형태로 변환 + 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, + settings: rel.settings, + })); + + // 저장 요청 데이터 구성 + 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, + })), + })), + // 데이터 저장 액션이 있는 경우 추가 + plan: tempRelationships + .filter((rel) => rel.settings?.actions && Array.isArray(rel.settings.actions)) + .map((rel) => ({ + id: rel.id, + sourceTable: rel.fromTable, + actions: rel.settings?.actions || [], + })), + }; + + if (diagramId && diagramId > 0) { + // 기존 관계도 수정 + await DataFlowAPI.updateJsonDataFlowDiagram( + diagramId, + saveRequest, + companyCode, + authUser?.userId || "SYSTEM", + ); + } else { + // 새로운 관계도 생성 + await DataFlowAPI.createJsonDataFlowDiagram(saveRequest, companyCode, authUser?.userId || "SYSTEM"); + } + toast.success(`관계도 "${diagramName}"가 성공적으로 저장되었습니다.`); setHasUnsavedChanges(false); setShowSaveModal(false); @@ -520,7 +673,16 @@ export const DataFlowDesigner: React.FC = ({ setIsSaving(false); } }, - [nodes, setIsSaving, setHasUnsavedChanges, setShowSaveModal], + [ + nodes, + tempRelationships, + diagramId, + companyCode, + authUser?.userId, + setIsSaving, + setHasUnsavedChanges, + setShowSaveModal, + ], ); // 고립된 노드 제거 함수 @@ -608,7 +770,11 @@ export const DataFlowDesigner: React.FC = ({ {/* 관계 목록 모달 */} { + // 최신 tempRelationships에서 해당 관계 찾기 + const updatedRel = tempRelationships.find((tempRel) => tempRel.id === rel.id); + return updatedRel || rel; // 업데이트된 관계가 있으면 사용, 없으면 원본 사용 + })} nodes={nodes} diagramId={diagramId} companyCode={companyCode} @@ -682,8 +848,76 @@ export const DataFlowDesigner: React.FC = ({ setSelectedEdgeForEdit(null); setSelectedColumns({}); }} - onEdit={() => {}} - onDelete={() => {}} + 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("관계가 삭제되었습니다."); + } + }} /> {/* 관계도 저장 모달 */} diff --git a/frontend/components/dataflow/RelationshipListModal.tsx b/frontend/components/dataflow/RelationshipListModal.tsx index 3c2c8d48..08f7c11c 100644 --- a/frontend/components/dataflow/RelationshipListModal.tsx +++ b/frontend/components/dataflow/RelationshipListModal.tsx @@ -156,7 +156,7 @@ export const RelationshipListModal: React.FC = ({ >

- {relationship.fromTable} → {relationship.toTable} + {relationship.relationshipName || `${relationship.fromTable} → ${relationship.toTable}`}

{/* 편집 버튼 */} diff --git a/frontend/components/dataflow/SaveDiagramModal.tsx b/frontend/components/dataflow/SaveDiagramModal.tsx index ad00be8d..2ed0e13d 100644 --- a/frontend/components/dataflow/SaveDiagramModal.tsx +++ b/frontend/components/dataflow/SaveDiagramModal.tsx @@ -155,11 +155,11 @@ const SaveDiagramModal: React.FC = ({
- {relationship.relationshipType} + {relationship.connectionType || "simple-key"} - {relationship.fromTable} - - {relationship.toTable} + + {relationship.relationshipName || `${relationship.fromTable} → ${relationship.toTable}`} +
{relationship.fromColumns.join(", ")} → {relationship.toColumns.join(", ")}