89 lines
3.8 KiB
TypeScript
89 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* 데이터 변환 노드
|
|
*/
|
|
|
|
import { memo } from "react";
|
|
import { Handle, Position, NodeProps } from "reactflow";
|
|
import { Wand2, ArrowRight } from "lucide-react";
|
|
import type { DataTransformNodeData } from "@/types/node-editor";
|
|
|
|
export const DataTransformNode = memo(({ data, selected }: NodeProps<DataTransformNodeData>) => {
|
|
return (
|
|
<div
|
|
className={`min-w-[250px] rounded-lg border-2 bg-white shadow-md transition-all ${
|
|
selected ? "border-orange-500 shadow-lg" : "border-gray-200"
|
|
}`}
|
|
>
|
|
{/* 헤더 */}
|
|
<div className="flex items-center gap-2 rounded-t-lg bg-indigo-600 px-3 py-2 text-white">
|
|
<Wand2 className="h-4 w-4" />
|
|
<div className="flex-1">
|
|
<div className="text-sm font-semibold">{data.displayName || "데이터 변환"}</div>
|
|
<div className="text-xs opacity-80">{data.transformations?.length || 0}개 변환</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 본문 */}
|
|
<div className="p-3">
|
|
{data.transformations && data.transformations.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{data.transformations.slice(0, 3).map((transform, idx) => {
|
|
const sourceLabel = transform.sourceFieldLabel || transform.sourceField || "소스";
|
|
const targetField = transform.targetField || transform.sourceField;
|
|
const targetLabel = transform.targetFieldLabel || targetField;
|
|
const isInPlace = !transform.targetField || transform.targetField === transform.sourceField;
|
|
|
|
return (
|
|
<div key={idx} className="rounded bg-indigo-50 p-2">
|
|
<div className="mb-1 flex items-center gap-2 text-xs">
|
|
<span className="font-medium text-indigo-700">{transform.type}</span>
|
|
</div>
|
|
<div className="text-xs text-gray-600">
|
|
{sourceLabel}
|
|
<span className="mx-1 text-gray-400">→</span>
|
|
{isInPlace ? (
|
|
<span className="font-medium text-indigo-600">(자기자신)</span>
|
|
) : (
|
|
<span>{targetLabel}</span>
|
|
)}
|
|
</div>
|
|
{/* 타입별 추가 정보 */}
|
|
{transform.type === "EXPLODE" && transform.delimiter && (
|
|
<div className="mt-1 text-xs text-gray-500">구분자: {transform.delimiter}</div>
|
|
)}
|
|
{transform.type === "CONCAT" && transform.separator && (
|
|
<div className="mt-1 text-xs text-gray-500">구분자: {transform.separator}</div>
|
|
)}
|
|
{transform.type === "REPLACE" && (
|
|
<div className="mt-1 text-xs text-gray-500">
|
|
"{transform.searchValue}" → "{transform.replaceValue}"
|
|
</div>
|
|
)}
|
|
{transform.expression && (
|
|
<div className="mt-1 text-xs text-gray-500">
|
|
<code className="rounded bg-white px-1 py-0.5">{transform.expression}</code>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
{data.transformations.length > 3 && (
|
|
<div className="text-xs text-gray-400">... 외 {data.transformations.length - 3}개</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="py-4 text-center text-xs text-gray-400">변환 규칙 없음</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 핸들 */}
|
|
<Handle type="target" position={Position.Left} className="!h-3 !w-3 !bg-indigo-500" />
|
|
<Handle type="source" position={Position.Right} className="!h-3 !w-3 !bg-indigo-500" />
|
|
</div>
|
|
);
|
|
});
|
|
|
|
DataTransformNode.displayName = "DataTransformNode";
|