오류 해결 및 마지막 업데이트 삭제

This commit is contained in:
dohyeons 2025-10-16 17:17:18 +09:00
parent cb224be93e
commit 2ff0d77e62
2 changed files with 44 additions and 115 deletions

View File

@ -27,6 +27,10 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
elements: DashboardElement[]; elements: DashboardElement[];
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
settings?: {
resolution?: string;
backgroundColor?: string;
};
} | null>(null); } | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -102,7 +106,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
return ( return (
<div className="h-screen bg-gray-50"> <div className="h-screen bg-gray-50">
{/* 대시보드 뷰어 - 전체 화면 */} {/* 대시보드 뷰어 - 전체 화면 */}
<DashboardViewer elements={dashboard.elements} dashboardId={dashboard.id} /> <DashboardViewer elements={dashboard.elements} resolution={dashboard.settings?.resolution || "fhd"} />
</div> </div>
); );
} }

View File

@ -1,12 +1,12 @@
"use client"; "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 { DashboardElement, QueryResult } from "@/components/admin/dashboard/types";
import { ChartRenderer } from "@/components/admin/dashboard/charts/ChartRenderer"; import { ChartRenderer } from "@/components/admin/dashboard/charts/ChartRenderer";
import { RESOLUTIONS, Resolution } from "@/components/admin/dashboard/ResolutionSelector";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
// 위젯 동적 import - 모든 위젯 // 위젯 동적 import - 모든 위젯
const ListSummaryWidget = dynamic(() => import("./widgets/ListSummaryWidget"), { ssr: false });
const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false }); const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false });
const StatusSummaryWidget = dynamic(() => import("./widgets/StatusSummaryWidget"), { ssr: false }); const StatusSummaryWidget = dynamic(() => import("./widgets/StatusSummaryWidget"), { ssr: false });
const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false }); const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false });
@ -65,8 +65,6 @@ function renderWidget(element: DashboardElement) {
return <ClockWidget element={element} />; return <ClockWidget element={element} />;
case "map-summary": case "map-summary":
return <MapSummaryWidget element={element} />; return <MapSummaryWidget element={element} />;
case "list-summary":
return <ListSummaryWidget element={element} />;
case "risk-alert": case "risk-alert":
return <RiskAlertWidget element={element} />; return <RiskAlertWidget element={element} />;
case "calendar": case "calendar":
@ -80,7 +78,7 @@ function renderWidget(element: DashboardElement) {
case "booking-alert": case "booking-alert":
return <BookingAlertWidget element={element} />; return <BookingAlertWidget element={element} />;
case "maintenance": case "maintenance":
return <MaintenanceWidget element={element} />; return <MaintenanceWidget />;
case "document": case "document":
return <DocumentWidget element={element} />; return <DocumentWidget element={element} />;
case "list": case "list":
@ -91,23 +89,23 @@ function renderWidget(element: DashboardElement) {
// === 차량 관련 (추가 위젯) === // === 차량 관련 (추가 위젯) ===
case "vehicle-status": case "vehicle-status":
return <VehicleStatusWidget />; return <VehicleStatusWidget element={element} />;
case "vehicle-list": case "vehicle-list":
return <VehicleListWidget />; return <VehicleListWidget element={element} />;
case "vehicle-map": case "vehicle-map":
return <VehicleMapOnlyWidget element={element} />; return <VehicleMapOnlyWidget element={element} />;
// === 배송 관련 (추가 위젯) === // === 배송 관련 (추가 위젯) ===
case "delivery-status": case "delivery-status":
return <DeliveryStatusWidget />; return <DeliveryStatusWidget element={element} />;
case "delivery-status-summary": case "delivery-status-summary":
return <DeliveryStatusSummaryWidget />; return <DeliveryStatusSummaryWidget element={element} />;
case "delivery-today-stats": case "delivery-today-stats":
return <DeliveryTodayStatsWidget />; return <DeliveryTodayStatsWidget element={element} />;
case "cargo-list": case "cargo-list":
return <CargoListWidget />; return <CargoListWidget element={element} />;
case "customer-issues": case "customer-issues":
return <CustomerIssuesWidget />; return <CustomerIssuesWidget element={element} />;
// === 기본 fallback === // === 기본 fallback ===
default: default:
@ -124,20 +122,33 @@ function renderWidget(element: DashboardElement) {
interface DashboardViewerProps { interface DashboardViewerProps {
elements: DashboardElement[]; elements: DashboardElement[];
dashboardId: string;
refreshInterval?: number; // 전체 대시보드 새로고침 간격 (ms) refreshInterval?: number; // 전체 대시보드 새로고침 간격 (ms)
resolution?: string; // 대시보드 해상도
} }
/** /**
* *
* - * -
* - * -
* - * - ( , )
*/ */
export function DashboardViewer({ elements, dashboardId, refreshInterval }: DashboardViewerProps) { export function DashboardViewer({ elements, refreshInterval, resolution = "fhd" }: DashboardViewerProps) {
const [elementData, setElementData] = useState<Record<string, QueryResult>>({}); const [elementData, setElementData] = useState<Record<string, QueryResult>>({});
const [loadingElements, setLoadingElements] = useState<Set<string>>(new Set()); const [loadingElements, setLoadingElements] = useState<Set<string>>(new Set());
const [lastRefresh, setLastRefresh] = useState<Date>(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) => { const loadElementData = useCallback(async (element: DashboardElement) => {
@ -191,7 +202,7 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash
[element.id]: data, [element.id]: data,
})); }));
} }
} catch (error) { } catch {
// 에러 발생 시 무시 (차트는 빈 상태로 표시됨) // 에러 발생 시 무시 (차트는 빈 상태로 표시됨)
} finally { } finally {
setLoadingElements((prev) => { setLoadingElements((prev) => {
@ -204,8 +215,6 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash
// 모든 요소 데이터 로딩 // 모든 요소 데이터 로딩
const loadAllData = useCallback(async () => { const loadAllData = useCallback(async () => {
setLastRefresh(new Date());
const chartElements = elements.filter((el) => el.type === "chart" && el.dataSource?.query); const chartElements = elements.filter((el) => el.type === "chart" && el.dataSource?.query);
// 병렬로 모든 차트 데이터 로딩 // 병렬로 모든 차트 데이터 로딩
@ -241,17 +250,17 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash
} }
return ( return (
<div className="relative h-full w-full overflow-auto bg-gray-100"> <div className="flex h-full items-start justify-center overflow-auto bg-gray-100 p-8">
{/* 새로고침 상태 표시 */} {/* 고정 크기 캔버스 (편집 화면과 동일한 레이아웃) */}
<div className="text-muted-foreground absolute top-4 right-4 z-10 rounded-lg bg-white px-3 py-2 text-xs shadow-sm"> <div
: {lastRefresh.toLocaleTimeString()} className="relative overflow-hidden rounded-lg"
{Array.from(loadingElements).length > 0 && ( style={{
<span className="text-primary ml-2">({Array.from(loadingElements).length} ...)</span> width: `${canvasConfig.width}px`,
)} minHeight: `${canvasConfig.height}px`,
</div> height: `${canvasHeight}px`,
}}
{/* 대시보드 요소들 */} >
<div className="relative" style={{ minHeight: "100%" }}> {/* 대시보드 요소들 */}
{elements.map((element) => ( {elements.map((element) => (
<ViewerElement <ViewerElement
key={element.id} key={element.id}
@ -333,87 +342,3 @@ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementPro
</div> </div>
); );
} }
/**
* ()
*/
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<string, any>[];
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,
};
}