quiz reset
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
* 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
|
||||
* 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";
|
||||
@@ -18,6 +21,9 @@ import {
|
||||
type QuizResult,
|
||||
} from "@/lib/api";
|
||||
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 { Table, THead, TH, TR, TD } from "@/components/ui/table";
|
||||
import { RichText } from "@/lib/rich-text";
|
||||
@@ -31,6 +37,30 @@ export function Participants({ quizId }: { quizId: string }) {
|
||||
const [rows, setRows] = useState<QuizParticipant[] | null>(null);
|
||||
const [open, setOpen] = useState<QuizParticipant | 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(() => {
|
||||
quizzes
|
||||
@@ -70,6 +100,7 @@ export function Participants({ quizId }: { quizId: string }) {
|
||||
<TH>Progress</TH>
|
||||
<TH>Time</TH>
|
||||
<TH>Started</TH>
|
||||
{can("quizzes:reset") && <TH className="text-right">Actions</TH>}
|
||||
</TR>
|
||||
</THead>
|
||||
<tbody>
|
||||
@@ -118,6 +149,18 @@ export function Participants({ quizId }: { quizId: string }) {
|
||||
<TD className="text-xs text-muted-foreground">
|
||||
{fmtDate(p.started_at)}
|
||||
</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>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -744,4 +744,7 @@ export const quizzes = {
|
||||
call<QuizResult>(`/admin/quizzes/${id}/participants/${userId}`).then((r) => r.data),
|
||||
responses: (id: string) =>
|
||||
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" }),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user