"use client"; import React, { useEffect, useState, useCallback, useMemo, useRef } from "react"; import { DashboardElement, ChartDataSource, ChartData } from "@/components/admin/dashboard/types"; import { Button } from "@/components/ui/button"; import { Loader2, RefreshCw } from "lucide-react"; import { applyColumnMapping } from "@/lib/utils/columnMapping"; import { getApiUrl } from "@/lib/utils/apiUrl"; import { Chart } from "@/components/admin/dashboard/charts/Chart"; 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); const containerRef = useRef(null); const [containerSize, setContainerSize] = useState({ width: 600, height: 400 }); // console.log("๐Ÿงช ChartTestWidget ๋ Œ๋”๋ง (D3 ๊ธฐ๋ฐ˜)!", element); const dataSources = useMemo(() => { return element?.dataSources || element?.chartConfig?.dataSources; }, [element?.dataSources, element?.chartConfig?.dataSources]); // ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ ์ธก์ • useEffect(() => { const updateSize = () => { if (containerRef.current) { const width = containerRef.current.offsetWidth || 600; const height = containerRef.current.offsetHeight || 400; setContainerSize({ width, height }); } }; updateSize(); window.addEventListener("resize", updateSize); return () => window.removeEventListener("resize", updateSize); }, []); // ๋‹ค์ค‘ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋กœ๋”ฉ const loadMultipleDataSources = useCallback(async () => { 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, element?.chartConfig?.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(getApiUrl("/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) { const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection"); result = await ExternalDbConnectionAPI.executeQuery(parseInt(source.externalConnectionId), source.query); } else { 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 || []; return applyColumnMapping(rows, source.columnMapping); }; // ์ดˆ๊ธฐ ๋กœ๋“œ 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 || []; // ๋ฐ์ดํ„ฐ๋ฅผ D3 Chart ์ปดํฌ๋„ŒํŠธ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ const chartData = useMemo((): ChartData | null => { if (data.length === 0 || dataSourceConfigs.length === 0) { return null; } const labels = new Set(); const datasets: any[] = []; // ๋ณ‘ํ•ฉ ๋ชจ๋“œ: ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์นจ if (mergeMode && dataSourceConfigs.length > 1) { // console.log("๐Ÿ”€ ๋ณ‘ํ•ฉ ๋ชจ๋“œ ํ™œ์„ฑํ™”"); // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์†Œ์Šค์˜ X์ถ• ํ•„๋“œ ์ˆ˜์ง‘ (์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ์†Œ์Šค์˜ X์ถ• ์‚ฌ์šฉ) const baseConfig = dataSourceConfigs[0]; const xAxisField = baseConfig.xAxis; // console.log("๐Ÿ“Š X์ถ• ํ•„๋“œ:", xAxisField); // X์ถ• ๊ฐ’ ์ˆ˜์ง‘ (๋ชจ๋“  ๋ฐ์ดํ„ฐ ์†Œ์Šค์—์„œ) dataSourceConfigs.forEach((dsConfig, idx) => { const sourceName = dataSources?.find((ds) => ds.id === dsConfig.dataSourceId)?.name; const sourceData = data.filter((item) => item._source === sourceName); // console.log(` ์†Œ์Šค ${idx + 1} (${sourceName}): ${sourceData.length}๊ฐœ ํ–‰`); sourceData.forEach((item) => { if (item[xAxisField] !== undefined) { labels.add(String(item[xAxisField])); } }); }); // console.log("๐Ÿ“ ์ˆ˜์ง‘๋œ X์ถ• ๋ผ๋ฒจ:", Array.from(labels)); // ๊ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ณ„๋กœ ๋ฐ์ดํ„ฐ์…‹ ์ƒ์„ฑ (๋ณ‘ํ•ฉํ•˜์ง€ ์•Š๊ณ  ๊ฐ๊ฐ ํ‘œ์‹œ) dataSourceConfigs.forEach((dsConfig, idx) => { const sourceName = dataSources?.find((ds) => ds.id === dsConfig.dataSourceId)?.name || `์†Œ์Šค ${idx + 1}`; const sourceData = data.filter((item) => item._source === sourceName); // ๊ฐ Y์ถ• ํ•„๋“œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ์…‹ ์ƒ์„ฑ (dsConfig.yAxis || []).forEach((yAxisField, yIdx) => { const datasetData: number[] = []; labels.forEach((label) => { const matchingItem = sourceData.find((item) => String(item[xAxisField]) === label); const value = matchingItem && yAxisField ? parseFloat(matchingItem[yAxisField]) || 0 : 0; datasetData.push(value); }); // console.log(` ๐Ÿ“ˆ ${sourceName} - ${yAxisField}: [${datasetData.join(", ")}]`); datasets.push({ label: `${sourceName} - ${yAxisField}`, data: datasetData, backgroundColor: COLORS[(idx * 2 + yIdx) % COLORS.length], borderColor: COLORS[(idx * 2 + yIdx) % COLORS.length], type: dsConfig.chartType || chartType, // ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ณ„ ์ฐจํŠธ ํƒ€์ž… }); }); }); // console.log("โœ… ๋ณ‘ํ•ฉ ๋ชจ๋“œ ๋ฐ์ดํ„ฐ์…‹ ์ƒ์„ฑ ์™„๋ฃŒ:", datasets.length, "๊ฐœ"); } else { // ์ผ๋ฐ˜ ๋ชจ๋“œ: ๊ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ณ„๋„๋กœ ํ‘œ์‹œ dataSourceConfigs.forEach((dsConfig, index) => { const sourceName = dataSources?.find((ds) => ds.id === dsConfig.dataSourceId)?.name || `์†Œ์Šค ${index + 1}`; const sourceData = data.filter((item) => item._source === sourceName); // X์ถ• ๊ฐ’ ์ˆ˜์ง‘ sourceData.forEach((item) => { const xValue = item[dsConfig.xAxis]; if (xValue !== undefined) { labels.add(String(xValue)); } }); // Y์ถ• ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ const yField = dsConfig.yAxis[0]; const dataValues: number[] = []; labels.forEach((label) => { const matchingItem = sourceData.find((item) => String(item[dsConfig.xAxis]) === label); dataValues.push(matchingItem && yField ? parseFloat(matchingItem[yField]) || 0 : 0); }); datasets.push({ label: dsConfig.label || sourceName, data: dataValues, color: COLORS[index % COLORS.length], }); }); } return { labels: Array.from(labels), datasets, }; }, [data, dataSourceConfigs, mergeMode, dataSources]); return (
{/* ์ฐจํŠธ ์˜์—ญ - ์ „์ฒด ๊ณต๊ฐ„ ์‚ฌ์šฉ */}
{error ? (

{error}

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

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

) : loading && data.length === 0 ? (

๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘...

) : !chartData ? (

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

) : ( )}
{/* ํ‘ธํ„ฐ - ์ฃผ์„ ์ฒ˜๋ฆฌ (๊ณต๊ฐ„ ํ™•๋ณด) */} {/* {data.length > 0 && (
์ด {data.length.toLocaleString()}๊ฐœ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ ์ค‘
)} */}
); }