관계도 조회 구현

This commit is contained in:
hyeonsu
2025-09-09 12:00:58 +09:00
parent 7260ad733b
commit 3a24fd3ebd
3 changed files with 199 additions and 94 deletions

View File

@@ -62,7 +62,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
companyCode,
onSave,
selectedDiagram,
onBackToList,
onBackToList, // eslint-disable-line @typescript-eslint/no-unused-vars
}) => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node<TableNodeData>>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
@@ -84,7 +84,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
};
} | null>(null);
const [relationships, setRelationships] = useState<TableRelationship[]>([]); // eslint-disable-line @typescript-eslint/no-unused-vars
const toastShownRef = useRef(false);
const toastShownRef = useRef(false); // eslint-disable-line @typescript-eslint/no-unused-vars
// 키보드 이벤트 핸들러 (Del 키로 선택된 노드 삭제)
useEffect(() => {
@@ -212,6 +212,38 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
}
}
// 연결된 컬럼 정보 계산
const connectedColumnsInfo: {
[tableName: string]: { [columnName: string]: { direction: "source" | "target" | "both" } };
} = {};
diagramRelationships.forEach((rel) => {
const fromTable = rel.from_table_name;
const toTable = rel.to_table_name;
const fromColumns = rel.from_column_name.split(",").map((col) => col.trim());
const toColumns = rel.to_column_name.split(",").map((col) => col.trim());
// 소스 테이블의 컬럼들을 source로 표시
if (!connectedColumnsInfo[fromTable]) connectedColumnsInfo[fromTable] = {};
fromColumns.forEach((col) => {
if (connectedColumnsInfo[fromTable][col]) {
connectedColumnsInfo[fromTable][col].direction = "both";
} else {
connectedColumnsInfo[fromTable][col] = { direction: "source" };
}
});
// 타겟 테이블의 컬럼들을 target으로 표시
if (!connectedColumnsInfo[toTable]) connectedColumnsInfo[toTable] = {};
toColumns.forEach((col) => {
if (connectedColumnsInfo[toTable][col]) {
connectedColumnsInfo[toTable][col].direction = "both";
} else {
connectedColumnsInfo[toTable][col] = { direction: "target" };
}
});
});
// 테이블을 노드로 변환 (자동 레이아웃)
const tableNodes = tableDefinitions.map((table, index) => {
const x = (index % 3) * 400 + 100; // 3열 배치
@@ -234,6 +266,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
},
onColumnClick: handleColumnClick,
selectedColumns: selectedColumns[table.tableName] || [],
connectedColumns: connectedColumnsInfo[table.tableName] || {},
} as TableNodeData,
};
});
@@ -242,23 +275,47 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("📍 테이블 노드 상세:", tableNodes);
setNodes(tableNodes);
// 관계를 엣지로 변환하여 표시
const relationshipEdges = diagramRelationships.map((rel) => ({
id: generateUniqueId("edge", rel.relationship_id),
source: `table-${rel.from_table_name}`,
target: `table-${rel.to_table_name}`,
sourceHandle: "right",
targetHandle: "left",
type: "default",
data: {
relationshipId: rel.relationship_id,
relationshipType: rel.relationship_type,
connectionType: rel.connection_type,
label: rel.relationship_name,
fromColumn: rel.from_column_name,
toColumn: rel.to_column_name,
},
}));
// 관계를 엣지로 변환하여 표시 (컬럼별 연결)
const relationshipEdges: Edge[] = [];
diagramRelationships.forEach((rel) => {
const fromTable = rel.from_table_name;
const toTable = rel.to_table_name;
const fromColumns = rel.from_column_name.split(",").map((col) => col.trim());
const toColumns = rel.to_column_name.split(",").map((col) => col.trim());
// 각 from 컬럼을 각 to 컬럼에 연결 (1:1 매핑이거나 many:many인 경우)
const maxConnections = Math.max(fromColumns.length, toColumns.length);
for (let i = 0; i < maxConnections; i++) {
const fromColumn = fromColumns[i] || fromColumns[0]; // 컬럼이 부족하면 첫 번째 컬럼 재사용
const toColumn = toColumns[i] || toColumns[0]; // 컬럼이 부족하면 첫 번째 컬럼 재사용
relationshipEdges.push({
id: generateUniqueId("edge", rel.relationship_id),
source: `table-${fromTable}`,
target: `table-${toTable}`,
sourceHandle: `${fromTable}-${fromColumn}-source`,
targetHandle: `${toTable}-${toColumn}-target`,
type: "smoothstep",
animated: false,
style: {
stroke: "#3b82f6",
strokeWidth: 1.5,
strokeDasharray: "none",
},
data: {
relationshipId: rel.relationship_id,
relationshipType: rel.relationship_type,
connectionType: rel.connection_type,
fromTable: fromTable,
toTable: toTable,
fromColumn: fromColumn,
toColumn: toColumn,
},
});
}
});
console.log("🔗 생성된 관계 에지 수:", relationshipEdges.length);
console.log("📍 관계 에지 상세:", relationshipEdges);
@@ -268,7 +325,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.error("선택된 관계도 로드 실패:", error);
toast.error("관계도를 불러오는데 실패했습니다.", { id: "load-diagram" });
}
}, [selectedDiagram, companyCode, setNodes, setEdges, selectedColumns, handleColumnClick]);
}, [selectedDiagram, setNodes, setEdges, selectedColumns, handleColumnClick]);
// 기존 관계 로드 (새 관계도 생성 시)
const loadExistingRelationships = useCallback(async () => {
@@ -278,23 +335,47 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
const existingRelationships = await DataFlowAPI.getRelationshipsByCompany(companyCode);
setRelationships(existingRelationships);
// 기존 관계를 엣지로 변환하여 표시
const existingEdges = existingRelationships.map((rel) => ({
id: generateUniqueId("edge", rel.relationship_id),
source: `table-${rel.from_table_name}`,
target: `table-${rel.to_table_name}`,
sourceHandle: "right",
targetHandle: "left",
type: "default",
data: {
relationshipId: rel.relationship_id,
relationshipType: rel.relationship_type,
connectionType: rel.connection_type,
label: rel.relationship_name,
fromColumn: rel.from_column_name,
toColumn: rel.to_column_name,
},
}));
// 기존 관계를 엣지로 변환하여 표시 (컬럼별 연결)
const existingEdges: Edge[] = [];
existingRelationships.forEach((rel) => {
const fromTable = rel.from_table_name;
const toTable = rel.to_table_name;
const fromColumns = rel.from_column_name.split(",").map((col) => col.trim());
const toColumns = rel.to_column_name.split(",").map((col) => col.trim());
// 각 from 컬럼을 각 to 컬럼에 연결
const maxConnections = Math.max(fromColumns.length, toColumns.length);
for (let i = 0; i < maxConnections; i++) {
const fromColumn = fromColumns[i] || fromColumns[0];
const toColumn = toColumns[i] || toColumns[0];
existingEdges.push({
id: generateUniqueId("edge", rel.relationship_id),
source: `table-${fromTable}`,
target: `table-${toTable}`,
sourceHandle: `${fromTable}-${fromColumn}-source`,
targetHandle: `${toTable}-${toColumn}-target`,
type: "smoothstep",
animated: false,
style: {
stroke: "#3b82f6",
strokeWidth: 1.5,
strokeDasharray: "none",
},
data: {
relationshipId: rel.relationship_id,
relationshipType: rel.relationship_type,
connectionType: rel.connection_type,
fromTable: fromTable,
toTable: toTable,
fromColumn: fromColumn,
toColumn: toColumn,
},
});
}
});
setEdges(existingEdges);
} catch (error) {
@@ -488,24 +569,45 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
(relationship: TableRelationship) => {
if (!pendingConnection) return;
const newEdge = {
id: generateUniqueId("edge", relationship.relationship_id),
source: pendingConnection.fromNode.id,
target: pendingConnection.toNode.id,
sourceHandle: "right",
targetHandle: "left",
type: "default",
data: {
relationshipId: relationship.relationship_id,
relationshipType: relationship.relationship_type,
connectionType: relationship.connection_type,
label: relationship.relationship_name,
fromColumn: relationship.from_column_name,
toColumn: relationship.to_column_name,
},
};
// 컬럼별 에지 생성
const newEdges: Edge[] = [];
const fromTable = relationship.from_table_name;
const toTable = relationship.to_table_name;
const fromColumns = relationship.from_column_name.split(",").map((col) => col.trim());
const toColumns = relationship.to_column_name.split(",").map((col) => col.trim());
setEdges((eds) => [...eds, newEdge]);
const maxConnections = Math.max(fromColumns.length, toColumns.length);
for (let i = 0; i < maxConnections; i++) {
const fromColumn = fromColumns[i] || fromColumns[0];
const toColumn = toColumns[i] || toColumns[0];
newEdges.push({
id: generateUniqueId("edge", relationship.relationship_id),
source: pendingConnection.fromNode.id,
target: pendingConnection.toNode.id,
sourceHandle: `${fromTable}-${fromColumn}-source`,
targetHandle: `${toTable}-${toColumn}-target`,
type: "smoothstep",
animated: false,
style: {
stroke: "#3b82f6",
strokeWidth: 1.5,
strokeDasharray: "none",
},
data: {
relationshipId: relationship.relationship_id,
relationshipType: relationship.relationship_type,
connectionType: relationship.connection_type,
fromTable: fromTable,
toTable: toTable,
fromColumn: fromColumn,
toColumn: toColumn,
},
});
}
setEdges((eds) => [...eds, ...newEdges]);
setRelationships((prev) => [...prev, relationship]);
setPendingConnection(null);