diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts index fa2a7384..a74185b4 100644 --- a/src/interfaces/exam.ts +++ b/src/interfaces/exam.ts @@ -12,6 +12,7 @@ interface ExamBase { isDiagnostic: boolean; variant?: Variant; difficulty?: Difficulty; + owners?: string[]; shuffle?: boolean; createdBy?: string; // option as it has been added later createdAt?: string; // option as it has been added later diff --git a/src/pages/(admin)/Lists/ExamList.tsx b/src/pages/(admin)/Lists/ExamList.tsx index 2c990677..fbb4bd1f 100644 --- a/src/pages/(admin)/Lists/ExamList.tsx +++ b/src/pages/(admin)/Lists/ExamList.tsx @@ -1,4 +1,4 @@ -import {useMemo} from "react"; +import {useMemo, useState} from "react"; import {PERMISSIONS} from "@/constants/userPermissions"; import useExams from "@/hooks/useExams"; import useUsers from "@/hooks/useUsers"; @@ -11,11 +11,15 @@ import {countExercises} from "@/utils/moduleUtils"; import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import axios from "axios"; import clsx from "clsx"; -import {capitalize} from "lodash"; +import {capitalize, uniq} from "lodash"; import {useRouter} from "next/router"; -import {BsBan, BsBanFill, BsCheck, BsCircle, BsStop, BsTrash, BsUpload, BsX} from "react-icons/bs"; +import {BsBan, BsBanFill, BsCheck, BsCircle, BsPencil, BsStop, BsTrash, BsUpload, BsX} from "react-icons/bs"; import {toast} from "react-toastify"; import {useListSearch} from "@/hooks/useListSearch"; +import Modal from "@/components/Modal"; +import {checkAccess} from "@/utils/permissions"; +import useGroups from "@/hooks/useGroups"; +import Button from "@/components/Low/Button"; const searchFields = [["module"], ["id"], ["createdBy"]]; @@ -29,9 +33,40 @@ const CLASSES: {[key in Module]: string} = { const columnHelper = createColumnHelper(); +const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam; onSave: (owners: string[]) => void}) => { + const [owners, setOwners] = useState(exam.owners || []); + + return ( +
+
+ {options.map((c) => ( + + ))} +
+ +
+ ); +}; + export default function ExamList({user}: {user: User}) { + const [selectedExam, setSelectedExam] = useState(); + const {exams, reload} = useExams(); const {users} = useUsers(); + const {groups} = useGroups({admin: user?.id, userType: user?.type}); + + const filteredCorporates = useMemo(() => { + const participantsAndAdmins = uniq(groups.flatMap((x) => [...x.participants, x.admin])).filter((x) => x !== user?.id); + return users.filter((x) => participantsAndAdmins.includes(x.id) && x.type === "corporate"); + }, [users, groups, user]); const parsedExams = useMemo(() => { return exams.map((exam) => { @@ -94,6 +129,29 @@ export default function ExamList({user}: {user: User}) { .finally(reload); }; + const updateExam = async (exam: Exam, body: object) => { + if (!confirm(`Are you sure you want to update this ${capitalize(exam.module)} exam?`)) return; + + axios + .patch(`/api/exam/${exam.module}/${exam.id}`, body) + .then(() => toast.success(`Updated the "${exam.id}" exam`)) + .catch((reason) => { + if (reason.response.status === 404) { + toast.error("Exam not found!"); + return; + } + + if (reason.response.status === 403) { + toast.error("You do not have permission to update this exam!"); + return; + } + + toast.error("Something went wrong, please try again later."); + }) + .finally(reload) + .finally(() => setSelectedExam(undefined)); + }; + const deleteExam = async (exam: Exam) => { if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return; @@ -166,12 +224,21 @@ export default function ExamList({user}: {user: User}) { cell: ({row}: {row: {original: Exam}}) => { return (
- + {(row.original.owners?.includes(user.id) || checkAccess(user, ["admin", "developer"])) && ( + <> + + {checkAccess(user, ["admin", "developer", "mastercorporate"]) && ( + + )} + + )}