110 lines
4.0 KiB
TypeScript
110 lines
4.0 KiB
TypeScript
"use client";
|
||
|
||
import React from "react";
|
||
import { Handle, Position } from "@xyflow/react";
|
||
|
||
interface ScreenField {
|
||
name: string;
|
||
type: string;
|
||
description: string;
|
||
}
|
||
|
||
interface Screen {
|
||
screenId: string;
|
||
screenName: string;
|
||
screenCode: string;
|
||
tableName: string;
|
||
fields: ScreenField[];
|
||
}
|
||
|
||
interface ScreenNodeData {
|
||
screen: Screen;
|
||
onFieldClick: (screenId: string, fieldName: string) => void;
|
||
}
|
||
|
||
export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
|
||
const { screen, onFieldClick } = data;
|
||
|
||
// 필드 타입에 따른 색상 반환
|
||
const getFieldTypeColor = (type: string) => {
|
||
if (type.includes("INTEGER") || type.includes("NUMERIC")) return "text-blue-600 bg-blue-50";
|
||
if (type.includes("VARCHAR") || type.includes("TEXT")) return "text-green-600 bg-green-50";
|
||
if (type.includes("TIMESTAMP") || type.includes("DATE")) return "text-purple-600 bg-purple-50";
|
||
if (type.includes("BOOLEAN")) return "text-orange-600 bg-orange-50";
|
||
return "text-gray-600 bg-gray-50";
|
||
};
|
||
|
||
return (
|
||
<div className="max-w-96 min-w-80 rounded-lg border-2 border-gray-300 bg-white shadow-lg transition-shadow hover:shadow-xl">
|
||
{/* 노드 헤더 */}
|
||
<div className="rounded-t-lg bg-gradient-to-r from-blue-500 to-blue-600 p-4 text-white">
|
||
<div className="mb-1 text-base font-bold">{screen.screenName}</div>
|
||
<div className="mb-1 text-sm opacity-90">ID: {screen.screenCode}</div>
|
||
<div className="flex items-center text-xs opacity-75">
|
||
<span className="mr-1">🗃️</span>
|
||
테이블: {screen.tableName}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 필드 목록 */}
|
||
<div className="p-4">
|
||
<div className="mb-3 flex items-center justify-between">
|
||
<div className="text-sm font-semibold text-gray-700">필드 목록</div>
|
||
<div className="rounded-full bg-gray-100 px-2 py-1 text-xs text-gray-500">{screen.fields.length}개</div>
|
||
</div>
|
||
|
||
<div className="max-h-64 space-y-2 overflow-y-auto">
|
||
{screen.fields.map((field, index) => (
|
||
<div
|
||
key={field.name}
|
||
className="flex cursor-pointer items-center justify-between rounded-lg border border-transparent p-3 transition-colors hover:border-gray-200 hover:bg-gray-50"
|
||
onClick={() => onFieldClick(screen.screenId, field.name)}
|
||
>
|
||
<div className="min-w-0 flex-1">
|
||
<div className="mb-1 flex items-center">
|
||
<div className="truncate text-sm font-medium text-gray-900">{field.name}</div>
|
||
{index === 0 && (
|
||
<span className="ml-2 rounded bg-yellow-100 px-1.5 py-0.5 text-xs text-yellow-800">PK</span>
|
||
)}
|
||
</div>
|
||
<div className="truncate text-xs text-gray-500">{field.description}</div>
|
||
</div>
|
||
<div className={`ml-2 rounded px-2 py-1 font-mono text-xs ${getFieldTypeColor(field.type)}`}>
|
||
{field.type}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* React Flow 핸들 */}
|
||
<Handle
|
||
type="source"
|
||
position={Position.Right}
|
||
className="h-4 w-4 border-2 border-white bg-blue-500 shadow-md transition-colors hover:bg-blue-600"
|
||
title="연결 시작점"
|
||
/>
|
||
<Handle
|
||
type="target"
|
||
position={Position.Left}
|
||
className="h-4 w-4 border-2 border-white bg-green-500 shadow-md transition-colors hover:bg-green-600"
|
||
title="연결 도착점"
|
||
/>
|
||
|
||
{/* 추가 핸들들 (상하) */}
|
||
<Handle
|
||
type="source"
|
||
position={Position.Bottom}
|
||
className="h-4 w-4 border-2 border-white bg-blue-500 shadow-md transition-colors hover:bg-blue-600"
|
||
id="bottom-source"
|
||
/>
|
||
<Handle
|
||
type="target"
|
||
position={Position.Top}
|
||
className="h-4 w-4 border-2 border-white bg-green-500 shadow-md transition-colors hover:bg-green-600"
|
||
id="top-target"
|
||
/>
|
||
</div>
|
||
);
|
||
};
|