diff --git a/backend-node/src/controllers/dataflowController.ts b/backend-node/src/controllers/dataflowController.ts index e516b5ce..57a0c3ea 100644 --- a/backend-node/src/controllers/dataflowController.ts +++ b/backend-node/src/controllers/dataflowController.ts @@ -826,3 +826,54 @@ export async function deleteDiagram( res.status(500).json(response); } } + +/** + * relationship_id로 관계도 관계 조회 + */ +export async function getDiagramRelationshipsByRelationshipId( + req: AuthenticatedRequest, + res: Response +): Promise { + try { + const { relationshipId } = req.params; + const companyCode = (req.user as any)?.company_code || "*"; + + if (!relationshipId) { + const response: ApiResponse = { + success: false, + message: "관계 ID가 필요합니다.", + error: { + code: "MISSING_RELATIONSHIP_ID", + details: "relationshipId 파라미터가 필요합니다.", + }, + }; + res.status(400).json(response); + return; + } + + const dataflowService = new DataflowService(); + const relationships = await dataflowService.getDiagramRelationshipsByRelationshipId( + companyCode, + parseInt(relationshipId) + ); + + const response: ApiResponse = { + success: true, + message: "관계도 관계 목록을 성공적으로 조회했습니다.", + data: relationships, + }; + + res.status(200).json(response); + } catch (error) { + logger.error("관계도 관계 조회 실패:", error); + const response: ApiResponse = { + success: false, + message: "관계도 관계 조회에 실패했습니다.", + error: { + code: "DIAGRAM_RELATIONSHIPS_FETCH_FAILED", + details: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }, + }; + res.status(500).json(response); + } +} diff --git a/backend-node/src/routes/dataflowRoutes.ts b/backend-node/src/routes/dataflowRoutes.ts index 21c9bc3f..ddb87a17 100644 --- a/backend-node/src/routes/dataflowRoutes.ts +++ b/backend-node/src/routes/dataflowRoutes.ts @@ -12,6 +12,7 @@ import { getTableData, getDataFlowDiagrams, getDiagramRelationships, + getDiagramRelationshipsByRelationshipId, copyDiagram, deleteDiagram, } from "../controllers/dataflowController"; @@ -108,4 +109,10 @@ router.post("/diagrams/:diagramName/copy", copyDiagram); */ router.delete("/diagrams/:diagramName", deleteDiagram); +// relationship_id로 관계도 관계 조회 +router.get( + "/relationships/:relationshipId/diagram", + getDiagramRelationshipsByRelationshipId +); + export default router; diff --git a/backend-node/src/services/dataflowService.ts b/backend-node/src/services/dataflowService.ts index 3958b109..e188a4cf 100644 --- a/backend-node/src/services/dataflowService.ts +++ b/backend-node/src/services/dataflowService.ts @@ -776,6 +776,7 @@ export class DataflowService { const relationships = await prisma.table_relationships.findMany({ where: whereCondition, select: { + relationship_id: true, relationship_name: true, from_table_name: true, to_table_name: true, @@ -797,6 +798,7 @@ export class DataflowService { if (!diagramMap.has(diagramName)) { diagramMap.set(diagramName, { + relationshipId: rel.relationship_id, // 첫 번째 관계의 ID를 대표 ID로 사용 diagramName: diagramName, connectionType: rel.connection_type, relationshipType: rel.relationship_type, @@ -998,4 +1000,59 @@ export class DataflowService { throw error; } } + + /** + * relationship_id로 해당 관계도의 모든 관계 조회 + */ + async getDiagramRelationshipsByRelationshipId( + companyCode: string, + relationshipId: number + ) { + try { + logger.info( + `DataflowService: relationship_id로 관계도 관계 조회 - ${relationshipId}` + ); + + // 먼저 해당 relationship_id의 관계도명을 찾음 + const targetRelationship = await prisma.table_relationships.findFirst({ + where: { + relationship_id: relationshipId, + company_code: companyCode, + is_active: "Y", + }, + select: { + relationship_name: true, + }, + }); + + if (!targetRelationship) { + throw new Error("해당 관계 ID를 찾을 수 없습니다."); + } + + // 같은 관계도명을 가진 모든 관계 조회 + const relationships = await prisma.table_relationships.findMany({ + where: { + relationship_name: targetRelationship.relationship_name, + company_code: companyCode, + is_active: "Y", + }, + orderBy: [{ relationship_id: "asc" }], + }); + + logger.info( + `DataflowService: relationship_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계` + ); + + return relationships.map((rel) => ({ + ...rel, + settings: rel.settings as any, + })); + } catch (error) { + logger.error( + `DataflowService: relationship_id로 관계도 관계 조회 실패 - ${relationshipId}`, + error + ); + throw error; + } + } } diff --git a/frontend/app/(main)/admin/dataflow/edit/[relationshipId]/page.tsx b/frontend/app/(main)/admin/dataflow/edit/[relationshipId]/page.tsx new file mode 100644 index 00000000..ff6fc87b --- /dev/null +++ b/frontend/app/(main)/admin/dataflow/edit/[relationshipId]/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useParams, useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; +import { ArrowLeft } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { DataFlowDesigner } from "@/components/dataflow/DataFlowDesigner"; +import { DataFlowAPI } from "@/lib/api/dataflow"; +import { toast } from "sonner"; + +export default function DataFlowEditPage() { + const params = useParams(); + const router = useRouter(); + const [relationshipId, setRelationshipId] = useState(""); + const [diagramName, setDiagramName] = useState(""); + + useEffect(() => { + if (params.relationshipId) { + // URL에서 relationship_id 설정 + const id = params.relationshipId as string; + setRelationshipId(id); + + // relationship_id로 관계도명 조회 + const fetchDiagramName = async () => { + try { + const relationships = await DataFlowAPI.getDiagramRelationshipsByRelationshipId(id); + if (relationships.length > 0) { + setDiagramName(relationships[0].relationship_name); + } else { + setDiagramName(`관계도 ID: ${id}`); + } + } catch (error) { + console.error("관계도명 조회 실패:", error); + setDiagramName(`관계도 ID: ${id}`); + } + }; + + fetchDiagramName(); + } + }, [params.relationshipId]); + + const handleBackToList = () => { + router.push("/admin/dataflow"); + }; + + if (!relationshipId || !diagramName) { + return ( +
+
+
+

관계도 정보를 불러오는 중...

+
+
+ ); + } + + return ( +
+ {/* 헤더 */} +
+
+ +
+

📊 관계도 편집

+

+ {diagramName} 관계도를 편집하고 있습니다 +

+
+
+
+ + {/* 데이터플로우 디자이너 */} +
+ +
+
+ ); +} diff --git a/frontend/app/(main)/admin/dataflow/page.tsx b/frontend/app/(main)/admin/dataflow/page.tsx index 65b049b5..29a3aea3 100644 --- a/frontend/app/(main)/admin/dataflow/page.tsx +++ b/frontend/app/(main)/admin/dataflow/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useRouter } from "next/navigation"; import { DataFlowDesigner } from "@/components/dataflow/DataFlowDesigner"; import DataFlowList from "@/components/dataflow/DataFlowList"; import { TableRelationship, DataFlowDiagram } from "@/lib/api/dataflow"; @@ -12,6 +13,7 @@ type Step = "list" | "design"; export default function DataFlowPage() { const { user } = useAuth(); + const router = useRouter(); const [currentStep, setCurrentStep] = useState("list"); const [selectedDiagram, setSelectedDiagram] = useState(null); const [stepHistory, setStepHistory] = useState(["list"]); @@ -79,8 +81,14 @@ export default function DataFlowPage() { }; const handleDesignDiagram = (diagram: DataFlowDiagram | null) => { - setSelectedDiagram(diagram); - goToNextStep("design"); + if (diagram) { + // 기존 관계도 편집 - 새로운 URL로 이동 + router.push(`/admin/dataflow/edit/${diagram.relationshipId}`); + } else { + // 새 관계도 생성 - 현재 페이지에서 처리 + setSelectedDiagram(null); + goToNextStep("design"); + } }; return ( diff --git a/frontend/components/dataflow/DataFlowDesigner.tsx b/frontend/components/dataflow/DataFlowDesigner.tsx index 2c8f4c7a..119fd8dd 100644 --- a/frontend/components/dataflow/DataFlowDesigner.tsx +++ b/frontend/components/dataflow/DataFlowDesigner.tsx @@ -50,16 +50,18 @@ const nodeTypes = { const edgeTypes = {}; interface DataFlowDesignerProps { - companyCode: string; + companyCode?: string; onSave?: (relationships: TableRelationship[]) => void; - selectedDiagram?: DataFlowDiagram | null; + selectedDiagram?: DataFlowDiagram | string | null; + relationshipId?: string; onBackToList?: () => void; } // TableRelationship 타입은 dataflow.ts에서 import export const DataFlowDesigner: React.FC = ({ - companyCode, + companyCode = "*", + relationshipId, onSave, selectedDiagram, onBackToList, // eslint-disable-line @typescript-eslint/no-unused-vars @@ -167,12 +169,14 @@ export const DataFlowDesigner: React.FC = ({ // 선택된 관계도의 관계 로드 const loadSelectedDiagramRelationships = useCallback(async () => { - if (!selectedDiagram) return; + if (!relationshipId) return; try { - console.log("🔍 관계도 로드 시작:", selectedDiagram.diagramName); + console.log("🔍 관계도 로드 시작 (relationshipId):", relationshipId); toast.loading("관계도를 불러오는 중...", { id: "load-diagram" }); - const diagramRelationships = await DataFlowAPI.getDiagramRelationships(selectedDiagram.diagramName); + + // relationshipId로 해당 관계도의 모든 관계 조회 + const diagramRelationships = await DataFlowAPI.getDiagramRelationshipsByRelationshipId(relationshipId); console.log("📋 관계도 관계 데이터:", diagramRelationships); console.log("📋 첫 번째 관계 상세:", diagramRelationships[0]); console.log( @@ -320,12 +324,12 @@ export const DataFlowDesigner: React.FC = ({ console.log("🔗 생성된 관계 에지 수:", relationshipEdges.length); console.log("📍 관계 에지 상세:", relationshipEdges); setEdges(relationshipEdges); - toast.success(`"${selectedDiagram.diagramName}" 관계도를 불러왔습니다.`, { id: "load-diagram" }); + toast.success("관계도를 불러왔습니다.", { id: "load-diagram" }); } catch (error) { console.error("선택된 관계도 로드 실패:", error); toast.error("관계도를 불러오는데 실패했습니다.", { id: "load-diagram" }); } - }, [selectedDiagram, setNodes, setEdges, selectedColumns, handleColumnClick]); + }, [relationshipId, setNodes, setEdges, selectedColumns, handleColumnClick]); // 기존 관계 로드 (새 관계도 생성 시) const loadExistingRelationships = useCallback(async () => { @@ -387,13 +391,13 @@ export const DataFlowDesigner: React.FC = ({ // 컴포넌트 마운트 시 관계 로드 useEffect(() => { if (companyCode) { - if (selectedDiagram) { + if (relationshipId) { loadSelectedDiagramRelationships(); } else { loadExistingRelationships(); } } - }, [companyCode, selectedDiagram, loadExistingRelationships, loadSelectedDiagramRelationships]); + }, [companyCode, relationshipId, loadExistingRelationships, loadSelectedDiagramRelationships]); // 노드 선택 변경 핸들러 const onSelectionChange = useCallback(({ nodes }: { nodes: Node[] }) => { diff --git a/frontend/lib/api/dataflow.ts b/frontend/lib/api/dataflow.ts index 2eee2398..6678c59d 100644 --- a/frontend/lib/api/dataflow.ts +++ b/frontend/lib/api/dataflow.ts @@ -89,6 +89,7 @@ export interface TableDataResponse { // 관계도 정보 인터페이스 export interface DataFlowDiagram { + relationshipId: number; diagramName: string; connectionType: string; relationshipType: string; @@ -377,7 +378,7 @@ export class DataFlowAPI { } } - // 특정 관계도의 모든 관계 조회 + // 특정 관계도의 모든 관계 조회 (관계도명으로) static async getDiagramRelationships(diagramName: string): Promise { try { const encodedDiagramName = encodeURIComponent(diagramName); @@ -433,4 +434,22 @@ export class DataFlowAPI { throw error; } } + + // 특정 관계도의 모든 관계 조회 (relationship_id로) + static async getDiagramRelationshipsByRelationshipId(relationshipId: string): Promise { + try { + const response = await apiClient.get>( + `/dataflow/relationships/${relationshipId}/diagram`, + ); + + if (!response.data.success) { + throw new Error(response.data.message || "관계도 관계 조회에 실패했습니다."); + } + + return response.data.data || []; + } catch (error) { + console.error("관계도 관계 조회 오류:", error); + throw error; + } + } }