All files / components/Scaffold UnlockAssessmentsModal.tsx

100% Statements 8/8
80% Branches 4/5
100% Functions 5/5
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                              171x               171x       3x             1x           171x       171x                             16x                     3x                                    
import Modal from "react-bootstrap/Modal";
import { Form } from "react-bootstrap";
import { toast } from "react-toastify";
import { useBackend, useBackendMutation } from "main/utils/useBackend";
import type { AssessmentManagementDTO } from "main/types/conceptGraph";
import PrairieLearnAssessment from "main/components/Scaffold/PrairieLearnAssessment";
import { compareByAssessmentSetAndNumber } from "main/utils/conceptGraphUtils";
 
interface UnlockAssessmentsModalProps {
  show: boolean;
  onHide: () => void;
  courseId: number;
}
 
// Lets an instructor see every assessment for the course (locked and unlocked) and toggle
// which ones are visible to students in the AssessmentSelect dropdown. Self-contained: owns
// its own fetch (only while open) and its own mutation, since the trigger button and this
// modal are siblings in ScaffoldTopBar rather than needing state lifted to ConceptGraphPage.
export default function UnlockAssessmentsModal({
  show,
  onHide,
  courseId,
}: UnlockAssessmentsModalProps) {
  const { data: assessments = [] } = useBackend<AssessmentManagementDTO[]>(
    ["/api/assessments/all", courseId],
    { method: "GET", url: "/api/assessments/all", params: { courseId } },
    [],
    false,
    { enabled: show },
  );
 
  const setLockedMutation = useBackendMutation<
    { assessmentId: string; locked: boolean },
    AssessmentManagementDTO
  >(
    ({ assessmentId, locked }) => ({
      method: "PUT",
      url: "/api/assessments/lock",
      params: { courseId, assessmentId, locked },
    }),
    {
      onError: (_error, { locked }) => {
        toast(`Failed to ${locked ? "lock" : "unlock"} assessment`);
      },
    },
    ["/api/assessments/all", "/api/assessments"],
  );
 
  const sortedAssessments = [...assessments].sort(
    compareByAssessmentSetAndNumber,
  );
 
  return (
    <Modal
      show={show}
      onHide={onHide}
      centered={true}
      data-testid="UnlockAssessmentsModal"
    >
      <Modal.Header closeButton>Unlock Assessments</Modal.Header>
      <Modal.Body>
        {assessments.length === 0 ? (
          <Form.Text data-testid="UnlockAssessmentsModal-empty">
            No assessments are available for this course yet.
          </Form.Text>
        ) : (
          sortedAssessments.map((assessment) => (
            <Form.Check
              key={assessment.id}
              type="switch"
              id={`unlock-assessment-${assessment.id}`}
              className="mb-2"
            >
              <Form.Check.Input
                type="checkbox"
                data-testid={`UnlockAssessmentsModal-switch-${assessment.id}`}
                checked={!assessment.locked}
                onChange={(e) =>
                  setLockedMutation.mutate({
                    assessmentId: assessment.id,
                    locked: !e.target.checked,
                  })
                }
              />
              <Form.Check.Label
                data-testid={`UnlockAssessmentsModal-label-${assessment.id}`}
              >
                <PrairieLearnAssessment assessment={assessment} />
              </Form.Check.Label>
            </Form.Check>
          ))
        )}
      </Modal.Body>
    </Modal>
  );
}