ERP-node/frontend/app/(main)/dashboard/[dashboardId]/page.tsx

314 lines
9.9 KiB
TypeScript

"use client";
import React, { useState, useEffect, use } from "react";
import { DashboardViewer } from "@/components/dashboard/DashboardViewer";
import { DashboardElement } from "@/components/admin/dashboard/types";
interface DashboardViewPageProps {
params: Promise<{
dashboardId: string;
}>;
}
/**
* 대시보드 뷰어 페이지
* - 저장된 대시보드를 읽기 전용으로 표시
* - 실시간 데이터 업데이트
* - 전체화면 모드 지원
*/
export default function DashboardViewPage({ params }: DashboardViewPageProps) {
const resolvedParams = use(params);
const [dashboard, setDashboard] = useState<{
id: string;
title: string;
description?: string;
elements: DashboardElement[];
settings?: {
backgroundColor?: string;
resolution?: string;
};
createdAt: string;
updatedAt: string;
} | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadDashboard = React.useCallback(async () => {
setIsLoading(true);
setError(null);
try {
// 실제 API 호출 시도
const { dashboardApi } = await import("@/lib/api/dashboard");
try {
const dashboardData = await dashboardApi.getDashboard(resolvedParams.dashboardId);
setDashboard({
...dashboardData,
elements: dashboardData.elements || [],
});
} catch (apiError) {
console.warn("API 호출 실패, 로컬 스토리지 확인:", apiError);
// API 실패 시 로컬 스토리지에서 찾기
const savedDashboards = JSON.parse(localStorage.getItem("savedDashboards") || "[]");
const savedDashboard = savedDashboards.find((d: { id: string }) => d.id === resolvedParams.dashboardId);
if (savedDashboard) {
setDashboard(savedDashboard);
} else {
// 로컬에도 없으면 샘플 데이터 사용
const sampleDashboard = generateSampleDashboard(resolvedParams.dashboardId);
setDashboard(sampleDashboard);
}
}
} catch (err) {
setError("대시보드를 불러오는 중 오류가 발생했습니다.");
console.error("Dashboard loading error:", err);
} finally {
setIsLoading(false);
}
}, [resolvedParams.dashboardId]);
// 대시보드 데이터 로딩
useEffect(() => {
loadDashboard();
}, [loadDashboard]);
// 로딩 상태
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center bg-background">
<div className="text-center">
<div className="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-ring border-t-transparent" />
<div className="text-lg font-medium text-foreground"> ...</div>
<div className="mt-1 text-sm text-muted-foreground"> </div>
</div>
</div>
);
}
// 에러 상태
if (error || !dashboard) {
return (
<div className="flex h-screen items-center justify-center bg-background">
<div className="text-center">
<div className="mb-4 text-6xl">😞</div>
<div className="mb-2 text-xl font-medium text-foreground">{error || "대시보드를 찾을 수 없습니다"}</div>
<div className="mb-4 text-sm text-muted-foreground"> ID: {resolvedParams.dashboardId}</div>
<button onClick={loadDashboard} className="rounded-lg bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90">
</button>
</div>
</div>
);
}
return (
<div className="h-screen">
{/* 대시보드 헤더 - 보기 모드에서는 숨김 */}
{/* <div className="border-b border-gray-200 bg-white px-6 py-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-800">{dashboard.title}</h1>
{dashboard.description && <p className="mt-1 text-sm text-gray-600">{dashboard.description}</p>}
</div>
<div className="flex items-center gap-3">
{/* 새로고침 버튼 *\/}
<button
onClick={loadDashboard}
className="rounded-lg border border-gray-300 px-3 py-2 text-gray-600 hover:bg-gray-50 hover:text-gray-800"
title="새로고침"
>
🔄
</button>
{/* 전체화면 버튼 *\/}
<button
onClick={() => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.documentElement.requestFullscreen();
}
}}
className="rounded-lg border border-gray-300 px-3 py-2 text-gray-600 hover:bg-gray-50 hover:text-gray-800"
title="전체화면"
>
</button>
{/* 편집 버튼 *\/}
<button
onClick={() => {
router.push(`/admin/screenMng/dashboardList?load=${resolvedParams.dashboardId}`);
}}
className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
>
편집
</button>
</div>
</div>
{/* 메타 정보 *\/}
<div className="mt-2 flex items-center gap-4 text-xs text-gray-500">
<span>생성: {new Date(dashboard.createdAt).toLocaleString()}</span>
<span>수정: {new Date(dashboard.updatedAt).toLocaleString()}</span>
<span>요소: {dashboard.elements.length}개</span>
</div>
</div> */}
{/* 대시보드 뷰어 */}
<DashboardViewer
elements={dashboard.elements}
dashboardId={dashboard.id}
dashboardTitle={dashboard.title}
backgroundColor={dashboard.settings?.backgroundColor}
resolution={dashboard.settings?.resolution}
/>
</div>
);
}
/**
* 샘플 대시보드 생성 함수
*/
function generateSampleDashboard(dashboardId: string): {
id: string;
title: string;
description?: string;
elements: DashboardElement[];
settings?: {
backgroundColor?: string;
resolution?: string;
};
createdAt: string;
updatedAt: string;
} {
const dashboards: Record<
string,
{
id: string;
title: string;
description?: string;
elements: DashboardElement[];
settings?: {
backgroundColor?: string;
resolution?: string;
};
createdAt: string;
updatedAt: string;
}
> = {
"sales-overview": {
id: "sales-overview",
title: "📊 매출 현황 대시보드",
description: "월별 매출 추이 및 상품별 판매 현황을 한눈에 확인할 수 있습니다.",
elements: [
{
id: "chart-1",
type: "chart",
subtype: "bar",
position: { x: 20, y: 20 },
size: { width: 400, height: 300 },
title: "📊 월별 매출 추이",
content: "월별 매출 데이터",
dataSource: {
type: "database",
query: "SELECT month, sales FROM monthly_sales",
refreshInterval: 30000,
},
chartConfig: {
xAxis: "month",
yAxis: "sales",
title: "월별 매출 추이",
colors: ["#3B82F6", "#EF4444", "#10B981"],
},
},
{
id: "chart-2",
type: "chart",
subtype: "pie",
position: { x: 450, y: 20 },
size: { width: 350, height: 300 },
title: "🥧 상품별 판매 비율",
content: "상품별 판매 데이터",
dataSource: {
type: "database",
query: "SELECT product_name, total_sold FROM product_sales",
refreshInterval: 60000,
},
chartConfig: {
xAxis: "product_name",
yAxis: "total_sold",
title: "상품별 판매 비율",
colors: ["#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"],
},
},
{
id: "chart-3",
type: "chart",
subtype: "line",
position: { x: 20, y: 350 },
size: { width: 780, height: 250 },
title: "📈 사용자 가입 추이",
content: "사용자 가입 데이터",
dataSource: {
type: "database",
query: "SELECT week, new_users FROM user_growth",
refreshInterval: 300000,
},
chartConfig: {
xAxis: "week",
yAxis: "new_users",
title: "주간 신규 사용자 가입 추이",
colors: ["#10B981"],
},
},
],
createdAt: "2024-09-30T10:00:00Z",
updatedAt: "2024-09-30T14:30:00Z",
},
"user-analytics": {
id: "user-analytics",
title: "👥 사용자 분석 대시보드",
description: "사용자 행동 패턴 및 가입 추이 분석",
elements: [
{
id: "chart-4",
type: "chart",
subtype: "line",
position: { x: 20, y: 20 },
size: { width: 500, height: 300 },
title: "📈 일일 활성 사용자",
content: "사용자 활동 데이터",
dataSource: {
type: "database",
query: "SELECT date, active_users FROM daily_active_users",
refreshInterval: 60000,
},
chartConfig: {
xAxis: "date",
yAxis: "active_users",
title: "일일 활성 사용자 추이",
},
},
],
createdAt: "2024-09-29T15:00:00Z",
updatedAt: "2024-09-30T09:15:00Z",
},
};
return (
dashboards[dashboardId] || {
id: dashboardId,
title: `대시보드 ${dashboardId}`,
description: "샘플 대시보드입니다.",
elements: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
);
}