quiz reset
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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" }),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user