removed angular app and switched to react
This commit is contained in:
parent
56e3938561
commit
3a9d45c543
101 changed files with 2697 additions and 4075 deletions
16
apps/web/src/features/dashboard/DashboardPage.tsx
Normal file
16
apps/web/src/features/dashboard/DashboardPage.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { DashboardStats, DashboardActivity, PortfolioTable } from './components';
|
||||
|
||||
export function DashboardPage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-lg font-bold text-text-primary mb-2">Welcome to Stock Bot Dashboard</h1>
|
||||
<p className="text-text-secondary mb-6 text-sm">
|
||||
Monitor your trading performance, manage portfolios, and analyze market data.
|
||||
</p>
|
||||
|
||||
<DashboardStats />
|
||||
<DashboardActivity />
|
||||
<PortfolioTable />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { Card, CardHeader, CardContent } from '@/components/ui';
|
||||
|
||||
export function DashboardActivity() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-base font-medium text-text-primary">Recent Activity</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between p-2 bg-surface-tertiary rounded border border-border">
|
||||
<span className="text-text-secondary text-sm">No recent activity</span>
|
||||
<span className="text-text-muted text-xs">--</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-base font-medium text-text-primary">Market Overview</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between p-2 bg-surface-tertiary rounded border border-border">
|
||||
<span className="text-text-secondary text-sm">Market data loading...</span>
|
||||
<span className="text-text-muted text-xs">--</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { CurrencyDollarIcon, ChartBarIcon, DocumentTextIcon } from '@heroicons/react/24/outline';
|
||||
import { StatCard } from '@/components/ui';
|
||||
|
||||
export function DashboardStats() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 mb-6">
|
||||
<StatCard
|
||||
title="Portfolio Value"
|
||||
value="$0.00"
|
||||
subtitle="Total assets"
|
||||
icon={<CurrencyDollarIcon className="h-5 w-5 text-primary-400" />}
|
||||
iconBgColor="bg-primary-500/10"
|
||||
valueColor="text-primary-400"
|
||||
borderColor="border-primary-500/50"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Total Return"
|
||||
value="+0.00%"
|
||||
subtitle="Since inception"
|
||||
icon={<ChartBarIcon className="h-5 w-5 text-success" />}
|
||||
iconBgColor="bg-success/10"
|
||||
valueColor="text-success"
|
||||
borderColor="border-success/50"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Active Strategies"
|
||||
value="0"
|
||||
subtitle="Running algorithms"
|
||||
icon={<DocumentTextIcon className="h-5 w-5 text-warning" />}
|
||||
iconBgColor="bg-warning/10"
|
||||
valueColor="text-warning"
|
||||
borderColor="border-warning/50"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
652
apps/web/src/features/dashboard/components/PortfolioTable.tsx
Normal file
652
apps/web/src/features/dashboard/components/PortfolioTable.tsx
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
import { DataTable } from '@/components/ui';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import React from 'react';
|
||||
|
||||
interface PortfolioItem {
|
||||
symbol: string;
|
||||
quantity: number;
|
||||
avgPrice: number;
|
||||
currentPrice: number;
|
||||
value: number;
|
||||
change: number;
|
||||
changePercent: number;
|
||||
// Additional columns for stress testing
|
||||
volume: number;
|
||||
marketCap: number;
|
||||
pe: number;
|
||||
pb: number;
|
||||
roe: number;
|
||||
debt: number;
|
||||
revenue: number;
|
||||
earnings: number;
|
||||
dividend: number;
|
||||
beta: number;
|
||||
rsi: number;
|
||||
macd: number;
|
||||
sma20: number;
|
||||
sma50: number;
|
||||
sma200: number;
|
||||
support: number;
|
||||
resistance: number;
|
||||
volatility: number;
|
||||
sharpe: number;
|
||||
alpha: number;
|
||||
correlation: number;
|
||||
sector: string;
|
||||
industry: string;
|
||||
country: string;
|
||||
exchange: string;
|
||||
currency: string;
|
||||
lastUpdate: string;
|
||||
analyst1: string;
|
||||
analyst2: string;
|
||||
analyst3: string;
|
||||
rating1: number;
|
||||
rating2: number;
|
||||
rating3: number;
|
||||
target1: number;
|
||||
target2: number;
|
||||
target3: number;
|
||||
risk: string;
|
||||
esg: number;
|
||||
}
|
||||
|
||||
export function PortfolioTable() {
|
||||
// Generate 100,000 rows of sample data
|
||||
const data: PortfolioItem[] = React.useMemo(() => {
|
||||
const symbols = [
|
||||
'AAPL',
|
||||
'GOOGL',
|
||||
'MSFT',
|
||||
'TSLA',
|
||||
'AMZN',
|
||||
'META',
|
||||
'NFLX',
|
||||
'NVDA',
|
||||
'AMD',
|
||||
'INTC',
|
||||
'CRM',
|
||||
'ORCL',
|
||||
'IBM',
|
||||
'CSCO',
|
||||
'UBER',
|
||||
'LYFT',
|
||||
'SNAP',
|
||||
'TWTR',
|
||||
'SPOT',
|
||||
'SQ',
|
||||
];
|
||||
const sectors = [
|
||||
'Technology',
|
||||
'Healthcare',
|
||||
'Finance',
|
||||
'Energy',
|
||||
'Consumer',
|
||||
'Industrial',
|
||||
'Materials',
|
||||
'Utilities',
|
||||
'Real Estate',
|
||||
'Telecom',
|
||||
];
|
||||
const industries = [
|
||||
'Software',
|
||||
'Hardware',
|
||||
'Biotech',
|
||||
'Banking',
|
||||
'Oil & Gas',
|
||||
'Retail',
|
||||
'Manufacturing',
|
||||
'Mining',
|
||||
'Utilities',
|
||||
'REITs',
|
||||
];
|
||||
const countries = [
|
||||
'USA',
|
||||
'Canada',
|
||||
'UK',
|
||||
'Germany',
|
||||
'Japan',
|
||||
'China',
|
||||
'India',
|
||||
'Brazil',
|
||||
'Australia',
|
||||
'France',
|
||||
];
|
||||
const exchanges = ['NYSE', 'NASDAQ', 'LSE', 'TSX', 'Nikkei', 'SSE', 'BSE', 'ASX', 'Euronext'];
|
||||
const currencies = ['USD', 'CAD', 'GBP', 'EUR', 'JPY', 'CNY', 'INR', 'BRL', 'AUD'];
|
||||
const analysts = [
|
||||
'Goldman Sachs',
|
||||
'Morgan Stanley',
|
||||
'JPMorgan',
|
||||
'Bank of America',
|
||||
'Wells Fargo',
|
||||
'Credit Suisse',
|
||||
'Deutsche Bank',
|
||||
'Barclays',
|
||||
'UBS',
|
||||
'Citigroup',
|
||||
];
|
||||
const risks = ['Low', 'Medium', 'High', 'Very High'];
|
||||
|
||||
return Array.from({ length: 100000 }, (_, i) => {
|
||||
const basePrice = 50 + Math.random() * 500;
|
||||
const change = (Math.random() - 0.5) * 20;
|
||||
const quantity = Math.floor(Math.random() * 1000) + 1;
|
||||
|
||||
return {
|
||||
symbol: `${symbols[i % symbols.length]}${Math.floor(i / symbols.length)}`,
|
||||
quantity,
|
||||
avgPrice: basePrice,
|
||||
currentPrice: basePrice + change,
|
||||
value: (basePrice + change) * quantity,
|
||||
change: change * quantity,
|
||||
changePercent: (change / basePrice) * 100,
|
||||
volume: Math.floor(Math.random() * 10000000),
|
||||
marketCap: Math.floor(Math.random() * 1000000000000),
|
||||
pe: Math.random() * 50 + 5,
|
||||
pb: Math.random() * 10 + 0.5,
|
||||
roe: Math.random() * 30 + 5,
|
||||
debt: Math.random() * 50,
|
||||
revenue: Math.floor(Math.random() * 100000000000),
|
||||
earnings: Math.floor(Math.random() * 10000000000),
|
||||
dividend: Math.random() * 5,
|
||||
beta: Math.random() * 3 + 0.5,
|
||||
rsi: Math.random() * 100,
|
||||
macd: (Math.random() - 0.5) * 10,
|
||||
sma20: basePrice + (Math.random() - 0.5) * 10,
|
||||
sma50: basePrice + (Math.random() - 0.5) * 20,
|
||||
sma200: basePrice + (Math.random() - 0.5) * 50,
|
||||
support: basePrice - Math.random() * 20,
|
||||
resistance: basePrice + Math.random() * 20,
|
||||
volatility: Math.random() * 100,
|
||||
sharpe: Math.random() * 3,
|
||||
alpha: (Math.random() - 0.5) * 20,
|
||||
correlation: (Math.random() - 0.5) * 2,
|
||||
sector: sectors[Math.floor(Math.random() * sectors.length)],
|
||||
industry: industries[Math.floor(Math.random() * industries.length)],
|
||||
country: countries[Math.floor(Math.random() * countries.length)],
|
||||
exchange: exchanges[Math.floor(Math.random() * exchanges.length)],
|
||||
currency: currencies[Math.floor(Math.random() * currencies.length)],
|
||||
lastUpdate: new Date(Date.now() - Math.random() * 86400000).toISOString(),
|
||||
analyst1: analysts[Math.floor(Math.random() * analysts.length)],
|
||||
analyst2: analysts[Math.floor(Math.random() * analysts.length)],
|
||||
analyst3: analysts[Math.floor(Math.random() * analysts.length)],
|
||||
rating1: Math.random() * 5 + 1,
|
||||
rating2: Math.random() * 5 + 1,
|
||||
rating3: Math.random() * 5 + 1,
|
||||
target1: basePrice + (Math.random() - 0.3) * 50,
|
||||
target2: basePrice + (Math.random() - 0.3) * 50,
|
||||
target3: basePrice + (Math.random() - 0.3) * 50,
|
||||
risk: risks[Math.floor(Math.random() * risks.length)],
|
||||
esg: Math.random() * 100,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const columns: ColumnDef<PortfolioItem>[] = [
|
||||
{
|
||||
id: 'symbol',
|
||||
header: 'Symbol',
|
||||
accessorKey: 'symbol',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono font-bold text-primary-400">{getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'quantity',
|
||||
header: 'Quantity',
|
||||
accessorKey: 'quantity',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toLocaleString()}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'avgPrice',
|
||||
header: 'Avg Price',
|
||||
accessorKey: 'avgPrice',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'currentPrice',
|
||||
header: 'Current Price',
|
||||
accessorKey: 'currentPrice',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
header: 'Value',
|
||||
accessorKey: 'value',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono font-bold">${(getValue() as number).toLocaleString()}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'change',
|
||||
header: 'P&L',
|
||||
accessorKey: 'change',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const isPositive = value >= 0;
|
||||
return (
|
||||
<span className={`font-mono font-medium ${isPositive ? 'text-success' : 'text-danger'}`}>
|
||||
{isPositive ? '+' : ''}${value.toLocaleString()}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'changePercent',
|
||||
header: 'P&L %',
|
||||
accessorKey: 'changePercent',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const isPositive = value >= 0;
|
||||
return (
|
||||
<span className={`font-mono font-medium ${isPositive ? 'text-success' : 'text-danger'}`}>
|
||||
{isPositive ? '+' : ''}
|
||||
{value.toFixed(2)}%
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'volume',
|
||||
header: 'Volume',
|
||||
accessorKey: 'volume',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono text-text-secondary">
|
||||
{(getValue() as number).toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'marketCap',
|
||||
header: 'Market Cap',
|
||||
accessorKey: 'marketCap',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
if (value >= 1e12) return <span className="font-mono">${(value / 1e12).toFixed(2)}T</span>;
|
||||
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
|
||||
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
|
||||
return <span className="font-mono">${value.toLocaleString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pe',
|
||||
header: 'P/E',
|
||||
accessorKey: 'pe',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'pb',
|
||||
header: 'P/B',
|
||||
accessorKey: 'pb',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'roe',
|
||||
header: 'ROE %',
|
||||
accessorKey: 'roe',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'debt',
|
||||
header: 'Debt %',
|
||||
accessorKey: 'debt',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'revenue',
|
||||
header: 'Revenue',
|
||||
accessorKey: 'revenue',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
|
||||
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
|
||||
return <span className="font-mono">${value.toLocaleString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'earnings',
|
||||
header: 'Earnings',
|
||||
accessorKey: 'earnings',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
if (value >= 1e9) return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;
|
||||
if (value >= 1e6) return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;
|
||||
return <span className="font-mono">${value.toLocaleString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'dividend',
|
||||
header: 'Dividend %',
|
||||
accessorKey: 'dividend',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
return <span className="font-mono text-success">{value.toFixed(2)}%</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'beta',
|
||||
header: 'Beta',
|
||||
accessorKey: 'beta',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value > 1 ? 'text-warning' : value < 1 ? 'text-success' : 'text-text-primary';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'rsi',
|
||||
header: 'RSI',
|
||||
accessorKey: 'rsi',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color =
|
||||
value > 70 ? 'text-danger' : value < 30 ? 'text-success' : 'text-text-primary';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'macd',
|
||||
header: 'MACD',
|
||||
accessorKey: 'macd',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value > 0 ? 'text-success' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sma20',
|
||||
header: 'SMA 20',
|
||||
accessorKey: 'sma20',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sma50',
|
||||
header: 'SMA 50',
|
||||
accessorKey: 'sma50',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sma200',
|
||||
header: 'SMA 200',
|
||||
accessorKey: 'sma200',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
header: 'Support',
|
||||
accessorKey: 'support',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono text-success">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'resistance',
|
||||
header: 'Resistance',
|
||||
accessorKey: 'resistance',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono text-danger">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'volatility',
|
||||
header: 'Volatility',
|
||||
accessorKey: 'volatility',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sharpe',
|
||||
header: 'Sharpe',
|
||||
accessorKey: 'sharpe',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'alpha',
|
||||
header: 'Alpha',
|
||||
accessorKey: 'alpha',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value > 0 ? 'text-success' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'correlation',
|
||||
header: 'Correlation',
|
||||
accessorKey: 'correlation',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sector',
|
||||
header: 'Sector',
|
||||
accessorKey: 'sector',
|
||||
size: 150,
|
||||
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
|
||||
},
|
||||
{
|
||||
id: 'industry',
|
||||
header: 'Industry',
|
||||
accessorKey: 'industry',
|
||||
size: 150,
|
||||
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
header: 'Country',
|
||||
accessorKey: 'country',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
|
||||
},
|
||||
{
|
||||
id: 'exchange',
|
||||
header: 'Exchange',
|
||||
accessorKey: 'exchange',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
|
||||
},
|
||||
{
|
||||
id: 'currency',
|
||||
header: 'Currency',
|
||||
accessorKey: 'currency',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono text-text-secondary">{getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'lastUpdate',
|
||||
header: 'Last Update',
|
||||
accessorKey: 'lastUpdate',
|
||||
size: 150,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="text-xs text-text-muted">
|
||||
{new Date(getValue() as string).toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'analyst1',
|
||||
header: 'Analyst 1',
|
||||
accessorKey: 'analyst1',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="text-xs text-text-secondary">{getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'analyst2',
|
||||
header: 'Analyst 2',
|
||||
accessorKey: 'analyst2',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="text-xs text-text-secondary">{getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'analyst3',
|
||||
header: 'Analyst 3',
|
||||
accessorKey: 'analyst3',
|
||||
size: 120,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="text-xs text-text-secondary">{getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'rating1',
|
||||
header: 'Rating 1',
|
||||
accessorKey: 'rating1',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'rating2',
|
||||
header: 'Rating 2',
|
||||
accessorKey: 'rating2',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'rating3',
|
||||
header: 'Rating 3',
|
||||
accessorKey: 'rating3',
|
||||
size: 80,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'target1',
|
||||
header: 'Target 1',
|
||||
accessorKey: 'target1',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'target2',
|
||||
header: 'Target 2',
|
||||
accessorKey: 'target2',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'target3',
|
||||
header: 'Target 3',
|
||||
accessorKey: 'target3',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'risk',
|
||||
header: 'Risk Level',
|
||||
accessorKey: 'risk',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as string;
|
||||
const color =
|
||||
value === 'Low' ? 'text-success' : value === 'Medium' ? 'text-warning' : 'text-danger';
|
||||
return <span className={`px-2 py-1 rounded text-xs font-medium ${color}`}>{value}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'esg',
|
||||
header: 'ESG Score',
|
||||
accessorKey: 'esg',
|
||||
size: 100,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue() as number;
|
||||
const color = value >= 70 ? 'text-success' : value >= 40 ? 'text-warning' : 'text-danger';
|
||||
return <span className={`font-mono ${color}`}>{value.toFixed(0)}</span>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<h3 className="text-lg font-bold text-text-primary mb-2">Portfolio Holdings</h3>
|
||||
<p className="text-sm text-text-secondary mb-4">
|
||||
Performance test: 100,000 rows × {columns.length} columns ={' '}
|
||||
{(100000 * columns.length).toLocaleString()} cells
|
||||
</p>
|
||||
<div className="h-96 w-full border border-border rounded-lg overflow-hidden">
|
||||
<DataTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
height="100%"
|
||||
onRowClick={row => console.log('Clicked:', row.symbol)}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
apps/web/src/features/dashboard/components/index.ts
Normal file
3
apps/web/src/features/dashboard/components/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { DashboardStats } from './DashboardStats';
|
||||
export { DashboardActivity } from './DashboardActivity';
|
||||
export { PortfolioTable } from './PortfolioTable';
|
||||
1
apps/web/src/features/dashboard/index.ts
Normal file
1
apps/web/src/features/dashboard/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { DashboardPage } from './DashboardPage';
|
||||
Loading…
Add table
Add a link
Reference in a new issue