오류 해결 및 마지막 업데이트 삭제
This commit is contained in:
parent
cb224be93e
commit
2ff0d77e62
|
|
@ -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<string | null>(null);
|
||||
|
|
@ -102,7 +106,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
|||
return (
|
||||
<div className="h-screen bg-gray-50">
|
||||
{/* 대시보드 뷰어 - 전체 화면 */}
|
||||
<DashboardViewer elements={dashboard.elements} dashboardId={dashboard.id} />
|
||||
<DashboardViewer elements={dashboard.elements} resolution={dashboard.settings?.resolution || "fhd"} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <ClockWidget element={element} />;
|
||||
case "map-summary":
|
||||
return <MapSummaryWidget element={element} />;
|
||||
case "list-summary":
|
||||
return <ListSummaryWidget element={element} />;
|
||||
case "risk-alert":
|
||||
return <RiskAlertWidget element={element} />;
|
||||
case "calendar":
|
||||
|
|
@ -80,7 +78,7 @@ function renderWidget(element: DashboardElement) {
|
|||
case "booking-alert":
|
||||
return <BookingAlertWidget element={element} />;
|
||||
case "maintenance":
|
||||
return <MaintenanceWidget element={element} />;
|
||||
return <MaintenanceWidget />;
|
||||
case "document":
|
||||
return <DocumentWidget element={element} />;
|
||||
case "list":
|
||||
|
|
@ -91,23 +89,23 @@ function renderWidget(element: DashboardElement) {
|
|||
|
||||
// === 차량 관련 (추가 위젯) ===
|
||||
case "vehicle-status":
|
||||
return <VehicleStatusWidget />;
|
||||
return <VehicleStatusWidget element={element} />;
|
||||
case "vehicle-list":
|
||||
return <VehicleListWidget />;
|
||||
return <VehicleListWidget element={element} />;
|
||||
case "vehicle-map":
|
||||
return <VehicleMapOnlyWidget element={element} />;
|
||||
|
||||
// === 배송 관련 (추가 위젯) ===
|
||||
case "delivery-status":
|
||||
return <DeliveryStatusWidget />;
|
||||
return <DeliveryStatusWidget element={element} />;
|
||||
case "delivery-status-summary":
|
||||
return <DeliveryStatusSummaryWidget />;
|
||||
return <DeliveryStatusSummaryWidget element={element} />;
|
||||
case "delivery-today-stats":
|
||||
return <DeliveryTodayStatsWidget />;
|
||||
return <DeliveryTodayStatsWidget element={element} />;
|
||||
case "cargo-list":
|
||||
return <CargoListWidget />;
|
||||
return <CargoListWidget element={element} />;
|
||||
case "customer-issues":
|
||||
return <CustomerIssuesWidget />;
|
||||
return <CustomerIssuesWidget element={element} />;
|
||||
|
||||
// === 기본 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<Record<string, QueryResult>>({});
|
||||
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) => {
|
||||
|
|
@ -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 (
|
||||
<div className="relative h-full w-full overflow-auto bg-gray-100">
|
||||
{/* 새로고침 상태 표시 */}
|
||||
<div className="text-muted-foreground absolute top-4 right-4 z-10 rounded-lg bg-white px-3 py-2 text-xs shadow-sm">
|
||||
마지막 업데이트: {lastRefresh.toLocaleTimeString()}
|
||||
{Array.from(loadingElements).length > 0 && (
|
||||
<span className="text-primary ml-2">({Array.from(loadingElements).length}개 로딩 중...)</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 대시보드 요소들 */}
|
||||
<div className="relative" style={{ minHeight: "100%" }}>
|
||||
<div className="flex h-full items-start justify-center overflow-auto bg-gray-100 p-8">
|
||||
{/* 고정 크기 캔버스 (편집 화면과 동일한 레이아웃) */}
|
||||
<div
|
||||
className="relative overflow-hidden rounded-lg"
|
||||
style={{
|
||||
width: `${canvasConfig.width}px`,
|
||||
minHeight: `${canvasConfig.height}px`,
|
||||
height: `${canvasHeight}px`,
|
||||
}}
|
||||
>
|
||||
{/* 대시보드 요소들 */}
|
||||
{elements.map((element) => (
|
||||
<ViewerElement
|
||||
key={element.id}
|
||||
|
|
@ -333,87 +342,3 @@ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementPro
|
|||
</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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue