102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
import React from "react";
|
||
import { Handle, Position, NodeResizer } 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;
|
||
onScrollAreaEnter?: () => void;
|
||
onScrollAreaLeave?: () => void;
|
||
selected?: boolean;
|
||
selectedFields?: string[]; // 선택된 필드 목록
|
||
}
|
||
|
||
export const ScreenNode: React.FC<{ data: ScreenNodeData; selected?: boolean }> = ({ data, selected }) => {
|
||
const { screen, onFieldClick, onScrollAreaEnter, onScrollAreaLeave, selectedFields = [] } = data;
|
||
|
||
// 필드 타입에 따른 색상 반환
|
||
const getFieldTypeColor = (type: string) => {
|
||
if (!type || typeof type !== "string") return "text-gray-600 bg-gray-50";
|
||
|
||
const upperType = type.toUpperCase();
|
||
if (upperType.includes("INTEGER") || upperType.includes("NUMERIC")) return "text-blue-600 bg-blue-50";
|
||
if (upperType.includes("VARCHAR") || upperType.includes("TEXT")) return "text-green-600 bg-green-50";
|
||
if (upperType.includes("TIMESTAMP") || upperType.includes("DATE")) return "text-purple-600 bg-purple-50";
|
||
if (upperType.includes("BOOLEAN")) return "text-orange-600 bg-orange-50";
|
||
return "text-gray-600 bg-gray-50";
|
||
};
|
||
|
||
return (
|
||
<div className="flex h-full min-h-52 w-full min-w-80 flex-col rounded-lg border-2 border-gray-300 bg-white shadow-lg transition-shadow hover:shadow-xl">
|
||
{/* NodeResizer - 선택된 경우에만 표시 */}
|
||
{selected && <NodeResizer color="#3B82F6" isVisible={selected} minWidth={320} minHeight={200} />}
|
||
{/* 노드 헤더 */}
|
||
<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="flex flex-1 flex-col overflow-hidden 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="flex-1 space-y-2 overflow-y-auto"
|
||
onMouseEnter={onScrollAreaEnter}
|
||
onMouseLeave={onScrollAreaLeave}
|
||
>
|
||
{screen.fields.map((field, index) => {
|
||
const isSelected = selectedFields.includes(field.name);
|
||
return (
|
||
<div
|
||
key={field.name}
|
||
className={`relative flex cursor-pointer items-center justify-between rounded-lg border p-3 transition-colors ${
|
||
isSelected
|
||
? "border-blue-500 bg-blue-50 hover:bg-blue-100"
|
||
: "border-transparent 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>
|
||
</div>
|
||
);
|
||
};
|