diff --git a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx
index 25b3f193..ede20c29 100644
--- a/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx
+++ b/frontend/app/(main)/admin/dataflow/edit/[diagramId]/page.tsx
@@ -43,6 +43,11 @@ export default function DataFlowEditPage() {
router.push("/admin/dataflow");
};
+ // 관계도 이름 업데이트 핸들러
+ const handleDiagramNameUpdate = (newDiagramName: string) => {
+ setDiagramName(newDiagramName);
+ };
+
if (!diagramId || !diagramName) {
return (
@@ -74,7 +79,12 @@ export default function DataFlowEditPage() {
{/* 데이터플로우 디자이너 */}
-
+
);
diff --git a/frontend/components/dataflow/DataFlowDesigner.tsx b/frontend/components/dataflow/DataFlowDesigner.tsx
index 38a33db0..26a6eba6 100644
--- a/frontend/components/dataflow/DataFlowDesigner.tsx
+++ b/frontend/components/dataflow/DataFlowDesigner.tsx
@@ -66,6 +66,7 @@ interface DataFlowDesignerProps {
diagramId?: number;
relationshipId?: string; // 하위 호환성 유지
onBackToList?: () => void;
+ onDiagramNameUpdate?: (diagramName: string) => void; // 관계도 이름 업데이트 콜백 추가
}
// TableRelationship 타입은 dataflow.ts에서 import
@@ -82,6 +83,7 @@ export const DataFlowDesigner: React.FC = ({
onSave, // eslint-disable-line @typescript-eslint/no-unused-vars
selectedDiagram,
onBackToList, // eslint-disable-line @typescript-eslint/no-unused-vars
+ onDiagramNameUpdate, // 관계도 이름 업데이트 콜백
}) => {
const { user } = useAuth();
@@ -136,6 +138,8 @@ export const DataFlowDesigner: React.FC = ({
const [showEdgeActions, setShowEdgeActions] = useState(false); // 엣지 액션 버튼 표시 상태
const [edgeActionPosition, setEdgeActionPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); // 액션 버튼 위치
const [editingRelationshipId, setEditingRelationshipId] = useState(null); // 현재 수정 중인 관계 ID
+ const [showRelationshipListModal, setShowRelationshipListModal] = useState(false); // 관계 목록 모달 표시 상태
+ const [selectedTablePairRelationships, setSelectedTablePairRelationships] = useState([]); // 선택된 테이블 쌍의 관계들
const toastShownRef = useRef(false); // eslint-disable-line @typescript-eslint/no-unused-vars
// 편집 모드일 때 관계도 이름 로드
@@ -333,8 +337,9 @@ export const DataFlowDesigner: React.FC = ({
console.log("📍 테이블 노드 상세:", tableNodes);
setNodes(tableNodes);
- // JSON 관계를 엣지로 변환하여 표시 (테이블 간 번들 연결)
+ // JSON 관계를 엣지로 변환하여 표시 (각 관계마다 개별 엣지 생성)
const relationshipEdges: Edge[] = [];
+ const tableRelationshipCount: { [key: string]: number } = {}; // 테이블 쌍별 관계 개수
normalizedRelationships.forEach((rel: ExtendedJsonRelationship) => {
const fromTable = rel.fromTable;
@@ -347,9 +352,16 @@ export const DataFlowDesigner: React.FC = ({
return;
}
- // 테이블 간 하나의 번들 엣지 생성 (컬럼별 개별 엣지 대신)
+ // 테이블 쌍 키 생성 (양방향 동일하게 처리)
+ const tableKey = [fromTable, toTable].sort().join("-");
+ tableRelationshipCount[tableKey] = (tableRelationshipCount[tableKey] || 0) + 1;
+ const relationshipIndex = tableRelationshipCount[tableKey];
+
+ // 각 관계마다 고유한 엣지 생성 (곡선 오프셋으로 구분)
+ const curveOffset = (relationshipIndex - 1) * 30; // 30px씩 오프셋
+
relationshipEdges.push({
- id: generateUniqueId("edge", currentDiagramId),
+ id: `edge-${rel.id}`, // 관계 ID를 기반으로 고유 ID 생성
source: `table-${fromTable}`,
target: `table-${toTable}`,
type: "smoothstep",
@@ -359,6 +371,17 @@ export const DataFlowDesigner: React.FC = ({
strokeWidth: 2,
strokeDasharray: "none",
},
+ // 여러 관계가 있을 때 곡선 오프셋 적용
+ ...(relationshipIndex > 1 && {
+ style: {
+ stroke: "#3b82f6",
+ strokeWidth: 2,
+ strokeDasharray: "none",
+ },
+ pathOptions: {
+ offset: curveOffset,
+ },
+ }),
data: {
relationshipId: rel.id,
relationshipName: rel.relationshipName,
@@ -367,6 +390,9 @@ export const DataFlowDesigner: React.FC = ({
toTable: toTable,
fromColumns: fromColumns,
toColumns: toColumns,
+ // 테이블 쌍의 모든 관계 정보 (엣지 클릭 시 사용)
+ tableKey: tableKey,
+ relationshipIndex: relationshipIndex,
// 클릭 시 표시할 상세 정보
details: {
connectionInfo: `${fromTable}(${fromColumns.join(", ")}) → ${toTable}(${toColumns.join(", ")})`,
@@ -486,56 +512,45 @@ export const DataFlowDesigner: React.FC = ({
}, []);
// 엣지 클릭 시 연결 정보 표시 및 관련 컬럼 하이라이트
- const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
- event.stopPropagation();
- const edgeData = edge.data as {
- relationshipId: string;
- relationshipName: string;
- fromTable: string;
- toTable: string;
- fromColumns: string[];
- toColumns: string[];
- connectionType: string;
- details?: {
- connectionInfo: string;
+ const onEdgeClick = useCallback(
+ (event: React.MouseEvent, edge: Edge) => {
+ event.stopPropagation();
+ const edgeData = edge.data as {
+ relationshipId: string;
+ relationshipName: string;
+ fromTable: string;
+ toTable: string;
+ fromColumns: string[];
+ toColumns: string[];
connectionType: string;
+ tableKey: string;
+ relationshipIndex: number;
+ details?: {
+ connectionInfo: string;
+ connectionType: string;
+ };
};
- };
- if (edgeData) {
- // 엣지 정보 설정
- setSelectedEdgeInfo({
- relationshipId: edgeData.relationshipId,
- relationshipName: edgeData.relationshipName || "관계",
- fromTable: edgeData.fromTable,
- toTable: edgeData.toTable,
- fromColumns: edgeData.fromColumns || [],
- toColumns: edgeData.toColumns || [],
- connectionType: edgeData.connectionType,
- connectionInfo: edgeData.details?.connectionInfo || `${edgeData.fromTable} → ${edgeData.toTable}`,
- });
+ if (edgeData) {
+ // 해당 테이블 쌍의 모든 관계 찾기
+ const fromTable = edgeData.fromTable;
+ const toTable = edgeData.toTable;
- // 관련 컬럼 하이라이트
- const newSelectedColumns: { [tableName: string]: string[] } = {};
+ const tablePairRelationships = tempRelationships.filter(
+ (rel) =>
+ (rel.fromTable === fromTable && rel.toTable === toTable) ||
+ (rel.fromTable === toTable && rel.toTable === fromTable),
+ );
- // fromTable의 컬럼들 선택
- if (edgeData.fromTable && edgeData.fromColumns) {
- newSelectedColumns[edgeData.fromTable] = [...edgeData.fromColumns];
+ console.log(`🔗 ${fromTable} ↔ ${toTable} 간의 관계:`, tablePairRelationships);
+
+ // 관계가 1개든 여러 개든 항상 관계 목록 모달 표시
+ setSelectedTablePairRelationships(tablePairRelationships);
+ setShowRelationshipListModal(true);
}
-
- // toTable의 컬럼들 선택
- if (edgeData.toTable && edgeData.toColumns) {
- newSelectedColumns[edgeData.toTable] = [...edgeData.toColumns];
- }
-
- setSelectedColumns(newSelectedColumns);
-
- // 액션 버튼 표시
- setSelectedEdgeForEdit(edge);
- setEdgeActionPosition({ x: event.clientX, y: event.clientY });
- setShowEdgeActions(true);
- }
- }, []);
+ },
+ [tempRelationships],
+ );
// 엣지 마우스 엔터 시 색상 변경
const onEdgeMouseEnter = useCallback(
@@ -845,6 +860,11 @@ export const DataFlowDesigner: React.FC = ({
setShowSaveModal(false);
setCurrentDiagramId(savedDiagram.diagram_id);
+ // 관계도 이름 업데이트 (편집 모드일 때만)
+ if (diagramId && diagramId > 0 && onDiagramNameUpdate) {
+ onDiagramNameUpdate(diagramName);
+ }
+
console.log("관계도 저장 완료:", savedDiagram);
} catch (error) {
console.error("관계도 저장 실패:", error);
@@ -853,7 +873,7 @@ export const DataFlowDesigner: React.FC = ({
setIsSaving(false);
}
},
- [tempRelationships, diagramId, companyCode, user?.userId, nodes],
+ [tempRelationships, diagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
);
// 저장 모달 열기
@@ -1136,6 +1156,72 @@ export const DataFlowDesigner: React.FC = ({
>
+
+ {/* 관계 목록 모달 - 캔버스 내부 우측 상단에 배치 */}
+ {showRelationshipListModal && (
+
+ {/* 헤더 */}
+
+
+
+
+
+ {/* 관계 목록 */}
+
+
+ {selectedTablePairRelationships.map((relationship, index) => (
+
{
+ // 관계 선택 시 수정 모드로 전환
+ setEditingRelationshipId(relationship.id);
+
+ // 관련 컬럼 하이라이트
+ const newSelectedColumns: { [tableName: string]: string[] } = {};
+ if (relationship.fromTable && relationship.fromColumns) {
+ newSelectedColumns[relationship.fromTable] = [...relationship.fromColumns];
+ }
+ if (relationship.toTable && relationship.toColumns) {
+ newSelectedColumns[relationship.toTable] = [...relationship.toColumns];
+ }
+ setSelectedColumns(newSelectedColumns);
+
+ // 모달 닫기
+ setShowRelationshipListModal(false);
+ }}
+ className="cursor-pointer rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-blue-50"
+ >
+
+
+ {relationship.fromTable} → {relationship.toTable}
+
+
+
+
+
타입: {relationship.connectionType}
+
From: {relationship.fromTable}
+
To: {relationship.toTable}
+
+
+ ))}
+
+
+
+ )}
{/* 선택된 테이블 노드 팝업 - 캔버스 좌측 상단 고정 (새 관계 생성 시에만 표시) */}