287 lines
9.1 KiB
TypeScript
287 lines
9.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { DashboardViewer } from '@/components/dashboard/DashboardViewer';
|
|
import { DashboardElement } from '@/components/admin/dashboard/types';
|
|
|
|
interface DashboardViewPageProps {
|
|
params: {
|
|
dashboardId: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 대시보드 뷰어 페이지
|
|
* - 저장된 대시보드를 읽기 전용으로 표시
|
|
* - 실시간 데이터 업데이트
|
|
* - 전체화면 모드 지원
|
|
*/
|
|
export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
|
const [dashboard, setDashboard] = useState<{
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
elements: DashboardElement[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
} | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// 대시보드 데이터 로딩
|
|
useEffect(() => {
|
|
loadDashboard();
|
|
}, [params.dashboardId]);
|
|
|
|
const loadDashboard = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// 실제 API 호출 시도
|
|
const { dashboardApi } = await import('@/lib/api/dashboard');
|
|
|
|
try {
|
|
const dashboardData = await dashboardApi.getDashboard(params.dashboardId);
|
|
setDashboard(dashboardData);
|
|
} catch (apiError) {
|
|
console.warn('API 호출 실패, 로컬 스토리지 확인:', apiError);
|
|
|
|
// API 실패 시 로컬 스토리지에서 찾기
|
|
const savedDashboards = JSON.parse(localStorage.getItem('savedDashboards') || '[]');
|
|
const savedDashboard = savedDashboards.find((d: any) => d.id === params.dashboardId);
|
|
|
|
if (savedDashboard) {
|
|
setDashboard(savedDashboard);
|
|
} else {
|
|
// 로컬에도 없으면 샘플 데이터 사용
|
|
const sampleDashboard = generateSampleDashboard(params.dashboardId);
|
|
setDashboard(sampleDashboard);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
setError('대시보드를 불러오는 중 오류가 발생했습니다.');
|
|
console.error('Dashboard loading error:', err);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// 로딩 상태
|
|
if (isLoading) {
|
|
return (
|
|
<div className="h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
|
<div className="text-lg font-medium text-gray-700">대시보드 로딩 중...</div>
|
|
<div className="text-sm text-gray-500 mt-1">잠시만 기다려주세요</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 에러 상태
|
|
if (error || !dashboard) {
|
|
return (
|
|
<div className="h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<div className="text-6xl mb-4">😞</div>
|
|
<div className="text-xl font-medium text-gray-700 mb-2">
|
|
{error || '대시보드를 찾을 수 없습니다'}
|
|
</div>
|
|
<div className="text-sm text-gray-500 mb-4">
|
|
대시보드 ID: {params.dashboardId}
|
|
</div>
|
|
<button
|
|
onClick={loadDashboard}
|
|
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
|
>
|
|
다시 시도
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="h-screen bg-gray-50">
|
|
{/* 대시보드 헤더 */}
|
|
<div className="bg-white border-b border-gray-200 px-6 py-4">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-800">{dashboard.title}</h1>
|
|
{dashboard.description && (
|
|
<p className="text-sm text-gray-600 mt-1">{dashboard.description}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
{/* 새로고침 버튼 */}
|
|
<button
|
|
onClick={loadDashboard}
|
|
className="px-3 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded-lg hover:bg-gray-50"
|
|
title="새로고침"
|
|
>
|
|
🔄
|
|
</button>
|
|
|
|
{/* 전체화면 버튼 */}
|
|
<button
|
|
onClick={() => {
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen();
|
|
} else {
|
|
document.documentElement.requestFullscreen();
|
|
}
|
|
}}
|
|
className="px-3 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded-lg hover:bg-gray-50"
|
|
title="전체화면"
|
|
>
|
|
⛶
|
|
</button>
|
|
|
|
{/* 편집 버튼 */}
|
|
<button
|
|
onClick={() => {
|
|
window.open(`/admin/dashboard?load=${params.dashboardId}`, '_blank');
|
|
}}
|
|
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
|
>
|
|
편집
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 메타 정보 */}
|
|
<div className="flex items-center gap-4 mt-2 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>
|
|
|
|
{/* 대시보드 뷰어 */}
|
|
<div className="h-[calc(100vh-120px)]">
|
|
<DashboardViewer
|
|
elements={dashboard.elements}
|
|
dashboardId={dashboard.id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 샘플 대시보드 생성 함수
|
|
*/
|
|
function generateSampleDashboard(dashboardId: string) {
|
|
const dashboards: Record<string, any> = {
|
|
'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()
|
|
};
|
|
}
|