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:
@@ -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">
|
|
||||||
<TabsList className="bp-glass">
|
|
||||||
<TabsTrigger value="sessions">Sessions</TabsTrigger>
|
|
||||||
<TabsTrigger value="devices">Devices</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="sessions" className="pt-4">
|
|
||||||
<SessionsSummaryCards summary={sessionsSummary} />
|
<SessionsSummaryCards summary={sessionsSummary} />
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="devices" className="pt-4">
|
|
||||||
<DeviceUsageTable devices={devices} />
|
<DeviceUsageTable devices={devices} />
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +17,6 @@ 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>
|
||||||
@@ -47,21 +45,5 @@ export function SessionsSummaryCards({ summary }: { summary: SessionsSummary })
|
|||||||
<CardContent className="bp-readout text-3xl">{formatReplays(summary.totalReplays)}</CardContent>
|
<CardContent className="bp-readout text-3xl">{formatReplays(summary.totalReplays)}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-12
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user