109 lines
4.0 KiB
TypeScript
109 lines
4.0 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 참조 테이블 조회 노드 (내부 DB 전용)
|
||
|
|
* 다른 테이블에서 데이터를 조회하여 조건 비교나 필드 매핑에 사용
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { memo } from "react";
|
||
|
|
import { Handle, Position, NodeProps } from "reactflow";
|
||
|
|
import { Link2, Database } from "lucide-react";
|
||
|
|
import type { ReferenceLookupNodeData } from "@/types/node-editor";
|
||
|
|
|
||
|
|
export const ReferenceLookupNode = memo(({ data, selected }: NodeProps<ReferenceLookupNodeData>) => {
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`min-w-[250px] rounded-lg border-2 bg-white shadow-md transition-all ${
|
||
|
|
selected ? "border-purple-500 shadow-lg" : "border-gray-200"
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div className="flex items-center gap-2 rounded-t-lg bg-purple-500 px-3 py-2 text-white">
|
||
|
|
<Link2 className="h-4 w-4" />
|
||
|
|
<div className="flex-1">
|
||
|
|
<div className="text-sm font-semibold">{data.displayName || "참조 조회"}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 본문 */}
|
||
|
|
<div className="p-3">
|
||
|
|
<div className="mb-2 flex items-center gap-1 text-xs font-medium text-gray-500">
|
||
|
|
<Database className="h-3 w-3" />
|
||
|
|
내부 DB 참조
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 참조 테이블 */}
|
||
|
|
{data.referenceTable && (
|
||
|
|
<div className="mb-3 rounded bg-purple-50 p-2">
|
||
|
|
<div className="text-xs font-medium text-purple-700">📋 참조 테이블</div>
|
||
|
|
<div className="mt-1 font-mono text-xs text-purple-900">
|
||
|
|
{data.referenceTableLabel || data.referenceTable}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 조인 조건 */}
|
||
|
|
{data.joinConditions && data.joinConditions.length > 0 && (
|
||
|
|
<div className="mb-3">
|
||
|
|
<div className="text-xs font-medium text-gray-700">🔗 조인 조건:</div>
|
||
|
|
<div className="mt-1 space-y-1">
|
||
|
|
{data.joinConditions.map((join, idx) => (
|
||
|
|
<div key={idx} className="text-xs text-gray-600">
|
||
|
|
<span className="font-medium">{join.sourceFieldLabel || join.sourceField}</span>
|
||
|
|
<span className="mx-1 text-purple-500">→</span>
|
||
|
|
<span className="font-medium">{join.referenceFieldLabel || join.referenceField}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* WHERE 조건 */}
|
||
|
|
{data.whereConditions && data.whereConditions.length > 0 && (
|
||
|
|
<div className="mb-3">
|
||
|
|
<div className="text-xs font-medium text-gray-700">⚡ WHERE 조건:</div>
|
||
|
|
<div className="mt-1 text-xs text-gray-600">{data.whereConditions.length}개 조건</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 출력 필드 */}
|
||
|
|
{data.outputFields && data.outputFields.length > 0 && (
|
||
|
|
<div>
|
||
|
|
<div className="text-xs font-medium text-gray-700">📤 출력 필드:</div>
|
||
|
|
<div className="mt-1 max-h-[100px] space-y-1 overflow-y-auto">
|
||
|
|
{data.outputFields.slice(0, 3).map((field, idx) => (
|
||
|
|
<div key={idx} className="flex items-center gap-2 text-xs text-gray-600">
|
||
|
|
<div className="h-1.5 w-1.5 rounded-full bg-purple-400" />
|
||
|
|
<span className="font-medium">{field.alias}</span>
|
||
|
|
<span className="text-gray-400">← {field.fieldLabel || field.fieldName}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
{data.outputFields.length > 3 && (
|
||
|
|
<div className="text-xs text-gray-400">... 외 {data.outputFields.length - 3}개</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 입력 핸들 (왼쪽) */}
|
||
|
|
<Handle
|
||
|
|
type="target"
|
||
|
|
position={Position.Left}
|
||
|
|
className="!h-3 !w-3 !border-2 !border-purple-500 !bg-white"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 출력 핸들 (오른쪽) */}
|
||
|
|
<Handle
|
||
|
|
type="source"
|
||
|
|
position={Position.Right}
|
||
|
|
className="!h-3 !w-3 !border-2 !border-purple-500 !bg-white"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
ReferenceLookupNode.displayName = "ReferenceLookupNode";
|
||
|
|
|
||
|
|
|