"use client"; import React, { useState, useEffect, useCallback, useMemo } from "react"; import { DashboardElement, QueryResult } from "@/components/admin/dashboard/types"; import { ChartRenderer } from "@/components/admin/dashboard/charts/ChartRenderer"; import { DashboardProvider } from "@/contexts/DashboardContext"; import { RESOLUTIONS, Resolution } from "@/components/admin/dashboard/ResolutionSelector"; import dynamic from "next/dynamic"; // 위젯 동적 import - 모든 위젯 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 YardManagement3DWidget = dynamic(() => import("@/components/admin/dashboard/widgets/YardManagement3DWidget"), { 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 "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 "yard-management-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) backgroundColor?: string; // 배경색 resolution?: string; // 대시보드 해상도 } /** * 대시보드 뷰어 컴포넌트 * - 저장된 대시보드를 읽기 전용으로 표시 * - 실시간 데이터 업데이트 * - 편집 화면과 동일한 레이아웃 (중앙 정렬, 고정 크기) */ export function DashboardViewer({ elements, dashboardId, refreshInterval, backgroundColor = "#f9fafb", resolution = "fhd", }: DashboardViewerProps) { const [elementData, setElementData] = useState>({}); const [loadingElements, setLoadingElements] = useState>(new Set()); // 캔버스 설정 계산 const canvasConfig = useMemo(() => { return RESOLUTIONS[resolution as Resolution] || RESOLUTIONS.fhd; }, [resolution]); // 캔버스 높이 동적 계산 const canvasHeight = useMemo(() => { if (elements.length === 0) { return canvasConfig.height; } const maxBottomY = Math.max(...elements.map((el) => el.position.y + el.size.height)); return Math.max(canvasConfig.height, maxBottomY + 100); }, [elements, canvasConfig.height]); // 개별 요소 데이터 로딩 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 { // 에러 발생 시 무시 (차트는 빈 상태로 표시됨) } finally { setLoadingElements((prev) => { const newSet = new Set(prev); newSet.delete(element.id); return newSet; }); } }, []); // 모든 요소 데이터 로딩 const loadAllData = useCallback(async () => { 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 (
{/* 고정 크기 캔버스 (편집 화면과 동일한 레이아웃) */}
{/* 대시보드 요소들 */} {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)} > {/* 헤더 (showHeader가 false가 아닐 때만 표시) */} {element.showHeader !== false && (

{element.customTitle || element.title}

{/* 새로고침 버튼 (항상 렌더링하되 opacity로 제어) */}
)} {/* 내용 */}
{element.type === "chart" ? ( ) : ( renderWidget(element) )}
{/* 로딩 오버레이 */} {isLoading && (
업데이트 중...
)}
); }