"use client"; import React, { useState, useEffect, useCallback } from "react"; import { DashboardElement, QueryResult } from "@/components/admin/dashboard/types"; import { ChartRenderer } from "@/components/admin/dashboard/charts/ChartRenderer"; import dynamic from "next/dynamic"; // 위젯 동적 import const ListSummaryWidget = dynamic(() => import("./widgets/ListSummaryWidget"), { ssr: false }); interface DashboardViewerProps { elements: DashboardElement[]; dashboardId: string; refreshInterval?: number; // 전체 대시보드 새로고침 간격 (ms) } /** * 대시보드 뷰어 컴포넌트 * - 저장된 대시보드를 읽기 전용으로 표시 * - 실시간 데이터 업데이트 * - 반응형 레이아웃 */ export function DashboardViewer({ elements, dashboardId, refreshInterval }: DashboardViewerProps) { const [elementData, setElementData] = useState>({}); const [loadingElements, setLoadingElements] = useState>(new Set()); const [lastRefresh, setLastRefresh] = useState(new Date()); // 개별 요소 데이터 로딩 const loadElementData = useCallback(async (element: DashboardElement) => { if (!element.dataSource?.query || element.type !== "chart") { return; } setLoadingElements((prev) => new Set([...prev, element.id])); try { let result; // 외부 DB vs 현재 DB 분기 if (element.dataSource.connectionType === "external" && element.dataSource.externalConnectionId) { // 외부 DB const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection"); const externalResult = await ExternalDbConnectionAPI.executeQuery( parseInt(element.dataSource.externalConnectionId), element.dataSource.query, ); if (!externalResult.success) { throw new Error(externalResult.message || "외부 DB 쿼리 실행 실패"); } const data: QueryResult = { columns: externalResult.data?.[0] ? Object.keys(externalResult.data[0]) : [], rows: externalResult.data || [], totalRows: externalResult.data?.length || 0, executionTime: 0, }; setElementData((prev) => ({ ...prev, [element.id]: data, })); } else { // 현재 DB const { dashboardApi } = await import("@/lib/api/dashboard"); result = await dashboardApi.executeQuery(element.dataSource.query); const data: QueryResult = { columns: result.columns || [], rows: result.rows || [], totalRows: result.rowCount || 0, executionTime: 0, }; setElementData((prev) => ({ ...prev, [element.id]: data, })); } } catch (error) { // 에러 발생 시 무시 (차트는 빈 상태로 표시됨) } finally { setLoadingElements((prev) => { const newSet = new Set(prev); newSet.delete(element.id); return newSet; }); } }, []); // 모든 요소 데이터 로딩 const loadAllData = useCallback(async () => { setLastRefresh(new Date()); const chartElements = elements.filter((el) => el.type === "chart" && el.dataSource?.query); // 병렬로 모든 차트 데이터 로딩 await Promise.all(chartElements.map((element) => loadElementData(element))); }, [elements, loadElementData]); // 초기 데이터 로딩 useEffect(() => { loadAllData(); }, [loadAllData]); // 전체 새로고침 간격 설정 useEffect(() => { if (!refreshInterval || refreshInterval === 0) { return; } const interval = setInterval(loadAllData, refreshInterval); return () => clearInterval(interval); }, [refreshInterval, loadAllData]); // 요소가 없는 경우 if (elements.length === 0) { return (
📊
표시할 요소가 없습니다
대시보드 편집기에서 차트나 위젯을 추가해보세요
); } return (
{/* 새로고침 상태 표시 */}
마지막 업데이트: {lastRefresh.toLocaleTimeString()} {Array.from(loadingElements).length > 0 && ( ({Array.from(loadingElements).length}개 로딩 중...) )}
{/* 대시보드 요소들 */}
{elements.map((element) => ( loadElementData(element)} /> ))}
); } interface ViewerElementProps { element: DashboardElement; data?: QueryResult; isLoading: boolean; onRefresh: () => void; } /** * 개별 뷰어 요소 컴포넌트 */ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementProps) { const [isHovered, setIsHovered] = useState(false); return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* 헤더 */}

{element.title}

{/* 새로고침 버튼 (호버 시에만 표시) */} {isHovered && ( )}
{/* 내용 */}
{element.type === "chart" ? ( ) : element.subtype === "list" ? ( // 리스트 위젯 ) : ( // 기타 위젯 렌더링
{element.subtype === "exchange" && "💱"} {element.subtype === "weather" && "☁️"}
{element.content}
)}
{/* 로딩 오버레이 */} {isLoading && (
업데이트 중...
)}
); } /** * 샘플 쿼리 결과 생성 함수 (뷰어용) */ function generateSampleQueryResult(query: string, chartType: string): QueryResult { // 시간에 따라 약간씩 다른 데이터 생성 (실시간 업데이트 시뮬레이션) const timeVariation = Math.sin(Date.now() / 10000) * 0.1 + 1; const isMonthly = query.toLowerCase().includes("month"); const isSales = query.toLowerCase().includes("sales") || query.toLowerCase().includes("매출"); const isUsers = query.toLowerCase().includes("users") || query.toLowerCase().includes("사용자"); const isProducts = query.toLowerCase().includes("product") || query.toLowerCase().includes("상품"); const isWeekly = query.toLowerCase().includes("week"); let columns: string[]; let rows: Record[]; if (isMonthly && isSales) { columns = ["month", "sales", "order_count"]; rows = [ { month: "2024-01", sales: Math.round(1200000 * timeVariation), order_count: Math.round(45 * timeVariation) }, { month: "2024-02", sales: Math.round(1350000 * timeVariation), order_count: Math.round(52 * timeVariation) }, { month: "2024-03", sales: Math.round(1180000 * timeVariation), order_count: Math.round(41 * timeVariation) }, { month: "2024-04", sales: Math.round(1420000 * timeVariation), order_count: Math.round(58 * timeVariation) }, { month: "2024-05", sales: Math.round(1680000 * timeVariation), order_count: Math.round(67 * timeVariation) }, { month: "2024-06", sales: Math.round(1540000 * timeVariation), order_count: Math.round(61 * timeVariation) }, ]; } else if (isWeekly && isUsers) { columns = ["week", "new_users"]; rows = [ { week: "2024-W10", new_users: Math.round(23 * timeVariation) }, { week: "2024-W11", new_users: Math.round(31 * timeVariation) }, { week: "2024-W12", new_users: Math.round(28 * timeVariation) }, { week: "2024-W13", new_users: Math.round(35 * timeVariation) }, { week: "2024-W14", new_users: Math.round(42 * timeVariation) }, { week: "2024-W15", new_users: Math.round(38 * timeVariation) }, ]; } else if (isProducts) { columns = ["product_name", "total_sold", "revenue"]; rows = [ { product_name: "스마트폰", total_sold: Math.round(156 * timeVariation), revenue: Math.round(234000000 * timeVariation), }, { product_name: "노트북", total_sold: Math.round(89 * timeVariation), revenue: Math.round(178000000 * timeVariation), }, { product_name: "태블릿", total_sold: Math.round(134 * timeVariation), revenue: Math.round(67000000 * timeVariation), }, { product_name: "이어폰", total_sold: Math.round(267 * timeVariation), revenue: Math.round(26700000 * timeVariation), }, { product_name: "스마트워치", total_sold: Math.round(98 * timeVariation), revenue: Math.round(49000000 * timeVariation), }, ]; } else { columns = ["category", "value", "count"]; rows = [ { category: "A", value: Math.round(100 * timeVariation), count: Math.round(10 * timeVariation) }, { category: "B", value: Math.round(150 * timeVariation), count: Math.round(15 * timeVariation) }, { category: "C", value: Math.round(120 * timeVariation), count: Math.round(12 * timeVariation) }, { category: "D", value: Math.round(180 * timeVariation), count: Math.round(18 * timeVariation) }, { category: "E", value: Math.round(90 * timeVariation), count: Math.round(9 * timeVariation) }, ]; } return { columns, rows, totalRows: rows.length, executionTime: Math.floor(Math.random() * 100) + 50, }; }