mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 02:54:37 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user