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:
머니페니 2026-03-18 22:22:44 +09:00
parent ee0de0504c
commit 4ea744ce62

View File

@ -41,6 +41,13 @@ interface Transaction {
realized_pnl: number | null; realized_pnl: number | null;
} }
interface SnapshotListItem {
id: number;
portfolio_id: number;
total_value: number;
snapshot_date: string;
}
interface PortfolioDetail { interface PortfolioDetail {
id: number; id: number;
name: string; name: string;
@ -66,31 +73,13 @@ const CHART_COLORS = [
'hsl(199.4, 95.5%, 53.8%)', 'hsl(199.4, 95.5%, 53.8%)',
]; ];
// Generate sample chart data for portfolio value over time function snapshotsToChartData(snapshots: SnapshotListItem[]): AreaData<Time>[] {
function generateChartData(totalValue: number | null): AreaData<Time>[] { return snapshots
if (totalValue === null || totalValue === 0) return []; .sort((a, b) => a.snapshot_date.localeCompare(b.snapshot_date))
.map((s) => ({
const data: AreaData<Time>[] = []; time: s.snapshot_date as Time,
const now = new Date(); value: Math.round(s.total_value),
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;
} }
export default function PortfolioDetailPage() { export default function PortfolioDetailPage() {
@ -101,6 +90,7 @@ export default function PortfolioDetailPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [portfolio, setPortfolio] = useState<PortfolioDetail | null>(null); const [portfolio, setPortfolio] = useState<PortfolioDetail | null>(null);
const [transactions, setTransactions] = useState<Transaction[]>([]); const [transactions, setTransactions] = useState<Transaction[]>([]);
const [snapshots, setSnapshots] = useState<SnapshotListItem[]>([]);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const fetchPortfolio = useCallback(async () => { const fetchPortfolio = useCallback(async () => {
@ -119,16 +109,24 @@ export default function PortfolioDetailPage() {
const data = await api.get<Transaction[]>(`/api/portfolios/${portfolioId}/transactions`); const data = await api.get<Transaction[]>(`/api/portfolios/${portfolioId}/transactions`);
setTransactions(data); setTransactions(data);
} catch { } catch {
// Transactions may not exist yet
setTransactions([]); setTransactions([]);
} }
}, [portfolioId]); }, [portfolioId]);
const fetchSnapshots = useCallback(async () => {
try {
const data = await api.get<SnapshotListItem[]>(`/api/portfolios/${portfolioId}/snapshots`);
setSnapshots(data);
} catch {
setSnapshots([]);
}
}, [portfolioId]);
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
try { try {
await api.getCurrentUser(); await api.getCurrentUser();
await Promise.all([fetchPortfolio(), fetchTransactions()]); await Promise.all([fetchPortfolio(), fetchTransactions(), fetchSnapshots()]);
} catch { } catch {
router.push('/login'); router.push('/login');
} finally { } finally {
@ -136,7 +134,7 @@ export default function PortfolioDetailPage() {
} }
}; };
init(); init();
}, [router, fetchPortfolio, fetchTransactions]); }, [router, fetchPortfolio, fetchTransactions, fetchSnapshots]);
const formatCurrency = (value: number | null) => { const formatCurrency = (value: number | null) => {
if (value === null) return '-'; 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(); const returnPercent = calculateReturnPercent();
return ( return (
@ -312,16 +310,20 @@ export default function PortfolioDetailPage() {
</div> </div>
{/* Chart Section */} {/* Chart Section */}
{chartData.length > 0 && ( <Card className="mb-6">
<Card className="mb-6"> <CardHeader>
<CardHeader> <CardTitle> </CardTitle>
<CardTitle> </CardTitle> </CardHeader>
</CardHeader> <CardContent>
<CardContent> {chartData.length > 0 ? (
<TradingViewChart data={chartData} height={300} /> <TradingViewChart data={chartData} height={300} />
</CardContent> ) : (
</Card> <div className="flex items-center justify-center h-[300px] text-muted-foreground">
)} . .
</div>
)}
</CardContent>
</Card>
{/* Tabs Section */} {/* Tabs Section */}
<Tabs defaultValue="holdings" className="space-y-4"> <Tabs defaultValue="holdings" className="space-y-4">