테이블 관계 저장 구현

This commit is contained in:
2025-09-08 16:46:53 +09:00
parent 37fac630b9
commit 0bdfb2ba92
9 changed files with 1980 additions and 166 deletions

View File

@@ -17,7 +17,14 @@ import "@xyflow/react/dist/style.css";
import { TableNode } from "./TableNode";
import { TableSelector } from "./TableSelector";
import { ConnectionSetupModal } from "./ConnectionSetupModal";
import { TableDefinition } from "@/lib/api/dataflow";
import { TableDefinition, TableRelationship, DataFlowAPI } from "@/lib/api/dataflow";
// 고유 ID 생성 함수
const generateUniqueId = (prefix: string, relationshipId?: number): string => {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
return `${prefix}-${relationshipId || timestamp}-${random}`;
};
// 테이블 노드 데이터 타입 정의
interface TableNodeData extends Record<string, unknown> {
@@ -47,19 +54,7 @@ interface DataFlowDesignerProps {
onSave?: (relationships: TableRelationship[]) => void;
}
interface TableRelationship {
relationshipId?: number;
relationshipName: string;
fromTableName: string;
fromColumnName: string;
toTableName: string;
toColumnName: string;
relationshipType: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many";
connectionType: "simple-key" | "data-save" | "external-call";
settings?: Record<string, unknown>;
companyCode: string;
isActive?: string;
}
// TableRelationship 타입은 dataflow.ts에서 import
export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode, onSave }) => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node<TableNodeData>>([]);
@@ -81,6 +76,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
};
};
} | null>(null);
const [relationships, setRelationships] = useState<TableRelationship[]>([]);
const toastShownRef = useRef(false);
// 키보드 이벤트 핸들러 (Del 키로 선택된 노드 삭제)
@@ -117,6 +113,44 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedNodes, setNodes]);
// 기존 관계 로드
const loadExistingRelationships = useCallback(async () => {
try {
const existingRelationships = await DataFlowAPI.getRelationshipsByCompany(companyCode);
setRelationships(existingRelationships);
// 기존 관계를 엣지로 변환하여 표시
const existingEdges = existingRelationships.map((rel) => ({
id: generateUniqueId("edge", rel.relationshipId),
source: `table-${rel.fromTableName}`,
target: `table-${rel.toTableName}`,
sourceHandle: "right",
targetHandle: "left",
type: "default",
data: {
relationshipId: rel.relationshipId,
relationshipType: rel.relationshipType,
connectionType: rel.connectionType,
label: rel.relationshipName,
fromColumn: rel.fromColumnName,
toColumn: rel.toColumnName,
},
}));
setEdges(existingEdges);
} catch (error) {
console.error("기존 관계 로드 실패:", error);
toast.error("기존 관계를 불러오는데 실패했습니다.");
}
}, [companyCode, setEdges]);
// 컴포넌트 마운트 시 기존 관계 로드
useEffect(() => {
if (companyCode) {
loadExistingRelationships();
}
}, [companyCode, loadExistingRelationships]);
// 노드 선택 변경 핸들러
const onSelectionChange = useCallback(({ nodes }: { nodes: Node<TableNodeData>[] }) => {
const selectedNodeIds = nodes.map((node) => node.id);
@@ -229,6 +263,10 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
if (!firstNode || !secondNode) return;
// 첫 번째로 선택된 컬럼들 가져오기
const firstTableColumns = selectedColumns[firstTableName] || [];
const secondTableColumns = selectedColumns[secondTableName] || [];
setPendingConnection({
fromNode: {
id: firstNode.id,
@@ -240,6 +278,9 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
tableName: secondNode.data.table.tableName,
displayName: secondNode.data.table.displayName,
},
// 선택된 첫 번째 컬럼을 연결 컬럼으로 설정
fromColumn: firstTableColumns[0] || "",
toColumn: secondTableColumns[0] || "",
// 선택된 모든 컬럼 정보를 선택 순서대로 전달
selectedColumnsData: (() => {
const orderedData: { [key: string]: { displayName: string; columns: string[] } } = {};
@@ -335,28 +376,41 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
// 연결 설정 확인
const handleConfirmConnection = useCallback(
(config: { relationshipType: string; connectionType: string; relationshipName: string }) => {
(relationship: TableRelationship) => {
if (!pendingConnection) return;
const newEdge = {
id: `edge-${Date.now()}`,
id: generateUniqueId("edge", relationship.relationshipId),
source: pendingConnection.fromNode.id,
target: pendingConnection.toNode.id,
sourceHandle: "right",
targetHandle: "left",
type: "default",
data: {
relationshipType: config.relationshipType,
connectionType: config.connectionType,
label: config.relationshipName,
relationshipId: relationship.relationshipId,
relationshipType: relationship.relationshipType,
connectionType: relationship.connectionType,
label: relationship.relationshipName,
fromColumn: relationship.fromColumnName,
toColumn: relationship.toColumnName,
},
};
setEdges((eds) => [...eds, newEdge]);
setRelationships((prev) => [...prev, relationship]);
setPendingConnection(null);
// TODO: 백엔드 API 호출하여 관계 저장
console.log("연결 설정:", config);
console.log("관계 생성 완료:", relationship);
// 저장 콜백 호출 (필요한 경우)
if (onSave) {
// 현재 모든 관계를 수집하여 전달
setRelationships((currentRelationships) => {
onSave([...currentRelationships, relationship]);
return currentRelationships;
});
}
},
[pendingConnection, setEdges],
[pendingConnection, setEdges, onSave],
);
// 연결 설정 취소
@@ -536,6 +590,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({ companyCode,
<ConnectionSetupModal
isOpen={!!pendingConnection}
connection={pendingConnection}
companyCode={companyCode}
onConfirm={handleConfirmConnection}
onCancel={handleCancelConnection}
/>