diff --git a/app/(app)/sessions/[id]/page.tsx b/app/(app)/sessions/[id]/page.tsx index afc16e8..d252da0 100644 --- a/app/(app)/sessions/[id]/page.tsx +++ b/app/(app)/sessions/[id]/page.tsx @@ -6,6 +6,7 @@ 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 { SessionDescriptionEditor } from "@/components/sessions/SessionDescriptionEditor"; import { STATUS_VARIANT } from "@/components/sessions/SessionsTable"; import { getSessionDetail, getSessionName } from "@/lib/db/queries/sessions"; import { getSessionTimeline } from "@/lib/db/queries/stats"; @@ -52,9 +53,7 @@ export default async function SessionDetailPage({ params }: { params: Promise<{ )}

)} - {detail.session.description && ( -

{detail.session.description}

- )} +
{detail.session.status} diff --git a/components/sessions/SessionDescriptionEditor.tsx b/components/sessions/SessionDescriptionEditor.tsx new file mode 100644 index 0000000..413df18 --- /dev/null +++ b/components/sessions/SessionDescriptionEditor.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Check, Loader2 } from "lucide-react"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + +const AUTOSAVE_DELAY_MS = 600; + +export function SessionDescriptionEditor({ + sessionId, + initialDescription, +}: { + sessionId: number; + initialDescription: string | null; +}) { + const router = useRouter(); + const initial = initialDescription ?? ""; + const [value, setValue] = useState(initial); + const [status, setStatus] = useState<"idle" | "saving" | "saved">("idle"); + const lastSaved = useRef(initial); + + useEffect(() => { + // Unlike the session name, an empty description is a valid, saveable + // state (it just means "no description"), so no non-empty guard here. + if (value === lastSaved.current) return; + + setStatus("saving"); + const timer = setTimeout(async () => { + const res = await fetch(`/api/sessions/${sessionId}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ description: value }), + }); + if (res.ok) { + lastSaved.current = value; + setStatus("saved"); + router.refresh(); + } else { + setStatus("idle"); + toast.error("Could not save description"); + } + }, AUTOSAVE_DELAY_MS); + + return () => clearTimeout(timer); + }, [value, sessionId, router]); + + useEffect(() => { + if (status !== "saved") return; + const timer = setTimeout(() => setStatus("idle"), 1500); + return () => clearTimeout(timer); + }, [status]); + + return ( +
+