ERP-node/frontend/components/dataflow/TableNode.tsx

107 lines
4.1 KiB
TypeScript

"use client";
import React from "react";
import { Handle, Position } from "@xyflow/react";
interface TableColumn {
name: string;
type: string;
description: string;
}
interface Table {
tableName: string;
displayName: string;
description: string;
columns: TableColumn[];
}
interface TableNodeData {
table: Table;
onColumnClick: (tableName: string, columnName: string) => void;
onScrollAreaEnter?: () => void;
onScrollAreaLeave?: () => void;
selectedColumns?: string[]; // 선택된 컬럼 목록
connectedColumns?: { [columnName: string]: { direction: "source" | "target" | "both" } }; // 연결된 컬럼 정보
}
export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
const {
table,
onColumnClick,
onScrollAreaEnter,
onScrollAreaLeave,
selectedColumns = [],
connectedColumns = {},
} = data;
return (
<div className="relative flex min-w-[280px] flex-col overflow-hidden rounded-lg border-2 border-gray-300 bg-white shadow-lg">
{/* React Flow Handles - 숨김 처리 */}
<Handle type="target" position={Position.Left} id="left" className="!invisible !h-1 !w-1" />
<Handle type="source" position={Position.Right} id="right" className="!invisible !h-1 !w-1" />
{/* 테이블 헤더 */}
<div className="bg-blue-600 p-3 text-white">
<h3 className="truncate text-sm font-semibold">{table.displayName}</h3>
{table.description && <p className="mt-1 truncate text-xs opacity-75">{table.description}</p>}
</div>
{/* 컬럼 목록 */}
<div className="flex-1 overflow-hidden p-2" onMouseEnter={onScrollAreaEnter} onMouseLeave={onScrollAreaLeave}>
<div className="space-y-1">
{table.columns.map((column, index) => {
const isSelected = selectedColumns.includes(column.name);
const connectionInfo = connectedColumns[column.name];
const isConnected = !!connectionInfo;
// 연결된 컬럼에만 핸들 표시
const showSourceHandle =
isConnected && (connectionInfo.direction === "source" || connectionInfo.direction === "both");
const showTargetHandle =
isConnected && (connectionInfo.direction === "target" || connectionInfo.direction === "both");
return (
<div
key={column.name}
className={`relative cursor-pointer rounded px-2 py-1 text-xs transition-colors ${
isSelected ? "bg-blue-100 text-blue-800 ring-2 ring-blue-500" : "text-gray-700 hover:bg-gray-100"
}`}
onClick={() => onColumnClick(table.tableName, column.name)}
>
{/* Target Handle (왼쪽) - 세련된 디자인 */}
{showTargetHandle && (
<Handle
type="target"
position={Position.Left}
id={`${table.tableName}-${column.name}-target`}
className="!absolute !left-[-6px] !h-2 !w-2 !rounded-full !border-0 !bg-blue-500 !shadow-sm hover:!bg-blue-600 hover:!shadow-md"
style={{ top: "50%", transform: "translateY(-50%)" }}
/>
)}
{/* Source Handle (오른쪽) - 세련된 디자인 */}
{showSourceHandle && (
<Handle
type="source"
position={Position.Right}
id={`${table.tableName}-${column.name}-source`}
className="!absolute !right-[-6px] !h-2 !w-2 !rounded-full !border-0 !bg-blue-500 !shadow-sm hover:!bg-blue-600 hover:!shadow-md"
style={{ top: "50%", transform: "translateY(-50%)" }}
/>
)}
<div className="flex items-center justify-between">
<span className="font-mono font-medium">{column.name}</span>
<span className="text-gray-500">{column.type}</span>
</div>
{column.description && <div className="mt-0.5 text-gray-500">{column.description}</div>}
</div>
);
})}
</div>
</div>
</div>
);
};