"use client"; import React, { useState, useEffect, useMemo } from "react"; import { ComponentRendererProps } from "@/types/component"; import { AggregationWidgetConfig, AggregationItem, AggregationResult, AggregationType } from "./types"; import { Calculator, TrendingUp, Hash, ArrowUp, ArrowDown } from "lucide-react"; import { cn } from "@/lib/utils"; import { useScreenMultiLang } from "@/contexts/ScreenMultiLangContext"; interface AggregationWidgetComponentProps extends ComponentRendererProps { config?: AggregationWidgetConfig; // 외부에서 데이터를 직접 전달받을 수 있음 externalData?: any[]; } /** * 집계 위젯 컴포넌트 * 연결된 테이블 리스트나 리피터의 데이터를 집계하여 표시 */ export function AggregationWidgetComponent({ component, isDesignMode = false, config: propsConfig, externalData, }: AggregationWidgetComponentProps) { // 다국어 지원 const { getText } = useScreenMultiLang(); const componentConfig: AggregationWidgetConfig = { dataSourceType: "manual", items: [], layout: "horizontal", showLabels: true, showIcons: true, gap: "16px", ...propsConfig, ...component?.config, }; // 다국어 라벨 가져오기 const getItemLabel = (item: AggregationItem): string => { if (item.labelLangKey) { const translated = getText(item.labelLangKey); if (translated && translated !== item.labelLangKey) { return translated; } } return item.columnLabel || item.columnName || "컬럼"; }; const { dataSourceType, dataSourceComponentId, items, layout, showLabels, showIcons, gap, backgroundColor, borderRadius, padding, fontSize, labelFontSize, valueFontSize, labelColor, valueColor, } = componentConfig; // 데이터 상태 const [data, setData] = useState([]); // 외부 데이터가 있으면 사용 useEffect(() => { if (externalData && Array.isArray(externalData)) { setData(externalData); } }, [externalData]); // 컴포넌트 데이터 변경 이벤트 리스닝 useEffect(() => { if (!dataSourceComponentId || isDesignMode) return; const handleDataChange = (event: CustomEvent) => { const { componentId, data: eventData } = event.detail || {}; if (componentId === dataSourceComponentId && Array.isArray(eventData)) { setData(eventData); } }; // 리피터 데이터 변경 이벤트 window.addEventListener("repeaterDataChange" as any, handleDataChange); // 테이블 리스트 데이터 변경 이벤트 window.addEventListener("tableListDataChange" as any, handleDataChange); return () => { window.removeEventListener("repeaterDataChange" as any, handleDataChange); window.removeEventListener("tableListDataChange" as any, handleDataChange); }; }, [dataSourceComponentId, isDesignMode]); // 집계 계산 const aggregationResults = useMemo((): AggregationResult[] => { if (!items || items.length === 0) { return []; } return items.map((item) => { const values = data .map((row) => { const val = row[item.columnName]; return typeof val === "number" ? val : parseFloat(val) || 0; }) .filter((v) => !isNaN(v)); let value: number = 0; switch (item.type) { case "sum": value = values.reduce((acc, v) => acc + v, 0); break; case "avg": value = values.length > 0 ? values.reduce((acc, v) => acc + v, 0) / values.length : 0; break; case "count": value = data.length; break; case "max": value = values.length > 0 ? Math.max(...values) : 0; break; case "min": value = values.length > 0 ? Math.min(...values) : 0; break; } // 포맷팅 let formattedValue = value.toFixed(item.decimalPlaces ?? 0); if (item.format === "currency") { formattedValue = new Intl.NumberFormat("ko-KR").format(value); } else if (item.format === "percent") { formattedValue = `${(value * 100).toFixed(item.decimalPlaces ?? 1)}%`; } else if (item.format === "number") { formattedValue = new Intl.NumberFormat("ko-KR").format(value); } if (item.prefix) { formattedValue = `${item.prefix}${formattedValue}`; } if (item.suffix) { formattedValue = `${formattedValue}${item.suffix}`; } return { id: item.id, label: getItemLabel(item), value, formattedValue, type: item.type, }; }); }, [data, items, getText]); // 집계 타입에 따른 아이콘 const getIcon = (type: AggregationType) => { switch (type) { case "sum": return ; case "avg": return ; case "count": return ; case "max": return ; case "min": return ; } }; // 집계 타입 라벨 const getTypeLabel = (type: AggregationType) => { switch (type) { case "sum": return "합계"; case "avg": return "평균"; case "count": return "개수"; case "max": return "최대"; case "min": return "최소"; } }; // 디자인 모드 미리보기 if (isDesignMode) { const previewItems: AggregationResult[] = items.length > 0 ? items.map((item) => ({ id: item.id, label: getItemLabel(item), value: 0, formattedValue: item.prefix ? `${item.prefix}0${item.suffix || ""}` : `0${item.suffix || ""}`, type: item.type, })) : [ { id: "1", label: "총 수량", value: 150, formattedValue: "150", type: "sum" }, { id: "2", label: "총 금액", value: 1500000, formattedValue: "₩1,500,000", type: "sum" }, { id: "3", label: "건수", value: 5, formattedValue: "5건", type: "count" }, ]; return (
{previewItems.map((result, index) => (
{showIcons && ( {getIcon(result.type)} )} {showLabels && ( {result.label} ({getTypeLabel(result.type)}): )} {result.formattedValue}
))}
); } // 실제 렌더링 if (aggregationResults.length === 0) { return (
집계 항목을 설정해주세요
); } return (
{aggregationResults.map((result, index) => (
{showIcons && ( {getIcon(result.type)} )} {showLabels && ( {result.label} ({getTypeLabel(result.type)}): )} {result.formattedValue}
))}
); } export const AggregationWidgetWrapper = AggregationWidgetComponent;