'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'; 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 { // console.log(`🔄 요소 ${element.id} 데이터 로딩 시작:`, element.dataSource.query); // 실제 API 호출 const { dashboardApi } = await import('@/lib/api/dashboard'); const result = await dashboardApi.executeQuery(element.dataSource.query); // console.log(`✅ 요소 ${element.id} 데이터 로딩 완료:`, result); const data: QueryResult = { columns: result.columns || [], rows: result.rows || [], totalRows: result.rowCount || 0, executionTime: 0 }; setElementData(prev => ({ ...prev, [element.id]: data })); } catch (error) { // console.error(`❌ Element ${element.id} data loading error:`, 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 === '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, }; }