Files
sexy/components/stats/SessionsSummaryCards.tsx
T
valknarandClaude Sonnet 5 f7503d03d0
CI / Static checks (push) Successful in 1m9s
CI / Build and push image (push) Failing after 1m2s
Restyle UI with a channel-strip console look and brand icon
Adds a pink/purple/blue SVG brand mark (favicon + header) and reworks
the visual language around the app's actual interaction - fader-driven
device control - via LED-style indicators, mono instrument readouts,
hairline-framed cards, console-tab nav, and an oscilloscope-style
session timeline. Leaves components/ui/* untouched, restyling only via
design tokens and the app's own composed components. Also fixes a
proxy.ts matcher gap that was gating /icon.svg behind auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
2026-08-25 16:43:28 +02:00

64 lines
2.5 KiB
TypeScript

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export interface SessionsSummary {
count: number;
totalDurationMs: number;
avgDurationMs: number;
byKind: { kind: "live" | "replay"; count: number; totalDurationMs: number }[];
durationPerDevice: { deviceId: number; displayName: string | null; bleName: string; totalActiveMs: number }[];
}
function formatHours(ms: number): string {
return `${(ms / 3_600_000).toFixed(1)}h`;
}
export function SessionsSummaryCards({ summary }: { summary: SessionsSummary }) {
const liveCount = summary.byKind.find((k) => k.kind === "live")?.count ?? 0;
const replayCount = summary.byKind.find((k) => k.kind === "replay")?.count ?? 0;
return (
<div className="flex flex-col gap-4">
<div className="grid gap-4 sm:grid-cols-3">
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Completed sessions</CardTitle>
</CardHeader>
<CardContent className="bp-readout text-3xl">{summary.count}</CardContent>
</Card>
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Total time</CardTitle>
</CardHeader>
<CardContent className="bp-readout text-3xl">{formatHours(summary.totalDurationMs)}</CardContent>
</Card>
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Avg session length</CardTitle>
</CardHeader>
<CardContent className="bp-readout text-3xl">
{Math.round(summary.avgDurationMs / 60_000)}m
</CardContent>
</Card>
</div>
<p className="text-sm text-muted-foreground">
{liveCount} live · {replayCount} replay
</p>
{summary.durationPerDevice.length > 0 && (
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-base">Duration per device</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-2">
{summary.durationPerDevice.map((d) => (
<div key={d.deviceId} className="flex items-center justify-between text-sm">
<span>{d.displayName ?? d.bleName}</span>
<span className="text-muted-foreground">{formatHours(d.totalActiveMs)}</span>
</div>
))}
</CardContent>
</Card>
)}
</div>
);
}