"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 }); const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false }); const StatusSummaryWidget = dynamic(() => import("./widgets/StatusSummaryWidget"), { ssr: false }); const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false }); const WeatherWidget = dynamic(() => import("./widgets/WeatherWidget"), { ssr: false }); const ExchangeWidget = dynamic(() => import("./widgets/ExchangeWidget"), { ssr: false }); const VehicleStatusWidget = dynamic(() => import("./widgets/VehicleStatusWidget"), { ssr: false }); const VehicleListWidget = dynamic(() => import("./widgets/VehicleListWidget"), { ssr: false }); const VehicleMapOnlyWidget = dynamic(() => import("./widgets/VehicleMapOnlyWidget"), { ssr: false }); const CargoListWidget = dynamic(() => import("./widgets/CargoListWidget"), { ssr: false }); const CustomerIssuesWidget = dynamic(() => import("./widgets/CustomerIssuesWidget"), { ssr: false }); const DeliveryStatusWidget = dynamic(() => import("./widgets/DeliveryStatusWidget"), { ssr: false }); const DeliveryStatusSummaryWidget = dynamic(() => import("./widgets/DeliveryStatusSummaryWidget"), { ssr: false }); const DeliveryTodayStatsWidget = dynamic(() => import("./widgets/DeliveryTodayStatsWidget"), { ssr: false }); const TodoWidget = dynamic(() => import("./widgets/TodoWidget"), { ssr: false }); const DocumentWidget = dynamic(() => import("./widgets/DocumentWidget"), { ssr: false }); const BookingAlertWidget = dynamic(() => import("./widgets/BookingAlertWidget"), { ssr: false }); const MaintenanceWidget = dynamic(() => import("./widgets/MaintenanceWidget"), { ssr: false }); const CalculatorWidget = dynamic(() => import("./widgets/CalculatorWidget"), { ssr: false }); const CalendarWidget = dynamic( () => import("@/components/admin/dashboard/widgets/CalendarWidget").then((mod) => ({ default: mod.CalendarWidget })), { ssr: false }, ); const ClockWidget = dynamic( () => import("@/components/admin/dashboard/widgets/ClockWidget").then((mod) => ({ default: mod.ClockWidget })), { ssr: false }, ); const ListWidget = dynamic( () => import("@/components/admin/dashboard/widgets/ListWidget").then((mod) => ({ default: mod.ListWidget })), { ssr: false }, ); const Warehouse3DWidget = dynamic( () => import("@/components/admin/dashboard/widgets/Warehouse3DWidget").then((mod) => ({ default: mod.Warehouse3DWidget, })), { ssr: false }, ); /** * 위젯 렌더링 함수 - DashboardSidebar의 모든 subtype 처리 * ViewerElement에서 사용하기 위해 컴포넌트 외부에 정의 */ function renderWidget(element: DashboardElement) { switch (element.subtype) { // 차트는 ChartRenderer에서 처리됨 (이 함수 호출 안됨) // === 위젯 종류 === case "exchange": return ; case "weather": return ; case "calculator": return ; case "clock": return ; case "map-summary": return ; case "list-summary": return ; case "risk-alert": return ; case "calendar": return ; case "status-summary": return ; // === 운영/작업 지원 === case "todo": return ; case "booking-alert": return ; case "maintenance": return ; case "document": return ; case "list": return ; case "warehouse-3d": return ; // === 차량 관련 (추가 위젯) === case "vehicle-status": return ; case "vehicle-list": return ; case "vehicle-map": return ; // === 배송 관련 (추가 위젯) === case "delivery-status": return ; case "delivery-status-summary": return ; case "delivery-today-stats": return ; case "cargo-list": return ; case "customer-issues": return ; // === 기본 fallback === default: return (
알 수 없는 위젯 타입: {element.subtype}
); } } 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" ? ( ) : ( renderWidget(element) )}
{/* 로딩 오버레이 */} {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, }; }