feat: replace simulated sine wave chart with real snapshot data
Portfolio value chart now uses actual snapshot API data instead of generated simulation. Shows empty state message when no snapshots exist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ee0de0504c
commit
4ea744ce62
@ -41,6 +41,13 @@ interface Transaction {
|
||||
realized_pnl: number | null;
|
||||
}
|
||||
|
||||
interface SnapshotListItem {
|
||||
id: number;
|
||||
portfolio_id: number;
|
||||
total_value: number;
|
||||
snapshot_date: string;
|
||||
}
|
||||
|
||||
interface PortfolioDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -66,31 +73,13 @@ const CHART_COLORS = [
|
||||
'hsl(199.4, 95.5%, 53.8%)',
|
||||
];
|
||||
|
||||
// Generate sample chart data for portfolio value over time
|
||||
function generateChartData(totalValue: number | null): AreaData<Time>[] {
|
||||
if (totalValue === null || totalValue === 0) return [];
|
||||
|
||||
const data: AreaData<Time>[] = [];
|
||||
const now = new Date();
|
||||
const baseValue = totalValue * 0.85;
|
||||
|
||||
for (let i = 90; i >= 0; i--) {
|
||||
const date = new Date(now);
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
|
||||
// Simulate value fluctuation
|
||||
const progress = (90 - i) / 90;
|
||||
const fluctuation = Math.sin(i * 0.1) * 0.05;
|
||||
const value = baseValue + (totalValue - baseValue) * progress * (1 + fluctuation);
|
||||
|
||||
data.push({
|
||||
time: dateStr as Time,
|
||||
value: Math.round(value),
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
function snapshotsToChartData(snapshots: SnapshotListItem[]): AreaData<Time>[] {
|
||||
return snapshots
|
||||
.sort((a, b) => a.snapshot_date.localeCompare(b.snapshot_date))
|
||||
.map((s) => ({
|
||||
time: s.snapshot_date as Time,
|
||||
value: Math.round(s.total_value),
|
||||
}));
|
||||
}
|
||||
|
||||
export default function PortfolioDetailPage() {
|
||||
@ -101,6 +90,7 @@ export default function PortfolioDetailPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [portfolio, setPortfolio] = useState<PortfolioDetail | null>(null);
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [snapshots, setSnapshots] = useState<SnapshotListItem[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchPortfolio = useCallback(async () => {
|
||||
@ -119,16 +109,24 @@ export default function PortfolioDetailPage() {
|
||||
const data = await api.get<Transaction[]>(`/api/portfolios/${portfolioId}/transactions`);
|
||||
setTransactions(data);
|
||||
} catch {
|
||||
// Transactions may not exist yet
|
||||
setTransactions([]);
|
||||
}
|
||||
}, [portfolioId]);
|
||||
|
||||
const fetchSnapshots = useCallback(async () => {
|
||||
try {
|
||||
const data = await api.get<SnapshotListItem[]>(`/api/portfolios/${portfolioId}/snapshots`);
|
||||
setSnapshots(data);
|
||||
} catch {
|
||||
setSnapshots([]);
|
||||
}
|
||||
}, [portfolioId]);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
try {
|
||||
await api.getCurrentUser();
|
||||
await Promise.all([fetchPortfolio(), fetchTransactions()]);
|
||||
await Promise.all([fetchPortfolio(), fetchTransactions(), fetchSnapshots()]);
|
||||
} catch {
|
||||
router.push('/login');
|
||||
} finally {
|
||||
@ -136,7 +134,7 @@ export default function PortfolioDetailPage() {
|
||||
}
|
||||
};
|
||||
init();
|
||||
}, [router, fetchPortfolio, fetchTransactions]);
|
||||
}, [router, fetchPortfolio, fetchTransactions, fetchSnapshots]);
|
||||
|
||||
const formatCurrency = (value: number | null) => {
|
||||
if (value === null) return '-';
|
||||
@ -192,7 +190,7 @@ export default function PortfolioDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const chartData = portfolio ? generateChartData(portfolio.total_value) : [];
|
||||
const chartData = snapshotsToChartData(snapshots);
|
||||
const returnPercent = calculateReturnPercent();
|
||||
|
||||
return (
|
||||
@ -312,16 +310,20 @@ export default function PortfolioDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Chart Section */}
|
||||
{chartData.length > 0 && (
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>포트폴리오 가치 추이</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{chartData.length > 0 ? (
|
||||
<TradingViewChart data={chartData} height={300} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
|
||||
스냅샷 데이터가 없습니다. 스냅샷을 생성하면 가치 추이를 확인할 수 있습니다.
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Tabs Section */}
|
||||
<Tabs defaultValue="holdings" className="space-y-4">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user