From c3d43c97d0520af615fd3ddee0964a51757df79a Mon Sep 17 00:00:00 2001 From: zephyrdark Date: Thu, 5 Feb 2026 23:03:45 +0900 Subject: [PATCH] feat(frontend): improve portfolio pages - TradingView chart component with lightweight-charts v5 API - PortfolioCard component with mini pie chart and return display - Updated portfolio list with cards and empty state - Portfolio detail with charts, tabs (holdings/transactions/analysis) - Improved holdings table with progress bars for weight - Added tabs component from shadcn Co-Authored-By: Claude Opus 4.5 --- frontend/package-lock.json | 31 ++ frontend/package.json | 1 + frontend/src/app/portfolio/[id]/page.tsx | 465 +++++++++++++++--- frontend/src/app/portfolio/page.tsx | 108 ++-- .../components/charts/trading-view-chart.tsx | 62 +++ .../components/portfolio/portfolio-card.tsx | 162 ++++++ frontend/src/components/ui/tabs.tsx | 55 +++ 7 files changed, 797 insertions(+), 87 deletions(-) create mode 100644 frontend/src/components/charts/trading-view-chart.tsx create mode 100644 frontend/src/components/portfolio/portfolio-card.tsx create mode 100644 frontend/src/components/ui/tabs.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0dc8fbd..19779dd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1838,6 +1839,36 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 573317d..9882e9c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/frontend/src/app/portfolio/[id]/page.tsx b/frontend/src/app/portfolio/[id]/page.tsx index c860bdf..30353bf 100644 --- a/frontend/src/app/portfolio/[id]/page.tsx +++ b/frontend/src/app/portfolio/[id]/page.tsx @@ -6,7 +6,11 @@ import Link from 'next/link'; import { DashboardLayout } from '@/components/layout/dashboard-layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { TradingViewChart } from '@/components/charts/trading-view-chart'; +import { DonutChart } from '@/components/charts/donut-chart'; import { api } from '@/lib/api'; +import { AreaData, Time } from 'lightweight-charts'; interface HoldingWithValue { ticker: string; @@ -24,6 +28,15 @@ interface Target { target_ratio: number; } +interface Transaction { + id: number; + ticker: string; + transaction_type: string; + quantity: number; + price: number; + created_at: string; +} + interface PortfolioDetail { id: number; name: string; @@ -37,6 +50,42 @@ interface PortfolioDetail { total_profit_loss: number | null; } +const CHART_COLORS = [ + 'hsl(221.2, 83.2%, 53.3%)', + 'hsl(262.1, 83.3%, 57.8%)', + 'hsl(142.1, 76.2%, 36.3%)', + 'hsl(38.3, 95.7%, 53.1%)', + 'hsl(346.8, 77.2%, 49.8%)', + 'hsl(199.4, 95.5%, 53.8%)', +]; + +// Generate sample chart data for portfolio value over time +function generateChartData(totalValue: number | null): AreaData