feat(ui): enhance dashboard visualizations

- Replace usage metrics line charts with gradient area charts
- Redesign summary cards with improved styling and icons
- Add revenue share progress bar to models table
- Format numbers in tables for better readability
This commit is contained in:
Shroominic
2025-11-23 21:51:36 +00:00
committed by Cursor Agent
parent f13638387f
commit d0aa91aa51
3 changed files with 88 additions and 38 deletions
+33 -22
View File
@@ -9,6 +9,7 @@ import {
TableRow,
} from '@/components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { ModelRevenueData } from '@/lib/api/services/admin';
interface RevenueByModelTableProps {
@@ -25,7 +26,7 @@ export function RevenueByModelTable({
<CardHeader>
<CardTitle>Revenue by Model</CardTitle>
<p className="text-sm text-muted-foreground">
Total Revenue: {totalRevenue.toLocaleString(undefined, { maximumFractionDigits: 2 })} sats
Total Revenue: <span className="font-mono font-medium text-foreground">{totalRevenue.toLocaleString(undefined, { maximumFractionDigits: 2 })}</span> sats
</p>
</CardHeader>
<CardContent>
@@ -37,6 +38,7 @@ export function RevenueByModelTable({
<TableHead className="text-right">Successful</TableHead>
<TableHead className="text-right">Failed</TableHead>
<TableHead className="text-right">Revenue (sats)</TableHead>
<TableHead className="w-[100px]">Share</TableHead>
<TableHead className="text-right">Refunds (sats)</TableHead>
<TableHead className="text-right">Net Revenue (sats)</TableHead>
<TableHead className="text-right">Avg/Request</TableHead>
@@ -45,31 +47,40 @@ export function RevenueByModelTable({
<TableBody>
{models.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="text-center text-muted-foreground">
<TableCell colSpan={9} className="text-center text-muted-foreground">
No model data available
</TableCell>
</TableRow>
) : (
models.map((model) => (
<TableRow key={model.model}>
<TableCell className="font-medium">{model.model}</TableCell>
<TableCell className="text-right">{model.requests}</TableCell>
<TableCell className="text-right text-green-600">{model.successful}</TableCell>
<TableCell className="text-right text-red-600">{model.failed}</TableCell>
<TableCell className="text-right">
{model.revenue_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-right text-red-500">
{model.refunds_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-right font-semibold">
{model.net_revenue_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-right text-muted-foreground">
{model.avg_revenue_per_request.toLocaleString(undefined, { maximumFractionDigits: 3 })}
</TableCell>
</TableRow>
))
models.map((model) => {
const share = totalRevenue > 0 ? (model.revenue_sats / totalRevenue) * 100 : 0;
return (
<TableRow key={model.model}>
<TableCell className="font-medium">{model.model}</TableCell>
<TableCell className="text-right font-mono">{model.requests}</TableCell>
<TableCell className="text-right text-green-600 font-mono">{model.successful}</TableCell>
<TableCell className="text-right text-red-600 font-mono">{model.failed}</TableCell>
<TableCell className="text-right font-mono">
{model.revenue_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Progress value={share} className="h-2" />
<span className="text-xs text-muted-foreground w-8 text-right">{share.toFixed(0)}%</span>
</div>
</TableCell>
<TableCell className="text-right text-red-500 font-mono">
{model.refunds_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-right font-semibold font-mono">
{model.net_revenue_sats.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-right text-muted-foreground font-mono">
{model.avg_revenue_per_request.toLocaleString(undefined, { maximumFractionDigits: 3 })}
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
+48 -11
View File
@@ -2,8 +2,8 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Line,
LineChart,
Area,
AreaChart,
ResponsiveContainer,
Tooltip,
XAxis,
@@ -42,34 +42,71 @@ export function UsageMetricsChart({
</CardHeader>
<CardContent>
<ResponsiveContainer width='100%' height={300}>
<LineChart data={formattedData}>
<CartesianGrid strokeDasharray='3 3' className='stroke-muted' />
<AreaChart data={formattedData}>
<defs>
{dataKeys.map((dataKey) => (
<linearGradient
key={dataKey.key}
id={`color${dataKey.key}`}
x1='0'
y1='0'
x2='0'
y2='1'
>
<stop
offset='5%'
stopColor={dataKey.color}
stopOpacity={0.3}
/>
<stop
offset='95%'
stopColor={dataKey.color}
stopOpacity={0}
/>
</linearGradient>
))}
</defs>
<CartesianGrid strokeDasharray='3 3' className='stroke-muted/30' />
<XAxis
dataKey='time'
className='text-xs'
tick={{ fill: 'currentColor' }}
tick={{ fill: 'hsl(var(--muted-foreground))' }}
tickLine={false}
axisLine={false}
minTickGap={32}
/>
<YAxis
className='text-xs'
tick={{ fill: 'hsl(var(--muted-foreground))' }}
tickLine={false}
axisLine={false}
width={40}
/>
<YAxis className='text-xs' tick={{ fill: 'currentColor' }} />
<Tooltip
contentStyle={{
backgroundColor: 'hsl(var(--background))',
border: '1px solid hsl(var(--border))',
borderRadius: '6px',
borderRadius: '8px',
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)',
}}
itemStyle={{ fontSize: '12px' }}
labelStyle={{ fontSize: '12px', color: 'hsl(var(--muted-foreground))', marginBottom: '8px' }}
/>
<Legend />
<Legend wrapperStyle={{ fontSize: '12px', paddingTop: '16px' }} />
{dataKeys.map((dataKey) => (
<Line
<Area
key={dataKey.key}
type='monotone'
dataKey={dataKey.key}
stroke={dataKey.color}
fillOpacity={1}
fill={`url(#color${dataKey.key})`}
name={dataKey.name}
strokeWidth={2}
dot={false}
animationDuration={1000}
/>
))}
</LineChart>
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
+7 -5
View File
@@ -102,15 +102,17 @@ export function UsageSummaryCards({ summary }: UsageSummaryCardsProps) {
];
return (
<div className='grid gap-4 md:grid-cols-2 lg:grid-cols-4'>
<div className='grid gap-6 md:grid-cols-2 lg:grid-cols-4'>
{cards.map((card) => (
<Card key={card.title}>
<Card key={card.title} className="hover:bg-muted/50 transition-colors">
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>{card.title}</CardTitle>
<card.icon className={`h-4 w-4 ${card.color}`} />
<CardTitle className='text-sm font-medium text-muted-foreground'>{card.title}</CardTitle>
<div className={`p-2 rounded-full bg-secondary`}>
<card.icon className={`h-4 w-4 ${card.color}`} />
</div>
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{card.value}</div>
<div className='text-2xl font-bold tracking-tight'>{card.value}</div>
</CardContent>
</Card>
))}