Remove stats tab switcher, drop duration-per-device, show devices inline

Sessions and Devices no longer live behind a Tabs switcher on the stats
page - both sections render directly, one after the other. Also drops
the "Duration per device" list from the sessions summary cards since
DeviceUsageTable already shows per-device active time in more detail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzyfRyA7h5rs5SCAahLAWd
This commit is contained in:
2026-08-30 20:33:47 +02:00
co-authored by Claude Sonnet 5
parent e459ccd0ed
commit c4ed849d37
3 changed files with 30 additions and 70 deletions
+2 -13
View File
@@ -1,5 +1,4 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { SessionsSummaryCards } from "@/components/stats/SessionsSummaryCards"; import { SessionsSummaryCards } from "@/components/stats/SessionsSummaryCards";
import { DeviceUsageTable } from "@/components/stats/DeviceUsageTable"; import { DeviceUsageTable } from "@/components/stats/DeviceUsageTable";
import { getDeviceCommandCounts, getDeviceUsageStats, getSessionsSummary } from "@/lib/db/queries/stats"; import { getDeviceCommandCounts, getDeviceUsageStats, getSessionsSummary } from "@/lib/db/queries/stats";
@@ -24,18 +23,8 @@ export default async function StatsPage() {
<p className="text-sm text-muted-foreground">Usage across sessions and devices.</p> <p className="text-sm text-muted-foreground">Usage across sessions and devices.</p>
</div> </div>
<Tabs defaultValue="sessions"> <SessionsSummaryCards summary={sessionsSummary} />
<TabsList className="bp-glass"> <DeviceUsageTable devices={devices} />
<TabsTrigger value="sessions">Sessions</TabsTrigger>
<TabsTrigger value="devices">Devices</TabsTrigger>
</TabsList>
<TabsContent value="sessions" className="pt-4">
<SessionsSummaryCards summary={sessionsSummary} />
</TabsContent>
<TabsContent value="devices" className="pt-4">
<DeviceUsageTable devices={devices} />
</TabsContent>
</Tabs>
</div> </div>
); );
} }
+27 -45
View File
@@ -5,7 +5,6 @@ export interface SessionsSummary {
totalDurationMs: number; totalDurationMs: number;
avgDurationMs: number; avgDurationMs: number;
totalReplays: number; totalReplays: number;
durationPerDevice: { deviceId: number; displayName: string | null; bleName: string; totalActiveMs: number }[];
} }
function formatHours(ms: number): string { function formatHours(ms: number): string {
@@ -18,50 +17,33 @@ function formatReplays(count: number): string {
export function SessionsSummaryCards({ summary }: { summary: SessionsSummary }) { export function SessionsSummaryCards({ summary }: { summary: SessionsSummary }) {
return ( return (
<div className="flex flex-col gap-4"> <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <Card className="bp-glass">
<Card className="bp-glass"> <CardHeader>
<CardHeader> <CardTitle className="text-sm text-muted-foreground">Completed sessions</CardTitle>
<CardTitle className="text-sm text-muted-foreground">Completed sessions</CardTitle> </CardHeader>
</CardHeader> <CardContent className="bp-readout text-3xl">{summary.count}</CardContent>
<CardContent className="bp-readout text-3xl">{summary.count}</CardContent> </Card>
</Card> <Card className="bp-glass">
<Card className="bp-glass"> <CardHeader>
<CardHeader> <CardTitle className="text-sm text-muted-foreground">Total time</CardTitle>
<CardTitle className="text-sm text-muted-foreground">Total time</CardTitle> </CardHeader>
</CardHeader> <CardContent className="bp-readout text-3xl">{formatHours(summary.totalDurationMs)}</CardContent>
<CardContent className="bp-readout text-3xl">{formatHours(summary.totalDurationMs)}</CardContent> </Card>
</Card> <Card className="bp-glass">
<Card className="bp-glass"> <CardHeader>
<CardHeader> <CardTitle className="text-sm text-muted-foreground">Avg session length</CardTitle>
<CardTitle className="text-sm text-muted-foreground">Avg session length</CardTitle> </CardHeader>
</CardHeader> <CardContent className="bp-readout text-3xl">
<CardContent className="bp-readout text-3xl"> {Math.round(summary.avgDurationMs / 60_000)}m
{Math.round(summary.avgDurationMs / 60_000)}m </CardContent>
</CardContent> </Card>
</Card> <Card className="bp-glass">
<Card className="bp-glass"> <CardHeader>
<CardHeader> <CardTitle className="text-sm text-muted-foreground">Replays</CardTitle>
<CardTitle className="text-sm text-muted-foreground">Replays</CardTitle> </CardHeader>
</CardHeader> <CardContent className="bp-readout text-3xl">{formatReplays(summary.totalReplays)}</CardContent>
<CardContent className="bp-readout text-3xl">{formatReplays(summary.totalReplays)}</CardContent> </Card>
</Card>
</div>
{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> </div>
); );
} }
+1 -12
View File
@@ -15,18 +15,7 @@ export async function getSessionsSummary() {
.from(sessions) .from(sessions)
.where(eq(sessions.status, "completed")); .where(eq(sessions.status, "completed"));
const durationPerDevice = await db return totals;
.select({
deviceId: devices.id,
displayName: devices.displayName,
bleName: devices.bleName,
totalActiveMs: sql<number>`coalesce(sum(coalesce(${sessionDevices.disconnectedAt}, ${sessionDevices.connectedAt}) - ${sessionDevices.connectedAt}), 0)`,
})
.from(sessionDevices)
.innerJoin(devices, eq(sessionDevices.deviceId, devices.id))
.groupBy(devices.id);
return { ...totals, durationPerDevice };
} }
export async function getSessionTimeline(sessionId: number, bucketMs = 1000) { export async function getSessionTimeline(sessionId: number, bucketMs = 1000) {