제어 집계함수 노드 추가
This commit is contained in:
107
frontend/components/dataflow/node-editor/nodes/AggregateNode.tsx
Normal file
107
frontend/components/dataflow/node-editor/nodes/AggregateNode.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 집계 노드 (Aggregate Node)
|
||||
* SUM, COUNT, AVG, MIN, MAX 등 집계 연산을 수행
|
||||
*/
|
||||
|
||||
import { memo } from "react";
|
||||
import { Handle, Position, NodeProps } from "reactflow";
|
||||
import { Calculator, Layers } from "lucide-react";
|
||||
import type { AggregateNodeData, AggregateFunction } from "@/types/node-editor";
|
||||
|
||||
// 집계 함수별 아이콘/라벨
|
||||
const AGGREGATE_FUNCTION_LABELS: Record<AggregateFunction, string> = {
|
||||
SUM: "합계",
|
||||
COUNT: "개수",
|
||||
AVG: "평균",
|
||||
MIN: "최소",
|
||||
MAX: "최대",
|
||||
FIRST: "첫번째",
|
||||
LAST: "마지막",
|
||||
};
|
||||
|
||||
export const AggregateNode = memo(({ data, selected }: NodeProps<AggregateNodeData>) => {
|
||||
const groupByCount = data.groupByFields?.length || 0;
|
||||
const aggregationCount = data.aggregations?.length || 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`min-w-[280px] 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-600 px-3 py-2 text-white">
|
||||
<Calculator className="h-4 w-4" />
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-semibold">{data.displayName || "집계"}</div>
|
||||
<div className="text-xs opacity-80">
|
||||
{groupByCount > 0 ? `${groupByCount}개 그룹` : "전체"} / {aggregationCount}개 집계
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 본문 */}
|
||||
<div className="p-3 space-y-3">
|
||||
{/* 그룹 기준 */}
|
||||
{groupByCount > 0 && (
|
||||
<div className="rounded bg-purple-50 p-2">
|
||||
<div className="flex items-center gap-1 mb-1">
|
||||
<Layers className="h-3 w-3 text-purple-600" />
|
||||
<span className="text-xs font-medium text-purple-700">그룹 기준</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{data.groupByFields.slice(0, 3).map((field, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="inline-flex items-center rounded bg-purple-100 px-2 py-0.5 text-xs text-purple-700"
|
||||
>
|
||||
{field.fieldLabel || field.field}
|
||||
</span>
|
||||
))}
|
||||
{data.groupByFields.length > 3 && (
|
||||
<span className="text-xs text-purple-500">+{data.groupByFields.length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 집계 연산 */}
|
||||
{aggregationCount > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{data.aggregations.slice(0, 4).map((agg, idx) => (
|
||||
<div key={agg.id || idx} className="rounded bg-gray-50 p-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="rounded bg-purple-600 px-1.5 py-0.5 text-xs font-medium text-white">
|
||||
{AGGREGATE_FUNCTION_LABELS[agg.function] || agg.function}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{agg.outputFieldLabel || agg.outputField}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-600">
|
||||
{agg.sourceFieldLabel || agg.sourceField}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{data.aggregations.length > 4 && (
|
||||
<div className="text-xs text-gray-400 text-center">
|
||||
... 외 {data.aggregations.length - 4}개
|
||||
</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-purple-500" />
|
||||
<Handle type="source" position={Position.Right} className="!h-3 !w-3 !bg-purple-500" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
AggregateNode.displayName = "AggregateNode";
|
||||
|
||||
Reference in New Issue
Block a user