에러 해결
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { DashboardViewer } from '@/components/dashboard/DashboardViewer';
|
||||
import { DashboardElement } from '@/components/admin/dashboard/types';
|
||||
import React, { useState, useEffect, use } from "react";
|
||||
import { DashboardViewer } from "@/components/dashboard/DashboardViewer";
|
||||
import { DashboardElement } from "@/components/admin/dashboard/types";
|
||||
|
||||
interface DashboardViewPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
dashboardId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,6 +17,7 @@ interface DashboardViewPageProps {
|
||||
* - 전체화면 모드 지원
|
||||
*/
|
||||
export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
const resolvedParams = use(params);
|
||||
const [dashboard, setDashboard] = useState<{
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -31,7 +32,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
// 대시보드 데이터 로딩
|
||||
useEffect(() => {
|
||||
loadDashboard();
|
||||
}, [params.dashboardId]);
|
||||
}, [resolvedParams.dashboardId]);
|
||||
|
||||
const loadDashboard = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -39,29 +40,29 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
|
||||
try {
|
||||
// 실제 API 호출 시도
|
||||
const { dashboardApi } = await import('@/lib/api/dashboard');
|
||||
|
||||
const { dashboardApi } = await import("@/lib/api/dashboard");
|
||||
|
||||
try {
|
||||
const dashboardData = await dashboardApi.getDashboard(params.dashboardId);
|
||||
const dashboardData = await dashboardApi.getDashboard(resolvedParams.dashboardId);
|
||||
setDashboard(dashboardData);
|
||||
} catch (apiError) {
|
||||
console.warn('API 호출 실패, 로컬 스토리지 확인:', apiError);
|
||||
|
||||
console.warn("API 호출 실패, 로컬 스토리지 확인:", apiError);
|
||||
|
||||
// API 실패 시 로컬 스토리지에서 찾기
|
||||
const savedDashboards = JSON.parse(localStorage.getItem('savedDashboards') || '[]');
|
||||
const savedDashboard = savedDashboards.find((d: any) => d.id === params.dashboardId);
|
||||
|
||||
const savedDashboards = JSON.parse(localStorage.getItem("savedDashboards") || "[]");
|
||||
const savedDashboard = savedDashboards.find((d: any) => d.id === resolvedParams.dashboardId);
|
||||
|
||||
if (savedDashboard) {
|
||||
setDashboard(savedDashboard);
|
||||
} else {
|
||||
// 로컬에도 없으면 샘플 데이터 사용
|
||||
const sampleDashboard = generateSampleDashboard(params.dashboardId);
|
||||
const sampleDashboard = generateSampleDashboard(resolvedParams.dashboardId);
|
||||
setDashboard(sampleDashboard);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('대시보드를 불러오는 중 오류가 발생했습니다.');
|
||||
console.error('Dashboard loading error:', err);
|
||||
setError("대시보드를 불러오는 중 오류가 발생했습니다.");
|
||||
console.error("Dashboard loading error:", err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -70,11 +71,11 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
// 로딩 상태
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="flex h-screen 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="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
|
||||
<div className="text-lg font-medium text-gray-700">대시보드 로딩 중...</div>
|
||||
<div className="text-sm text-gray-500 mt-1">잠시만 기다려주세요</div>
|
||||
<div className="mt-1 text-sm text-gray-500">잠시만 기다려주세요</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -83,19 +84,12 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
// 에러 상태
|
||||
if (error || !dashboard) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="flex h-screen 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"
|
||||
>
|
||||
<div className="mb-4 text-6xl">😞</div>
|
||||
<div className="mb-2 text-xl font-medium text-gray-700">{error || "대시보드를 찾을 수 없습니다"}</div>
|
||||
<div className="mb-4 text-sm text-gray-500">대시보드 ID: {resolvedParams.dashboardId}</div>
|
||||
<button onClick={loadDashboard} className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
다시 시도
|
||||
</button>
|
||||
</div>
|
||||
@@ -106,25 +100,23 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
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 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="text-sm text-gray-600 mt-1">{dashboard.description}</p>
|
||||
)}
|
||||
{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="px-3 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||
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={() => {
|
||||
@@ -134,26 +126,26 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
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"
|
||||
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={() => {
|
||||
window.open(`/admin/dashboard?load=${params.dashboardId}`, '_blank');
|
||||
window.open(`/admin/dashboard?load=${resolvedParams.dashboardId}`, "_blank");
|
||||
}}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
||||
className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
>
|
||||
편집
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 메타 정보 */}
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
|
||||
<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>
|
||||
@@ -162,10 +154,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
|
||||
{/* 대시보드 뷰어 */}
|
||||
<div className="h-[calc(100vh-120px)]">
|
||||
<DashboardViewer
|
||||
elements={dashboard.elements}
|
||||
dashboardId={dashboard.id}
|
||||
/>
|
||||
<DashboardViewer elements={dashboard.elements} dashboardId={dashboard.id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -176,111 +165,113 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
|
||||
*/
|
||||
function generateSampleDashboard(dashboardId: string) {
|
||||
const dashboards: Record<string, any> = {
|
||||
'sales-overview': {
|
||||
id: 'sales-overview',
|
||||
title: '📊 매출 현황 대시보드',
|
||||
description: '월별 매출 추이 및 상품별 판매 현황을 한눈에 확인할 수 있습니다.',
|
||||
"sales-overview": {
|
||||
id: "sales-overview",
|
||||
title: "📊 매출 현황 대시보드",
|
||||
description: "월별 매출 추이 및 상품별 판매 현황을 한눈에 확인할 수 있습니다.",
|
||||
elements: [
|
||||
{
|
||||
id: 'chart-1',
|
||||
type: 'chart',
|
||||
subtype: 'bar',
|
||||
id: "chart-1",
|
||||
type: "chart",
|
||||
subtype: "bar",
|
||||
position: { x: 20, y: 20 },
|
||||
size: { width: 400, height: 300 },
|
||||
title: '📊 월별 매출 추이',
|
||||
content: '월별 매출 데이터',
|
||||
title: "📊 월별 매출 추이",
|
||||
content: "월별 매출 데이터",
|
||||
dataSource: {
|
||||
type: 'database',
|
||||
query: 'SELECT month, sales FROM monthly_sales',
|
||||
refreshInterval: 30000
|
||||
type: "database",
|
||||
query: "SELECT month, sales FROM monthly_sales",
|
||||
refreshInterval: 30000,
|
||||
},
|
||||
chartConfig: {
|
||||
xAxis: 'month',
|
||||
yAxis: 'sales',
|
||||
title: '월별 매출 추이',
|
||||
colors: ['#3B82F6', '#EF4444', '#10B981']
|
||||
}
|
||||
xAxis: "month",
|
||||
yAxis: "sales",
|
||||
title: "월별 매출 추이",
|
||||
colors: ["#3B82F6", "#EF4444", "#10B981"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'chart-2',
|
||||
type: 'chart',
|
||||
subtype: 'pie',
|
||||
id: "chart-2",
|
||||
type: "chart",
|
||||
subtype: "pie",
|
||||
position: { x: 450, y: 20 },
|
||||
size: { width: 350, height: 300 },
|
||||
title: '🥧 상품별 판매 비율',
|
||||
content: '상품별 판매 데이터',
|
||||
title: "🥧 상품별 판매 비율",
|
||||
content: "상품별 판매 데이터",
|
||||
dataSource: {
|
||||
type: 'database',
|
||||
query: 'SELECT product_name, total_sold FROM product_sales',
|
||||
refreshInterval: 60000
|
||||
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']
|
||||
}
|
||||
xAxis: "product_name",
|
||||
yAxis: "total_sold",
|
||||
title: "상품별 판매 비율",
|
||||
colors: ["#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'chart-3',
|
||||
type: 'chart',
|
||||
subtype: 'line',
|
||||
id: "chart-3",
|
||||
type: "chart",
|
||||
subtype: "line",
|
||||
position: { x: 20, y: 350 },
|
||||
size: { width: 780, height: 250 },
|
||||
title: '📈 사용자 가입 추이',
|
||||
content: '사용자 가입 데이터',
|
||||
title: "📈 사용자 가입 추이",
|
||||
content: "사용자 가입 데이터",
|
||||
dataSource: {
|
||||
type: 'database',
|
||||
query: 'SELECT week, new_users FROM user_growth',
|
||||
refreshInterval: 300000
|
||||
type: "database",
|
||||
query: "SELECT week, new_users FROM user_growth",
|
||||
refreshInterval: 300000,
|
||||
},
|
||||
chartConfig: {
|
||||
xAxis: 'week',
|
||||
yAxis: 'new_users',
|
||||
title: '주간 신규 사용자 가입 추이',
|
||||
colors: ['#10B981']
|
||||
}
|
||||
}
|
||||
xAxis: "week",
|
||||
yAxis: "new_users",
|
||||
title: "주간 신규 사용자 가입 추이",
|
||||
colors: ["#10B981"],
|
||||
},
|
||||
},
|
||||
],
|
||||
createdAt: '2024-09-30T10:00:00Z',
|
||||
updatedAt: '2024-09-30T14:30:00Z'
|
||||
createdAt: "2024-09-30T10:00:00Z",
|
||||
updatedAt: "2024-09-30T14:30:00Z",
|
||||
},
|
||||
'user-analytics': {
|
||||
id: 'user-analytics',
|
||||
title: '👥 사용자 분석 대시보드',
|
||||
description: '사용자 행동 패턴 및 가입 추이 분석',
|
||||
"user-analytics": {
|
||||
id: "user-analytics",
|
||||
title: "👥 사용자 분석 대시보드",
|
||||
description: "사용자 행동 패턴 및 가입 추이 분석",
|
||||
elements: [
|
||||
{
|
||||
id: 'chart-4',
|
||||
type: 'chart',
|
||||
subtype: 'line',
|
||||
id: "chart-4",
|
||||
type: "chart",
|
||||
subtype: "line",
|
||||
position: { x: 20, y: 20 },
|
||||
size: { width: 500, height: 300 },
|
||||
title: '📈 일일 활성 사용자',
|
||||
content: '사용자 활동 데이터',
|
||||
title: "📈 일일 활성 사용자",
|
||||
content: "사용자 활동 데이터",
|
||||
dataSource: {
|
||||
type: 'database',
|
||||
query: 'SELECT date, active_users FROM daily_active_users',
|
||||
refreshInterval: 60000
|
||||
type: "database",
|
||||
query: "SELECT date, active_users FROM daily_active_users",
|
||||
refreshInterval: 60000,
|
||||
},
|
||||
chartConfig: {
|
||||
xAxis: 'date',
|
||||
yAxis: 'active_users',
|
||||
title: '일일 활성 사용자 추이'
|
||||
}
|
||||
}
|
||||
xAxis: "date",
|
||||
yAxis: "active_users",
|
||||
title: "일일 활성 사용자 추이",
|
||||
},
|
||||
},
|
||||
],
|
||||
createdAt: '2024-09-29T15:00:00Z',
|
||||
updatedAt: '2024-09-30T09:15:00Z'
|
||||
}
|
||||
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()
|
||||
};
|
||||
return (
|
||||
dashboards[dashboardId] || {
|
||||
id: dashboardId,
|
||||
title: `대시보드 ${dashboardId}`,
|
||||
description: "샘플 대시보드입니다.",
|
||||
elements: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user