shin / frontend /src /components /usage /VolumeAreaChart.tsx
shinmentakezo07
feat: theme dashboard charts and expose admin UI on HF Spaces
8bdd0d7
Raw
History Blame Contribute Delete
5.8 kB
import { useId, useState } from "react"
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts"
import {
STATUS_COLORS,
formatTokens,
type TimeBucket,
} from "@/lib/usage-data"
import { cn } from "@/lib/utils"
import { ChartCard, ChartTooltip } from "./chart-primitives"
type Metric = "requests" | "tokens"
const METRICS: { key: Metric; label: string }[] = [
{ key: "requests", label: "Requests" },
{ key: "tokens", label: "Tokens" },
]
export function VolumeAreaChart({ buckets }: { buckets: TimeBucket[] }) {
const [metric, setMetric] = useState<Metric>("requests")
const rawId = useId()
const suffix = rawId.replace(/[^a-zA-Z0-9]/g, "")
const data = buckets.map((bucket) => ({
label: bucket.label,
success: metric === "requests" ? bucket.success : bucket.successTokens,
error: metric === "requests" ? bucket.error : bucket.errorTokens,
cancelled: metric === "requests" ? bucket.cancelled : bucket.cancelledTokens,
}))
const totals = data.reduce(
(sum, row) => ({
success: sum.success + row.success,
error: sum.error + row.error,
cancelled: sum.cancelled + row.cancelled,
}),
{ success: 0, error: 0, cancelled: 0 },
)
const formatter = (value: number, _name: string) =>
metric === "tokens" ? formatTokens(value) : String(value)
return (
<ChartCard
title="Request volume"
subtitle="Requests per time bucket, stacked by status"
className="h-full"
actions={
<div
className="flex items-center gap-1 rounded-lg border bg-muted/40 p-0.5"
role="group"
aria-label="Chart metric"
>
{METRICS.map((option) => (
<button
key={option.key}
type="button"
className={cn(
"rounded-md px-2 py-1 text-xs font-medium transition-colors",
metric === option.key
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground",
)}
onClick={() => setMetric(option.key)}
>
{option.label}
</button>
))}
</div>
}
>
<div className="h-56 w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data} margin={{ top: 6, right: 8, bottom: 0, left: 0 }}>
<defs>
{(["success", "error", "cancelled"] as const).map((status) => (
<linearGradient
key={status}
id={`vol-${suffix}-${status}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={STATUS_COLORS[status]}
stopOpacity={0.1}
/>
<stop
offset="100%"
stopColor={STATUS_COLORS[status]}
stopOpacity={0.02}
/>
</linearGradient>
))}
</defs>
<CartesianGrid
stroke="var(--border)"
strokeWidth={1}
vertical={false}
/>
<XAxis
dataKey="label"
tickLine={false}
axisLine={false}
minTickGap={32}
interval="preserveStartEnd"
tick={{ fill: "var(--muted-foreground)", fontSize: 11 }}
/>
<YAxis
tickLine={false}
axisLine={false}
width={40}
allowDecimals={false}
tick={{ fill: "var(--muted-foreground)", fontSize: 11 }}
/>
<Tooltip content={<ChartTooltip formatter={formatter} />} />
<Area
type="monotone"
dataKey="success"
name="Success"
stackId="volume"
stroke={STATUS_COLORS.success}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
fill={`url(#vol-${suffix}-success)`}
/>
<Area
type="monotone"
dataKey="error"
name="Error"
stackId="volume"
stroke={STATUS_COLORS.error}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
fill={`url(#vol-${suffix}-error)`}
/>
<Area
type="monotone"
dataKey="cancelled"
name="Cancelled"
stackId="volume"
stroke={STATUS_COLORS.cancelled}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
fill={`url(#vol-${suffix}-cancelled)`}
/>
</AreaChart>
</ResponsiveContainer>
</div>
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1">
{(
[
["success", "Success"],
["error", "Error"],
["cancelled", "Cancelled"],
] as const
).map(([status, label]) => (
<span
key={status}
className="flex items-center gap-1.5 text-xs text-muted-foreground"
>
<span
className="size-2 rounded-full"
style={{ backgroundColor: STATUS_COLORS[status] }}
/>
{label}
<span className="font-mono text-foreground">
{metric === "tokens" ? formatTokens(totals[status]) : totals[status]}
</span>
</span>
))}
</div>
</ChartCard>
)
}