"use client"; import React, { useEffect, useState, useCallback, useMemo } from "react"; import { DashboardElement, ChartDataSource } from "@/components/admin/dashboard/types"; import { Button } from "@/components/ui/button"; import { Loader2, RefreshCw } from "lucide-react"; import { applyColumnMapping } from "@/lib/utils/columnMapping"; import { LineChart, Line, BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, ComposedChart, // ๐Ÿ†• ๋ฐ”/๋ผ์ธ/์˜์—ญ ํ˜ผํ•ฉ ์ฐจํŠธ Area, // ๐Ÿ†• ์˜์—ญ ์ฐจํŠธ } from "recharts"; interface ChartTestWidgetProps { element: DashboardElement; } const COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"]; export default function ChartTestWidget({ element }: ChartTestWidgetProps) { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [lastRefreshTime, setLastRefreshTime] = useState(null); console.log("๐Ÿงช ChartTestWidget ๋ Œ๋”๋ง!", element); const dataSources = useMemo(() => { return element?.dataSources || element?.chartConfig?.dataSources; }, [element?.dataSources, element?.chartConfig?.dataSources]); // ๋‹ค์ค‘ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋กœ๋”ฉ const loadMultipleDataSources = useCallback(async () => { // dataSources๋Š” element.dataSources ๋˜๋Š” chartConfig.dataSources์—์„œ ๋กœ๋“œ const dataSources = element?.dataSources || element?.chartConfig?.dataSources; if (!dataSources || dataSources.length === 0) { console.log("โš ๏ธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); return; } console.log(`๐Ÿ”„ \${dataSources.length}๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋กœ๋”ฉ ์‹œ์ž‘...`); setLoading(true); setError(null); try { // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ณ‘๋ ฌ๋กœ ๋กœ๋”ฉ const results = await Promise.allSettled( dataSources.map(async (source) => { try { console.log(`๐Ÿ“ก ๋ฐ์ดํ„ฐ ์†Œ์Šค "\${source.name || source.id}" ๋กœ๋”ฉ ์ค‘...`); if (source.type === "api") { return await loadRestApiData(source); } else if (source.type === "database") { return await loadDatabaseData(source); } return []; } catch (err: any) { console.error(`โŒ ๋ฐ์ดํ„ฐ ์†Œ์Šค "\${source.name || source.id}" ๋กœ๋”ฉ ์‹คํŒจ:`, err); return []; } }), ); // ์„ฑ๊ณตํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ณ‘ํ•ฉ const allData: any[] = []; results.forEach((result, index) => { if (result.status === "fulfilled" && Array.isArray(result.value)) { const sourceData = result.value.map((item: any) => ({ ...item, _source: dataSources[index].name || dataSources[index].id || `์†Œ์Šค \${index + 1}`, })); allData.push(...sourceData); } }); console.log(`โœ… ์ด \${allData.length}๊ฐœ์˜ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ`); setData(allData); setLastRefreshTime(new Date()); } catch (err: any) { console.error("โŒ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜:", err); setError(err.message); } finally { setLoading(false); } }, [element?.dataSources]); // ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ํ•ธ๋“ค๋Ÿฌ const handleManualRefresh = useCallback(() => { console.log("๐Ÿ”„ ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ ํด๋ฆญ"); loadMultipleDataSources(); }, [loadMultipleDataSources]); // REST API ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ const loadRestApiData = async (source: ChartDataSource): Promise => { if (!source.endpoint) { throw new Error("API endpoint๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); } const queryParams: Record = {}; if (source.queryParams) { source.queryParams.forEach((param) => { if (param.key && param.value) { queryParams[param.key] = param.value; } }); } const headers: Record = {}; if (source.headers) { source.headers.forEach((header) => { if (header.key && header.value) { headers[header.key] = header.value; } }); } const response = await fetch("/api/dashboards/fetch-external-api", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ url: source.endpoint, method: source.method || "GET", headers, queryParams, }), }); if (!response.ok) { throw new Error(`API ํ˜ธ์ถœ ์‹คํŒจ: \${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || "API ํ˜ธ์ถœ ์‹คํŒจ"); } let apiData = result.data; if (source.jsonPath) { const pathParts = source.jsonPath.split("."); for (const part of pathParts) { apiData = apiData?.[part]; } } const rows = Array.isArray(apiData) ? apiData : [apiData]; // ์ปฌ๋Ÿผ ๋งคํ•‘ ์ ์šฉ return applyColumnMapping(rows, source.columnMapping); }; // Database ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ const loadDatabaseData = async (source: ChartDataSource): Promise => { if (!source.query) { throw new Error("SQL ์ฟผ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); } let result; if (source.connectionType === "external" && source.externalConnectionId) { // ์™ธ๋ถ€ DB (ExternalDbConnectionAPI ์‚ฌ์šฉ) const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection"); result = await ExternalDbConnectionAPI.executeQuery(parseInt(source.externalConnectionId), source.query); } else { // ํ˜„์žฌ DB (dashboardApi.executeQuery ์‚ฌ์šฉ) const { dashboardApi } = await import("@/lib/api/dashboard"); try { const queryResult = await dashboardApi.executeQuery(source.query); result = { success: true, rows: queryResult.rows || [], }; } catch (err: any) { console.error("โŒ ๋‚ด๋ถ€ DB ์ฟผ๋ฆฌ ์‹คํŒจ:", err); throw new Error(err.message || "์ฟผ๋ฆฌ ์‹คํŒจ"); } } if (!result.success) { throw new Error(result.message || "์ฟผ๋ฆฌ ์‹คํŒจ"); } const rows = result.rows || result.data || []; console.log("๐Ÿ’พ ๋‚ด๋ถ€ DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ:", { hasRows: !!rows, rowCount: rows.length, hasColumns: rows.length > 0 && Object.keys(rows[0]).length > 0, columnCount: rows.length > 0 ? Object.keys(rows[0]).length : 0, firstRow: rows[0], }); // ์ปฌ๋Ÿผ ๋งคํ•‘ ์ ์šฉ const mappedRows = applyColumnMapping(rows, source.columnMapping); console.log("โœ… ๋งคํ•‘ ํ›„:", { columns: mappedRows.length > 0 ? Object.keys(mappedRows[0]) : [], rowCount: mappedRows.length, firstMappedRow: mappedRows[0], }); return mappedRows; }; // ์ดˆ๊ธฐ ๋กœ๋“œ useEffect(() => { if (dataSources && dataSources.length > 0) { loadMultipleDataSources(); } }, [dataSources, loadMultipleDataSources]); // ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ useEffect(() => { if (!dataSources || dataSources.length === 0) return; const intervals = dataSources .map((ds) => ds.refreshInterval) .filter((interval): interval is number => typeof interval === "number" && interval > 0); if (intervals.length === 0) return; const minInterval = Math.min(...intervals); console.log(`โฑ๏ธ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์„ค์ •: ${minInterval}์ดˆ๋งˆ๋‹ค`); const intervalId = setInterval(() => { console.log("๐Ÿ”„ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํ–‰"); loadMultipleDataSources(); }, minInterval * 1000); return () => { console.log("โน๏ธ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ ์ •๋ฆฌ"); clearInterval(intervalId); }; }, [dataSources, loadMultipleDataSources]); const chartConfig = element?.chartConfig || {}; const chartType = chartConfig.chartType || "line"; const mergeMode = chartConfig.mergeMode || false; const dataSourceConfigs = chartConfig.dataSourceConfigs || []; // ๋ฉ€ํ‹ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ฐจํŠธ ๋ Œ๋”๋ง const renderChart = () => { if (data.length === 0) { return (

๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

); } if (dataSourceConfigs.length === 0) { return (

์ฐจํŠธ ์„ค์ •์—์„œ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ 
X์ถ•, Y์ถ•์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”

); } // ๋ณ‘ํ•ฉ ๋ชจ๋“œ: ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ํ•˜๋‚˜์˜ ๋ผ์ธ/๋ฐ”๋กœ ํ•ฉ์นจ if (mergeMode && dataSourceConfigs.length > 1) { const chartData: any[] = []; const allXValues = new Set(); // ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ์†Œ์Šค์˜ ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ const baseConfig = dataSourceConfigs[0]; const xAxisField = baseConfig.xAxis; const yAxisField = baseConfig.yAxis[0]; // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์†Œ์Šค์—์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ (X์ถ• ๊ฐ’ ๊ธฐ์ค€) dataSourceConfigs.forEach((dsConfig) => { const sourceName = dataSources.find((ds) => ds.id === dsConfig.dataSourceId)?.name; const sourceData = data.filter((item) => item._source === sourceName); sourceData.forEach((item) => { const xValue = item[xAxisField]; if (xValue !== undefined) { allXValues.add(String(xValue)); } }); }); // X์ถ• ๊ฐ’๋ณ„๋กœ Y์ถ• ๊ฐ’ ํ•ฉ์‚ฐ allXValues.forEach((xValue) => { const dataPoint: any = { _xValue: xValue }; let totalYValue = 0; dataSourceConfigs.forEach((dsConfig) => { const sourceName = dataSources.find((ds) => ds.id === dsConfig.dataSourceId)?.name; const sourceData = data.filter((item) => item._source === sourceName); const matchingItem = sourceData.find((item) => String(item[xAxisField]) === xValue); if (matchingItem && yAxisField) { const yValue = parseFloat(matchingItem[yAxisField]) || 0; totalYValue += yValue; } }); dataPoint[yAxisField] = totalYValue; chartData.push(dataPoint); }); console.log("๐Ÿ”— ๋ณ‘ํ•ฉ ๋ชจ๋“œ ์ฐจํŠธ ๋ฐ์ดํ„ฐ:", chartData); // ๋ณ‘ํ•ฉ ๋ชจ๋“œ ์ฐจํŠธ ๋ Œ๋”๋ง switch (chartType) { case "line": return ( ); case "bar": return ( ); case "area": return ( ); default: return (

๋ณ‘ํ•ฉ ๋ชจ๋“œ๋Š” ๋ผ์ธ, ๋ฐ”, ์˜์—ญ ์ฐจํŠธ๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค

); } } // ์ผ๋ฐ˜ ๋ชจ๋“œ: ๊ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ณ„๋„์˜ ๋ผ์ธ/๋ฐ”๋กœ ํ‘œ์‹œ const chartData: any[] = []; const allXValues = new Set(); // 1๋‹จ๊ณ„: ๋ชจ๋“  X์ถ• ๊ฐ’ ์ˆ˜์ง‘ dataSourceConfigs.forEach((dsConfig) => { const sourceData = data.filter((item) => { const sourceName = dataSources.find((ds) => ds.id === dsConfig.dataSourceId)?.name; return item._source === sourceName; }); sourceData.forEach((item) => { const xValue = item[dsConfig.xAxis]; if (xValue !== undefined) { allXValues.add(String(xValue)); } }); }); // 2๋‹จ๊ณ„: X์ถ• ๊ฐ’๋ณ„๋กœ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ allXValues.forEach((xValue) => { const dataPoint: any = { _xValue: xValue }; dataSourceConfigs.forEach((dsConfig, index) => { const sourceName = dataSources.find((ds) => ds.id === dsConfig.dataSourceId)?.name || `์†Œ์Šค ${index + 1}`; const sourceData = data.filter((item) => item._source === sourceName); const matchingItem = sourceData.find((item) => String(item[dsConfig.xAxis]) === xValue); if (matchingItem && dsConfig.yAxis.length > 0) { const yField = dsConfig.yAxis[0]; dataPoint[`${sourceName}_${yField}`] = matchingItem[yField]; } }); chartData.push(dataPoint); }); console.log("๐Ÿ“Š ์ผ๋ฐ˜ ๋ชจ๋“œ ์ฐจํŠธ ๋ฐ์ดํ„ฐ:", chartData); console.log("๐Ÿ“Š ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ •:", dataSourceConfigs); // ๐Ÿ†• ํ˜ผํ•ฉ ์ฐจํŠธ ํƒ€์ž… ๊ฐ์ง€ (๊ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋งˆ๋‹ค ๋‹ค๋ฅธ ์ฐจํŠธ ํƒ€์ž…์ด ์„ค์ •๋œ ๊ฒฝ์šฐ) const isMixedChart = dataSourceConfigs.some((dsConfig) => dsConfig.chartType); const effectiveChartType = isMixedChart ? "mixed" : chartType; // ์ฐจํŠธ ํƒ€์ž…๋ณ„ ๋ Œ๋”๋ง switch (effectiveChartType) { case "mixed": case "line": case "bar": case "area": // ๐Ÿ†• ComposedChart ์‚ฌ์šฉ (๋ฐ”/๋ผ์ธ/์˜์—ญ ํ˜ผํ•ฉ ๊ฐ€๋Šฅ) return ( {dataSourceConfigs.map((dsConfig, index) => { const sourceName = dataSources.find((ds) => ds.id === dsConfig.dataSourceId)?.name || `์†Œ์Šค ${index + 1}`; const yField = dsConfig.yAxis[0]; const dataKey = `${sourceName}_${yField}`; const label = dsConfig.label || sourceName; const color = COLORS[index % COLORS.length]; // ๊ฐœ๋ณ„ ์ฐจํŠธ ํƒ€์ž… ๋˜๋Š” ์ „์—ญ ์ฐจํŠธ ํƒ€์ž… ์‚ฌ์šฉ const individualChartType = dsConfig.chartType || chartType; // ์ฐจํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง switch (individualChartType) { case "bar": return ; case "area": return ( ); case "line": default: return ( ); } })} ); case "pie": case "donut": // ํŒŒ์ด ์ฐจํŠธ๋Š” ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋งŒ ์‚ฌ์šฉ if (dataSourceConfigs.length > 0) { const firstConfig = dataSourceConfigs[0]; const sourceName = dataSources.find((ds) => ds.id === firstConfig.dataSourceId)?.name; // ํ•ด๋‹น ๋ฐ์ดํ„ฐ ์†Œ์Šค์˜ ๋ฐ์ดํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง const sourceData = data.filter((item) => item._source === sourceName); console.log("๐Ÿฉ ๋„๋„›/ํŒŒ์ด ์ฐจํŠธ ๋ฐ์ดํ„ฐ:", { sourceName, totalData: data.length, filteredData: sourceData.length, firstConfig, sampleItem: sourceData[0], }); // ํŒŒ์ด ์ฐจํŠธ์šฉ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ const pieData = sourceData.map((item) => ({ name: String(item[firstConfig.xAxis] || "Unknown"), value: Number(item[firstConfig.yAxis[0]]) || 0, })); console.log("๐Ÿฉ ๋ณ€ํ™˜๋œ ํŒŒ์ด ๋ฐ์ดํ„ฐ:", pieData); console.log("๐Ÿฉ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ:", pieData[0]); console.log("๐Ÿฉ ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ฒดํฌ:", { firstValue: pieData[0]?.value, valueType: typeof pieData[0]?.value, isNumber: typeof pieData[0]?.value === "number", }); if (pieData.length === 0) { return (

ํŒŒ์ด ์ฐจํŠธ์— ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

); } // value๊ฐ€ ๋ชจ๋‘ 0์ธ์ง€ ์ฒดํฌ const totalValue = pieData.reduce((sum, item) => sum + (item.value || 0), 0); if (totalValue === 0) { return (

๋ชจ๋“  ๊ฐ’์ด 0์ž…๋‹ˆ๋‹ค. Y์ถ• ํ•„๋“œ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

); } return ( `${entry.name}: ${entry.value}`} labelLine={true} fill="#8884d8" > {pieData.map((entry, index) => ( ))} ); } return (

ํŒŒ์ด ์ฐจํŠธ๋ฅผ ํ‘œ์‹œํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.

); default: return (

์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ฐจํŠธ ํƒ€์ž…: {chartType}

); } }; return (

{element?.customTitle || "์ฐจํŠธ"}

{dataSources?.length || 0}๊ฐœ ๋ฐ์ดํ„ฐ ์†Œ์Šค โ€ข {data.length}๊ฐœ ๋ฐ์ดํ„ฐ {lastRefreshTime && โ€ข {lastRefreshTime.toLocaleTimeString("ko-KR")}}

{loading && }
{error ? (

{error}

) : !(element?.dataSources || element?.chartConfig?.dataSources) || (element?.dataSources || element?.chartConfig?.dataSources)?.length === 0 ? (

๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ์„ธ์š”

) : ( renderChart() )}
{data.length > 0 && (
์ด {data.length}๊ฐœ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ ์ค‘
)}
); }