feat: quiz
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"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";
|
||||
|
||||
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">
|
||||
<Button>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="font-medium text-foreground hover:underline"
|
||||
>
|
||||
{q.title}
|
||||
</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 && (
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{q.target_title}
|
||||
</p>
|
||||
)}
|
||||
</TD>
|
||||
<TD>{q.question_count}</TD>
|
||||
<TD>
|
||||
{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">
|
||||
Published
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">Draft</span>
|
||||
)}
|
||||
</TD>
|
||||
</TR>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user