From 2ff0d77e62339ea37b7135df714aa7fc234a7071 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Thu, 16 Oct 2025 17:17:18 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/dashboard/[dashboardId]/page.tsx | 6 +- .../components/dashboard/DashboardViewer.tsx | 153 +++++------------- 2 files changed, 44 insertions(+), 115 deletions(-) diff --git a/frontend/app/(main)/dashboard/[dashboardId]/page.tsx b/frontend/app/(main)/dashboard/[dashboardId]/page.tsx index a9271967..cb6defd6 100644 --- a/frontend/app/(main)/dashboard/[dashboardId]/page.tsx +++ b/frontend/app/(main)/dashboard/[dashboardId]/page.tsx @@ -27,6 +27,10 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) { elements: DashboardElement[]; createdAt: string; updatedAt: string; + settings?: { + resolution?: string; + backgroundColor?: string; + }; } | null>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -102,7 +106,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) { return (
{/* 대시보드 뷰어 - 전체 화면 */} - +
); } diff --git a/frontend/components/dashboard/DashboardViewer.tsx b/frontend/components/dashboard/DashboardViewer.tsx index d0c12ccf..d5790779 100644 --- a/frontend/components/dashboard/DashboardViewer.tsx +++ b/frontend/components/dashboard/DashboardViewer.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useState, useEffect, useCallback } from "react"; +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 { RESOLUTIONS, Resolution } from "@/components/admin/dashboard/ResolutionSelector"; 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 }); @@ -65,8 +65,6 @@ function renderWidget(element: DashboardElement) { return ; case "map-summary": return ; - case "list-summary": - return ; case "risk-alert": return ; case "calendar": @@ -80,7 +78,7 @@ function renderWidget(element: DashboardElement) { case "booking-alert": return ; case "maintenance": - return ; + return ; case "document": return ; case "list": @@ -91,23 +89,23 @@ function renderWidget(element: DashboardElement) { // === 차량 관련 (추가 위젯) === case "vehicle-status": - return ; + return ; case "vehicle-list": - return ; + return ; case "vehicle-map": return ; // === 배송 관련 (추가 위젯) === case "delivery-status": - return ; + return ; case "delivery-status-summary": - return ; + return ; case "delivery-today-stats": - return ; + return ; case "cargo-list": - return ; + return ; case "customer-issues": - return ; + return ; // === 기본 fallback === default: @@ -124,20 +122,33 @@ function renderWidget(element: DashboardElement) { interface DashboardViewerProps { elements: DashboardElement[]; - dashboardId: string; refreshInterval?: number; // 전체 대시보드 새로고침 간격 (ms) + resolution?: string; // 대시보드 해상도 } /** * 대시보드 뷰어 컴포넌트 * - 저장된 대시보드를 읽기 전용으로 표시 * - 실시간 데이터 업데이트 - * - 반응형 레이아웃 + * - 편집 화면과 동일한 레이아웃 (중앙 정렬, 고정 크기) */ -export function DashboardViewer({ elements, dashboardId, refreshInterval }: DashboardViewerProps) { +export function DashboardViewer({ elements, refreshInterval, resolution = "fhd" }: DashboardViewerProps) { const [elementData, setElementData] = useState>({}); const [loadingElements, setLoadingElements] = useState>(new Set()); - const [lastRefresh, setLastRefresh] = useState(new Date()); + + // 캔버스 설정 계산 + 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) => { @@ -191,7 +202,7 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash [element.id]: data, })); } - } catch (error) { + } catch { // 에러 발생 시 무시 (차트는 빈 상태로 표시됨) } finally { setLoadingElements((prev) => { @@ -204,8 +215,6 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash // 모든 요소 데이터 로딩 const loadAllData = useCallback(async () => { - setLastRefresh(new Date()); - const chartElements = elements.filter((el) => el.type === "chart" && el.dataSource?.query); // 병렬로 모든 차트 데이터 로딩 @@ -241,17 +250,17 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash } return ( -
- {/* 새로고침 상태 표시 */} -
- 마지막 업데이트: {lastRefresh.toLocaleTimeString()} - {Array.from(loadingElements).length > 0 && ( - ({Array.from(loadingElements).length}개 로딩 중...) - )} -
- - {/* 대시보드 요소들 */} -
+
+ {/* 고정 크기 캔버스 (편집 화면과 동일한 레이아웃) */} +
+ {/* 대시보드 요소들 */} {elements.map((element) => ( ); } - -/** - * 샘플 쿼리 결과 생성 함수 (뷰어용) - */ -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, - }; -}