113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* /quizzes — every quiz, with how many people have started and finished it.
|
|
*/
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { toast } from "sonner";
|
|
import { quizzes, AdminError, type Quiz } from "@/lib/api";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Table, THead, TH, TR, TD } from "@/components/ui/table";
|
|
import { Can } from "@/components/staff-context";
|
|
import { fmtDate } from "@/lib/utils";
|
|
import { KIND_LABEL } from "./labels";
|
|
import { Clamp } from "@/components/ui/clamp";
|
|
|
|
export default function QuizzesPage() {
|
|
const [list, setList] = useState<Quiz[] | null>(null);
|
|
|
|
useEffect(() => {
|
|
quizzes
|
|
.list()
|
|
.then(setList)
|
|
.catch((err) => {
|
|
toast.error(
|
|
err instanceof AdminError ? err.message : "Could not load quizzes",
|
|
);
|
|
setList([]);
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<header className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-xl font-semibold text-foreground">Quizzes</h1>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
Quizzes on book chapters, lectures and conversations — or on their
|
|
own. A published quiz shows a button on its page and appears in the
|
|
app's quiz list.
|
|
</p>
|
|
</div>
|
|
<Can perm="quizzes:write">
|
|
<Link href="/quizzes/new" className="shrink-0">
|
|
<Button className="whitespace-nowrap">New quiz</Button>
|
|
</Link>
|
|
</Can>
|
|
</header>
|
|
|
|
{list === null ? (
|
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
) : list.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">No quizzes yet.</p>
|
|
) : (
|
|
<Table>
|
|
<THead>
|
|
<TR>
|
|
<TH>Quiz</TH>
|
|
<TH>Attached to</TH>
|
|
<TH>Questions</TH>
|
|
<TH>Taken</TH>
|
|
<TH>Status</TH>
|
|
</TR>
|
|
</THead>
|
|
<tbody>
|
|
{list.map((q) => (
|
|
<TR key={q.id}>
|
|
<TD>
|
|
<Link
|
|
href={`/quizzes/${q.id}`}
|
|
className="block font-medium text-foreground hover:underline"
|
|
>
|
|
<Clamp lines={2}>{q.title}</Clamp>
|
|
</Link>
|
|
<p className="text-xs text-muted-foreground">
|
|
Updated {fmtDate(q.updated_at)}
|
|
</p>
|
|
</TD>
|
|
<TD>
|
|
<p className="text-sm">{KIND_LABEL[q.target_kind]}</p>
|
|
{q.target_title && (
|
|
<Clamp className="text-xs text-muted-foreground">
|
|
{q.target_title}
|
|
</Clamp>
|
|
)}
|
|
</TD>
|
|
<TD>{q.question_count}</TD>
|
|
<TD className="[overflow-wrap:normal]">
|
|
{q.completed_count ?? 0} finished
|
|
<span className="text-muted-foreground">
|
|
{" "}
|
|
/ {q.attempt_count ?? 0} started
|
|
</span>
|
|
</TD>
|
|
<TD>
|
|
{q.published_at ? (
|
|
<span className="rounded bg-primary/10 px-2 py-0.5 text-xs text-primary whitespace-nowrap">
|
|
Published
|
|
</span>
|
|
) : (
|
|
<span className="text-xs text-muted-foreground">Draft</span>
|
|
)}
|
|
</TD>
|
|
</TR>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|