Files
sexy/app/(app)/sessions/[id]/page.tsx
T
valknarandClaude Sonnet 5 e9d5bb37d2 Show session status as a badge on the detail page
Reuses SessionsTable's STATUS_VARIANT mapping (now exported) so the
detail page's status matches the table's badge instead of plain text.
Also drops the now-stale px-1.5 alignment padding on the meta lines
now that the title input carries no horizontal padding of its own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzyfRyA7h5rs5SCAahLAWd
2026-08-30 21:03:00 +02:00

92 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { SessionTimelineChart } from "@/components/sessions/SessionTimelineChart";
import { SessionTitleEditor } from "@/components/sessions/SessionTitleEditor";
import { STATUS_VARIANT } from "@/components/sessions/SessionsTable";
import { getSessionDetail, getSessionName } from "@/lib/db/queries/sessions";
import { getSessionTimeline } from "@/lib/db/queries/stats";
import { Play } from "lucide-react";
export const dynamic = "force-dynamic";
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const { id } = await params;
const name = await getSessionName(Number(id));
return { title: name ?? `Session #${id}` };
}
function formatDuration(ms: number | null): string {
if (ms === null) return "-";
const totalSeconds = Math.round(ms / 1000);
return `${Math.floor(totalSeconds / 60)}:${(totalSeconds % 60).toString().padStart(2, "0")}`;
}
export default async function SessionDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const sessionId = Number(id);
const [detail, timeline] = await Promise.all([getSessionDetail(sessionId), getSessionTimeline(sessionId)]);
if (!detail) notFound();
const canReplay = detail.session.status === "completed" && detail.session.durationMs !== null;
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-4">
<div className="min-w-0 flex-1">
<SessionTitleEditor sessionId={detail.session.id} initialName={detail.session.name} />
<p className="mt-1 flex flex-wrap items-center gap-x-1 text-sm text-muted-foreground">
<Badge variant={STATUS_VARIANT[detail.session.status]}>{detail.session.status}</Badge>
· {formatDuration(detail.session.durationMs)}
{detail.session.kind === "replay" && detail.replayedFromName !== undefined && (
<> · replayed from {detail.replayedFromName ?? `session #${detail.session.replayedSessionId}`}</>
)}
{detail.session.playCount > 0 && (
<>
{" "}
· replayed {detail.session.playCount}×
{detail.session.lastPlayedAt && ` (last ${new Date(detail.session.lastPlayedAt).toLocaleString()})`}
</>
)}
</p>
{detail.session.description && (
<p className="mt-1 text-sm text-muted-foreground">{detail.session.description}</p>
)}
</div>
{canReplay && (
<Button asChild>
<Link href={`/sessions/${detail.session.id}/replay`}>
<Play className="size-4" /> Replay
</Link>
</Button>
)}
</div>
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-base">Intensity timeline</CardTitle>
</CardHeader>
<CardContent>
<SessionTimelineChart timeline={timeline} />
</CardContent>
</Card>
<Card className="bp-glass">
<CardHeader>
<CardTitle className="text-base">Devices</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-1">
{detail.devices.map((d) => (
<div key={d.id} className="text-sm">
{d.slotLabel} <span className="text-muted-foreground">({d.deviceDisplayName ?? d.deviceBleName})</span>
</div>
))}
</CardContent>
</Card>
</div>
);
}