"use client"; /** * 집계 노드 속성 편집 패널 * SUM, COUNT, AVG, MIN, MAX 등 집계 연산 설정 */ import { useEffect, useState, useCallback } from "react"; import { Plus, Trash2, Calculator, Layers, Filter } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import type { AggregateNodeData, AggregateFunction } from "@/types/node-editor"; interface AggregatePropertiesProps { nodeId: string; data: AggregateNodeData; } // 집계 함수 옵션 const AGGREGATE_FUNCTIONS: Array<{ value: AggregateFunction; label: string; description: string }> = [ { value: "SUM", label: "합계 (SUM)", description: "숫자 필드의 합계를 계산합니다" }, { value: "COUNT", label: "개수 (COUNT)", description: "레코드 개수를 계산합니다" }, { value: "AVG", label: "평균 (AVG)", description: "숫자 필드의 평균을 계산합니다" }, { value: "MIN", label: "최소 (MIN)", description: "최소값을 찾습니다" }, { value: "MAX", label: "최대 (MAX)", description: "최대값을 찾습니다" }, { value: "FIRST", label: "첫번째 (FIRST)", description: "그룹의 첫 번째 값을 가져옵니다" }, { value: "LAST", label: "마지막 (LAST)", description: "그룹의 마지막 값을 가져옵니다" }, ]; // 비교 연산자 옵션 const OPERATORS = [ { value: "=", label: "같음 (=)" }, { value: "!=", label: "다름 (!=)" }, { value: ">", label: "보다 큼 (>)" }, { value: ">=", label: "크거나 같음 (>=)" }, { value: "<", label: "보다 작음 (<)" }, { value: "<=", label: "작거나 같음 (<=)" }, ]; export function AggregateProperties({ nodeId, data }: AggregatePropertiesProps) { const { updateNode, nodes, edges } = useFlowEditorStore(); // 로컬 상태 const [displayName, setDisplayName] = useState(data.displayName || "집계"); const [groupByFields, setGroupByFields] = useState(data.groupByFields || []); const [aggregations, setAggregations] = useState(data.aggregations || []); const [havingConditions, setHavingConditions] = useState(data.havingConditions || []); // 소스 필드 목록 (연결된 입력 노드에서 가져오기) const [sourceFields, setSourceFields] = useState>([]); // 데이터 변경 시 로컬 상태 업데이트 useEffect(() => { setDisplayName(data.displayName || "집계"); setGroupByFields(data.groupByFields || []); setAggregations(data.aggregations || []); setHavingConditions(data.havingConditions || []); }, [data]); // 연결된 소스 노드에서 필드 가져오기 useEffect(() => { const inputEdges = edges.filter((edge) => edge.target === nodeId); const sourceNodeIds = inputEdges.map((edge) => edge.source); const sourceNodes = nodes.filter((node) => sourceNodeIds.includes(node.id)); const fields: Array<{ name: string; label?: string; type?: string }> = []; sourceNodes.forEach((node) => { if (node.data.fields) { node.data.fields.forEach((field: any) => { fields.push({ name: field.name, label: field.label || field.displayName, type: field.type, }); }); } }); setSourceFields(fields); }, [nodeId, nodes, edges]); // 저장 함수 const saveToNode = useCallback( (updates: Partial) => { updateNode(nodeId, { displayName, groupByFields, aggregations, havingConditions, ...updates, }); }, [nodeId, updateNode, displayName, groupByFields, aggregations, havingConditions] ); // 그룹 기준 필드 토글 const handleGroupByToggle = (fieldName: string, checked: boolean) => { let newGroupByFields; if (checked) { const field = sourceFields.find((f) => f.name === fieldName); newGroupByFields = [...groupByFields, { field: fieldName, fieldLabel: field?.label }]; } else { newGroupByFields = groupByFields.filter((f) => f.field !== fieldName); } setGroupByFields(newGroupByFields); saveToNode({ groupByFields: newGroupByFields }); }; // 집계 연산 추가 const handleAddAggregation = () => { const newAggregation = { id: `agg_${Date.now()}`, sourceField: "", sourceFieldLabel: "", function: "SUM" as AggregateFunction, outputField: "", outputFieldLabel: "", }; const newAggregations = [...aggregations, newAggregation]; setAggregations(newAggregations); saveToNode({ aggregations: newAggregations }); }; // 집계 연산 삭제 const handleRemoveAggregation = (index: number) => { const newAggregations = aggregations.filter((_, i) => i !== index); setAggregations(newAggregations); saveToNode({ aggregations: newAggregations }); }; // 집계 연산 변경 const handleAggregationChange = (index: number, field: string, value: any) => { const newAggregations = [...aggregations]; if (field === "sourceField") { const sourceField = sourceFields.find((f) => f.name === value); newAggregations[index] = { ...newAggregations[index], sourceField: value, sourceFieldLabel: sourceField?.label, // 출력 필드명 자동 생성 (예: sum_amount) outputField: newAggregations[index].outputField || `${newAggregations[index].function.toLowerCase()}_${value}`, }; } else if (field === "function") { newAggregations[index] = { ...newAggregations[index], function: value, // 출력 필드명 업데이트 outputField: newAggregations[index].sourceField ? `${value.toLowerCase()}_${newAggregations[index].sourceField}` : newAggregations[index].outputField, }; } else { newAggregations[index] = { ...newAggregations[index], [field]: value }; } setAggregations(newAggregations); saveToNode({ aggregations: newAggregations }); }; // HAVING 조건 추가 const handleAddHavingCondition = () => { const newCondition = { field: "", operator: "=", value: "", }; const newConditions = [...havingConditions, newCondition]; setHavingConditions(newConditions); saveToNode({ havingConditions: newConditions }); }; // HAVING 조건 삭제 const handleRemoveHavingCondition = (index: number) => { const newConditions = havingConditions.filter((_, i) => i !== index); setHavingConditions(newConditions); saveToNode({ havingConditions: newConditions }); }; // HAVING 조건 변경 const handleHavingConditionChange = (index: number, field: string, value: any) => { const newConditions = [...havingConditions]; newConditions[index] = { ...newConditions[index], [field]: value }; setHavingConditions(newConditions); saveToNode({ havingConditions: newConditions }); }; // 집계 결과 필드 목록 (HAVING 조건에서 선택용) const aggregatedFields = aggregations .filter((agg) => agg.outputField) .map((agg) => ({ name: agg.outputField, label: agg.outputFieldLabel || agg.outputField, })); return (
{/* 헤더 */}
집계 노드
{/* 기본 정보 */}

기본 정보

{ setDisplayName(e.target.value); saveToNode({ displayName: e.target.value }); }} className="mt-1" placeholder="노드 표시 이름" />
{/* 그룹 기준 필드 */}

그룹 기준 필드

선택한 필드를 기준으로 데이터를 그룹화합니다. 선택하지 않으면 전체 데이터를 하나의 그룹으로 처리합니다.

{sourceFields.length === 0 ? (
연결된 소스 노드가 없습니다
) : (
{sourceFields.map((field) => { const isChecked = groupByFields.some((f) => f.field === field.name); return (
handleGroupByToggle(field.name, checked as boolean)} />
); })}
)} {groupByFields.length > 0 && (
{groupByFields.map((field) => ( {field.fieldLabel || field.field} ))}
)}
{/* 집계 연산 */}

집계 연산

SUM, COUNT, AVG 등 집계 연산을 설정합니다.

{aggregations.length === 0 ? (
집계 연산을 추가하세요
) : (
{aggregations.map((agg, index) => (
집계 #{index + 1}
{/* 집계 함수 선택 */}
{/* 소스 필드 선택 */}
{/* 출력 필드명 */}
handleAggregationChange(index, "outputField", e.target.value)} placeholder="예: total_amount" className="mt-1 h-8 text-xs" />

집계 결과가 저장될 필드명입니다

{/* 출력 필드 라벨 */}
handleAggregationChange(index, "outputFieldLabel", e.target.value)} placeholder="예: 총 금액" className="mt-1 h-8 text-xs" />
))}
)}
{/* HAVING 조건 (선택) */}

집계 후 필터 (HAVING)

집계 결과에 대한 필터링 조건을 설정합니다 (선택 사항).

{havingConditions.length === 0 ? (
집계 후 필터링이 필요하면 조건을 추가하세요
) : (
{havingConditions.map((condition, index) => (
{/* 집계 결과 필드 선택 */} {/* 연산자 선택 */} {/* 비교값 */} handleHavingConditionChange(index, "value", e.target.value)} placeholder="값" className="h-8 flex-1 text-xs" /> {/* 삭제 버튼 */}
))}
)}
{/* 미리보기 */} {(groupByFields.length > 0 || aggregations.length > 0) && (

집계 결과 미리보기

그룹 기준:{" "} {groupByFields.length > 0 ? groupByFields.map((f) => f.fieldLabel || f.field).join(", ") : "전체 (그룹 없음)"}
집계 컬럼:{" "} {aggregations.length > 0 ? aggregations .filter((a) => a.outputField) .map((a) => `${a.function}(${a.sourceFieldLabel || a.sourceField}) → ${a.outputFieldLabel || a.outputField}`) .join(", ") : "없음"}
)}
); }