quiz reset

This commit is contained in:
gosangam
2026-10-15 20:59:13 +05:30
parent 111485a43e
commit fcbffead8a
2 changed files with 46 additions and 0 deletions
+43
View File
@@ -6,6 +6,9 @@
* Response times are measured by the BFF — from the moment a question was * Response times are measured by the BFF — from the moment a question was
* served to the moment its answer arrived — so they can't be massaged by the * served to the moment its answer arrived — so they can't be massaged by the
* browser. They include any time the person spent away from the screen. * browser. They include any time the person spent away from the screen.
*
* Staff with quizzes:reset can delete one person's attempt, which puts the
* quiz back to pending for them so they can take it again.
*/ */
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -18,6 +21,9 @@ import {
type QuizResult, type QuizResult,
} from "@/lib/api"; } from "@/lib/api";
import { Avatar } from "@/components/ui/avatar"; import { Avatar } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { useConfirm } from "@/components/confirm";
import { Can, useStaff } from "@/components/staff-context";
import { Modal } from "@/components/ui/modal"; import { Modal } from "@/components/ui/modal";
import { Table, THead, TH, TR, TD } from "@/components/ui/table"; import { Table, THead, TH, TR, TD } from "@/components/ui/table";
import { RichText } from "@/lib/rich-text"; import { RichText } from "@/lib/rich-text";
@@ -31,6 +37,30 @@ export function Participants({ quizId }: { quizId: string }) {
const [rows, setRows] = useState<QuizParticipant[] | null>(null); const [rows, setRows] = useState<QuizParticipant[] | null>(null);
const [open, setOpen] = useState<QuizParticipant | null>(null); const [open, setOpen] = useState<QuizParticipant | null>(null);
const [detail, setDetail] = useState<QuizResult | null>(null); const [detail, setDetail] = useState<QuizResult | null>(null);
const [resetting, setResetting] = useState<string | null>(null);
const confirm = useConfirm();
const { can } = useStaff();
const reset = async (p: QuizParticipant) => {
const ok = await confirm({
title: `Reset ${p.name}'s attempt?`,
message:
"Their answers and score are deleted and the quiz becomes pending for them, so they can take it again. This can't be undone.",
confirmLabel: "Reset attempt",
destructive: true,
});
if (!ok) return;
setResetting(p.user_id);
try {
await quizzes.resetParticipant(quizId, p.user_id);
setRows((rs) => rs?.filter((r) => r.user_id !== p.user_id) ?? rs);
toast.success(`${p.name}'s attempt was reset`);
} catch (err) {
toast.error(errMsg(err, "Could not reset the attempt"));
} finally {
setResetting(null);
}
};
useEffect(() => { useEffect(() => {
quizzes quizzes
@@ -70,6 +100,7 @@ export function Participants({ quizId }: { quizId: string }) {
<TH>Progress</TH> <TH>Progress</TH>
<TH>Time</TH> <TH>Time</TH>
<TH>Started</TH> <TH>Started</TH>
{can("quizzes:reset") && <TH className="text-right">Actions</TH>}
</TR> </TR>
</THead> </THead>
<tbody> <tbody>
@@ -118,6 +149,18 @@ export function Participants({ quizId }: { quizId: string }) {
<TD className="text-xs text-muted-foreground"> <TD className="text-xs text-muted-foreground">
{fmtDate(p.started_at)} {fmtDate(p.started_at)}
</TD> </TD>
<Can perm="quizzes:reset">
<TD className="text-right">
<Button
size="sm"
variant="ghost"
disabled={resetting === p.user_id}
onClick={() => void reset(p)}
>
{resetting === p.user_id ? "Resetting…" : "Reset"}
</Button>
</TD>
</Can>
</TR> </TR>
))} ))}
</tbody> </tbody>
+3
View File
@@ -744,4 +744,7 @@ export const quizzes = {
call<QuizResult>(`/admin/quizzes/${id}/participants/${userId}`).then((r) => r.data), call<QuizResult>(`/admin/quizzes/${id}/participants/${userId}`).then((r) => r.data),
responses: (id: string) => responses: (id: string) =>
call<QuizQuestionStat[]>(`/admin/quizzes/${id}/responses`).then((r) => r.data), call<QuizQuestionStat[]>(`/admin/quizzes/${id}/responses`).then((r) => r.data),
/** Deletes one person's attempt; the quiz is pending for them again. */
resetParticipant: (id: string, userId: string) =>
call<void>(`/admin/quizzes/${id}/participants/${userId}`, { method: "DELETE" }),
}; };