zephyrdark 4f432fb85c feat(frontend): add dashboard charts
- Sparkline for summary cards
- AreaChart for asset trends
- DonutChart for sector allocation
- BarChart for portfolio comparison

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:58:00 +09:00

228 lines
6.9 KiB
TypeScript

'use client';
import { DashboardLayout } from '@/components/layout/dashboard-layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Sparkline, AreaChart, DonutChart, BarChart } from '@/components/charts';
import { Wallet, TrendingUp, Briefcase, RefreshCw } from 'lucide-react';
// Sample data for sparklines
const totalAssetSparkline = [
{ value: 9500000 },
{ value: 9800000 },
{ value: 10000000 },
{ value: 9700000 },
{ value: 10200000 },
{ value: 10500000 },
{ value: 10800000 },
];
const returnSparkline = [
{ value: 2.5 },
{ value: 3.1 },
{ value: 2.8 },
{ value: 4.2 },
{ value: 5.1 },
{ value: 4.8 },
{ value: 5.5 },
];
// Sample data for asset trend chart
const assetTrendData = [
{ date: '2024-01', value: 10000000 },
{ date: '2024-02', value: 10500000 },
{ date: '2024-03', value: 10200000 },
{ date: '2024-04', value: 10800000 },
{ date: '2024-05', value: 11200000 },
{ date: '2024-06', value: 11000000 },
{ date: '2024-07', value: 11500000 },
{ date: '2024-08', value: 12000000 },
{ date: '2024-09', value: 11800000 },
{ date: '2024-10', value: 12500000 },
{ date: '2024-11', value: 13000000 },
{ date: '2024-12', value: 13500000 },
];
// Sample data for sector allocation
const sectorData = [
{ name: '기술', value: 40, color: '#3b82f6' },
{ name: '금융', value: 30, color: '#10b981' },
{ name: '헬스케어', value: 20, color: '#f59e0b' },
{ name: '기타', value: 10, color: '#6b7280' },
];
// Sample data for portfolio comparison
const portfolioComparisonData = [
{ name: '포트폴리오 A', value: 12.5 },
{ name: '포트폴리오 B', value: 8.3 },
{ name: '포트폴리오 C', value: -2.1 },
{ name: 'KOSPI', value: 5.7 },
{ name: 'S&P 500', value: 15.2 },
];
const summaryCards = [
{
title: '총 자산',
value: '₩135,000,000',
description: '전체 포트폴리오 가치',
icon: Wallet,
sparklineData: totalAssetSparkline,
sparklineColor: '#3b82f6',
},
{
title: '총 수익률',
value: '+35.0%',
description: '전체 수익률',
icon: TrendingUp,
sparklineData: returnSparkline,
sparklineColor: '#10b981',
},
{
title: '포트폴리오',
value: '3',
description: '활성 포트폴리오 수',
icon: Briefcase,
sparklineData: null,
sparklineColor: null,
},
{
title: '리밸런싱',
value: '2',
description: '예정된 리밸런싱',
icon: RefreshCw,
sparklineData: null,
sparklineColor: null,
},
];
const formatKRW = (value: number) => {
if (value >= 100000000) {
return `${(value / 100000000).toFixed(1)}`;
}
if (value >= 10000) {
return `${(value / 10000).toFixed(0)}`;
}
return value.toLocaleString();
};
const formatPercent = (value: number) => `${value.toFixed(1)}%`;
export default function DashboardPage() {
return (
<DashboardLayout>
<div className="space-y-6">
{/* Summary Cards */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{summaryCards.map((card) => {
const Icon = card.icon;
return (
<Card key={card.title}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
{card.title}
</CardTitle>
<Icon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{card.value}</div>
<p className="text-xs text-muted-foreground">
{card.description}
</p>
{card.sparklineData && (
<Sparkline
data={card.sparklineData}
color={card.sparklineColor || '#3b82f6'}
height={32}
className="mt-2"
/>
)}
</CardContent>
</Card>
);
})}
</div>
{/* Main Charts */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle> </CardTitle>
</CardHeader>
<CardContent>
<AreaChart
data={assetTrendData}
height={280}
color="#3b82f6"
formatValue={formatKRW}
showLegend={false}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle> </CardTitle>
</CardHeader>
<CardContent>
<DonutChart
data={sectorData}
height={280}
innerRadius={50}
outerRadius={90}
/>
</CardContent>
</Card>
</div>
{/* Secondary Charts */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle> </CardTitle>
</CardHeader>
<CardContent>
<BarChart
data={portfolioComparisonData}
height={240}
layout="horizontal"
formatValue={formatPercent}
colorByValue={true}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50">
<RefreshCw className="h-4 w-4 text-blue-500" />
<div className="flex-1">
<p className="text-sm font-medium"> </p>
<p className="text-xs text-muted-foreground"> A - 2 </p>
</div>
</div>
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50">
<TrendingUp className="h-4 w-4 text-green-500" />
<div className="flex-1">
<p className="text-sm font-medium"> </p>
<p className="text-xs text-muted-foreground"> B - </p>
</div>
</div>
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50">
<Briefcase className="h-4 w-4 text-amber-500" />
<div className="flex-1">
<p className="text-sm font-medium"> </p>
<p className="text-xs text-muted-foreground"> - 3 </p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</DashboardLayout>
);
}