From 82233c7d53b72de58a1211eb19eff043f4543e1a Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Tue, 27 Aug 2024 09:42:14 +0100 Subject: [PATCH] ENCOA-111: Change Modules Assignment Display --- src/dashboards/AssignmentCreator.tsx | 955 +++++++++++---------------- 1 file changed, 392 insertions(+), 563 deletions(-) diff --git a/src/dashboards/AssignmentCreator.tsx b/src/dashboards/AssignmentCreator.tsx index 7859b068..445193d9 100644 --- a/src/dashboards/AssignmentCreator.tsx +++ b/src/dashboards/AssignmentCreator.tsx @@ -1,593 +1,422 @@ import Input from "@/components/Low/Input"; import Modal from "@/components/Modal"; -import { Module } from "@/interfaces"; +import {Module} from "@/interfaces"; import clsx from "clsx"; -import { useEffect, useState } from "react"; -import { - BsBook, - BsCheckCircle, - BsClipboard, - BsHeadphones, - BsMegaphone, - BsPen, - BsXCircle, -} from "react-icons/bs"; -import { generate } from "random-words"; -import { capitalize } from "lodash"; +import {useEffect, useState} from "react"; +import {BsBook, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs"; +import {generate} from "random-words"; +import {capitalize} from "lodash"; import useUsers from "@/hooks/useUsers"; -import { Group, User } from "@/interfaces/user"; +import {Group, User} from "@/interfaces/user"; import ProgressBar from "@/components/Low/ProgressBar"; -import { calculateAverageLevel } from "@/utils/score"; +import {calculateAverageLevel} from "@/utils/score"; import Button from "@/components/Low/Button"; import ReactDatePicker from "react-datepicker"; import moment from "moment"; import axios from "axios"; -import { getExam } from "@/utils/exams"; -import { toast } from "react-toastify"; -import { Assignment } from "@/interfaces/results"; +import {getExam} from "@/utils/exams"; +import {toast} from "react-toastify"; +import {Assignment} from "@/interfaces/results"; import Checkbox from "@/components/Low/Checkbox"; -import { InstructorGender, Variant } from "@/interfaces/exam"; +import {InstructorGender, Variant} from "@/interfaces/exam"; import Select from "@/components/Low/Select"; import useExams from "@/hooks/useExams"; interface Props { - isCreating: boolean; - assigner: string; - users: User[]; - groups: Group[]; - assignment?: Assignment; - cancelCreation: () => void; + isCreating: boolean; + assigner: string; + users: User[]; + groups: Group[]; + assignment?: Assignment; + cancelCreation: () => void; } -export default function AssignmentCreator({ - isCreating, - assignment, - assigner, - groups, - users, - cancelCreation, -}: Props) { - const [selectedModules, setSelectedModules] = useState( - assignment?.exams.map((e) => e.module) || [] - ); - const [assignees, setAssignees] = useState( - assignment?.assignees || [] - ); - const [name, setName] = useState( - assignment?.name || - generate({ - minLength: 6, - maxLength: 8, - min: 2, - max: 3, - join: " ", - formatter: capitalize, - }) - ); - const [isLoading, setIsLoading] = useState(false); - const [startDate, setStartDate] = useState( - assignment ? moment(assignment.startDate).toDate() : new Date() - ); - const [endDate, setEndDate] = useState( - assignment - ? moment(assignment.endDate).toDate() - : moment().hours(23).minutes(59).add(8, "day").toDate() - ); - const [variant, setVariant] = useState("full"); - const [instructorGender, setInstructorGender] = useState( - assignment?.instructorGender || "varied" - ); - // creates a new exam for each assignee or just one exam for all assignees - const [generateMultiple, setGenerateMultiple] = useState(false); - const [useRandomExams, setUseRandomExams] = useState(true); - const [examIDs, setExamIDs] = useState<{ id: string; module: Module }[]>([]); +export default function AssignmentCreator({isCreating, assignment, assigner, groups, users, cancelCreation}: Props) { + const [selectedModules, setSelectedModules] = useState(assignment?.exams.map((e) => e.module) || []); + const [assignees, setAssignees] = useState(assignment?.assignees || []); + const [name, setName] = useState( + assignment?.name || + generate({ + minLength: 6, + maxLength: 8, + min: 2, + max: 3, + join: " ", + formatter: capitalize, + }), + ); + const [isLoading, setIsLoading] = useState(false); + const [startDate, setStartDate] = useState(assignment ? moment(assignment.startDate).toDate() : new Date()); + const [endDate, setEndDate] = useState( + assignment ? moment(assignment.endDate).toDate() : moment().hours(23).minutes(59).add(8, "day").toDate(), + ); + const [variant, setVariant] = useState("full"); + const [instructorGender, setInstructorGender] = useState(assignment?.instructorGender || "varied"); + // creates a new exam for each assignee or just one exam for all assignees + const [generateMultiple, setGenerateMultiple] = useState(false); + const [useRandomExams, setUseRandomExams] = useState(true); + const [examIDs, setExamIDs] = useState<{id: string; module: Module}[]>([]); - const { exams } = useExams(); + const {exams} = useExams(); - useEffect(() => { - setExamIDs((prev) => - prev.filter((x) => selectedModules.includes(x.module)) - ); - }, [selectedModules]); + useEffect(() => { + setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module))); + }, [selectedModules]); - const toggleModule = (module: Module) => { - const modules = selectedModules.filter((x) => x !== module); - setSelectedModules((prev) => - prev.includes(module) ? modules : [...modules, module] - ); - }; + const toggleModule = (module: Module) => { + const modules = selectedModules.filter((x) => x !== module); + setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module])); + }; - const toggleAssignee = (user: User) => { - setAssignees((prev) => - prev.includes(user.id) - ? prev.filter((a) => a !== user.id) - : [...prev, user.id] - ); - }; + const toggleAssignee = (user: User) => { + setAssignees((prev) => (prev.includes(user.id) ? prev.filter((a) => a !== user.id) : [...prev, user.id])); + }; - const createAssignment = () => { - setIsLoading(true); + const createAssignment = () => { + setIsLoading(true); - (assignment ? axios.patch : axios.post)( - `/api/assignments${assignment ? `/${assignment.id}` : ""}`, - { - assignees, - name, - startDate, - examIDs: !useRandomExams ? examIDs : undefined, - endDate, - selectedModules, - generateMultiple, - variant, - instructorGender, - } - ) - .then(() => { - toast.success( - `The assignment "${name}" has been ${ - assignment ? "updated" : "created" - } successfully!` - ); - cancelCreation(); - }) - .catch((e) => { - console.log(e); - toast.error("Something went wrong, please try again later!"); - }) - .finally(() => setIsLoading(false)); - }; - - const deleteAssignment = () => { - if (assignment) { - setIsLoading(true); - - if ( - !confirm( - `Are you sure you want to delete the "${assignment.name}" assignment?` - ) - ) - return; - axios - .delete(`api/assignments/${assignment.id}`) - .then(() => { - toast.success( - `The assignment "${name}" has been deleted successfully!` - ); - cancelCreation(); - }) - .catch((e) => { - console.log(e); - toast.error("Something went wrong, please try again later!"); - }) - .finally(() => setIsLoading(false)); - } - }; - - const startAssignment = () => { - if (assignment) { - setIsLoading(true); - - axios - .post(`/api/assignments/${assignment.id}/start`) - .then(() => { - toast.success( - `The assignment "${name}" has been started successfully!` - ); - cancelCreation(); + (assignment ? axios.patch : axios.post)(`/api/assignments${assignment ? `/${assignment.id}` : ""}`, { + assignees, + name, + startDate, + examIDs: !useRandomExams ? examIDs : undefined, + endDate, + selectedModules, + generateMultiple, + variant, + instructorGender, }) - .catch((e) => { - console.log(e); - toast.error("Something went wrong, please try again later!"); - }) - .finally(() => setIsLoading(false)); - } - } + .then(() => { + toast.success(`The assignment "${name}" has been ${assignment ? "updated" : "created"} successfully!`); + cancelCreation(); + }) + .catch((e) => { + console.log(e); + toast.error("Something went wrong, please try again later!"); + }) + .finally(() => setIsLoading(false)); + }; - return ( - -
-
-
toggleModule("reading") - : undefined - } - className={clsx( - "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", - "lg:col-span-2", - selectedModules.includes("reading") - ? "border-mti-purple-light" - : "border-mti-gray-platinum" - )} - > -
- -
- Reading - {!selectedModules.includes("reading") && - !selectedModules.includes("level") && ( -
- )} - {selectedModules.includes("level") && ( - - )} - {selectedModules.includes("reading") && ( - - )} -
-
toggleModule("listening") - : undefined - } - className={clsx( - "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", - "lg:col-span-2", - selectedModules.includes("listening") - ? "border-mti-purple-light" - : "border-mti-gray-platinum" - )} - > -
- -
- Listening - {!selectedModules.includes("listening") && - !selectedModules.includes("level") && ( -
- )} - {selectedModules.includes("level") && ( - - )} - {selectedModules.includes("listening") && ( - - )} -
-
toggleModule("writing") - : undefined - } - className={clsx( - "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", - "lg:col-span-2", - selectedModules.includes("writing") - ? "border-mti-purple-light" - : "border-mti-gray-platinum" - )} - > -
- -
- Writing - {!selectedModules.includes("writing") && - !selectedModules.includes("level") && ( -
- )} - {selectedModules.includes("level") && ( - - )} - {selectedModules.includes("writing") && ( - - )} -
-
toggleModule("speaking") - : undefined - } - className={clsx( - "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", - "lg:col-span-3", - selectedModules.includes("speaking") - ? "border-mti-purple-light" - : "border-mti-gray-platinum" - )} - > -
- -
- Speaking - {!selectedModules.includes("speaking") && - !selectedModules.includes("level") && ( -
- )} - {selectedModules.includes("level") && ( - - )} - {selectedModules.includes("speaking") && ( - - )} -
-
toggleModule("level") - : undefined - } - className={clsx( - "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", - "lg:col-span-3", - selectedModules.includes("level") - ? "border-mti-purple-light" - : "border-mti-gray-platinum" - )} - > -
- -
- Level - {!selectedModules.includes("level") && - selectedModules.length === 0 && ( -
- )} - {!selectedModules.includes("level") && - selectedModules.length > 0 && ( - - )} - {selectedModules.includes("level") && ( - - )} -
-
+ const deleteAssignment = () => { + if (assignment) { + setIsLoading(true); - setName(e)} - defaultValue={name} - label="Assignment Name" - required - /> + if (!confirm(`Are you sure you want to delete the "${assignment.name}" assignment?`)) return; + axios + .delete(`api/assignments/${assignment.id}`) + .then(() => { + toast.success(`The assignment "${name}" has been deleted successfully!`); + cancelCreation(); + }) + .catch((e) => { + console.log(e); + toast.error("Something went wrong, please try again later!"); + }) + .finally(() => setIsLoading(false)); + } + }; -
-
- - moment(date).isSameOrAfter(new Date())} - dateFormat="dd/MM/yyyy HH:mm" - selected={startDate} - showTimeSelect - onChange={(date) => setStartDate(date)} - /> -
-
- - moment(date).isAfter(startDate)} - dateFormat="dd/MM/yyyy HH:mm" - selected={endDate} - showTimeSelect - onChange={(date) => setEndDate(date)} - /> -
-
+ const startAssignment = () => { + if (assignment) { + setIsLoading(true); - {selectedModules.includes("speaking") && ( -
- - e.module === module)?.id || null, - label: - examIDs.find((e) => e.module === module)?.id || "", - }} - onChange={(value) => - value - ? setExamIDs((prev) => [ - ...prev.filter((x) => x.module !== module), - { id: value.value!, module }, - ]) - : setExamIDs((prev) => - prev.filter((x) => x.module !== module) - ) - } - options={exams - .filter((x) => !x.isDiagnostic && x.module === module) - .map((x) => ({ value: x.id, label: x.id }))} - /> -
- ))} -
- )} - - )} + return ( + +
+
+
toggleModule("reading") : undefined} + className={clsx( + "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", + selectedModules.includes("reading") ? "border-mti-purple-light" : "border-mti-gray-platinum", + )}> +
+ +
+ Reading + {!selectedModules.includes("reading") && !selectedModules.includes("level") && ( +
+ )} + {selectedModules.includes("level") && } + {selectedModules.includes("reading") && } +
+
toggleModule("listening") : undefined} + className={clsx( + "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", + selectedModules.includes("listening") ? "border-mti-purple-light" : "border-mti-gray-platinum", + )}> +
+ +
+ Listening + {!selectedModules.includes("listening") && !selectedModules.includes("level") && ( +
+ )} + {selectedModules.includes("level") && } + {selectedModules.includes("listening") && } +
+
toggleModule("level") + : undefined + } + className={clsx( + "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", + selectedModules.includes("level") ? "border-mti-purple-light" : "border-mti-gray-platinum", + )}> +
+ +
+ Level + {!selectedModules.includes("level") && selectedModules.length === 0 && ( +
+ )} + {!selectedModules.includes("level") && selectedModules.length > 0 && } + {selectedModules.includes("level") && } +
+
toggleModule("writing") : undefined} + className={clsx( + "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", + selectedModules.includes("writing") ? "border-mti-purple-light" : "border-mti-gray-platinum", + )}> +
+ +
+ Writing + {!selectedModules.includes("writing") && !selectedModules.includes("level") && ( +
+ )} + {selectedModules.includes("level") && } + {selectedModules.includes("writing") && } +
+
toggleModule("speaking") : undefined} + className={clsx( + "w-52 relative max-w-xs flex items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-8 cursor-pointer", + selectedModules.includes("speaking") ? "border-mti-purple-light" : "border-mti-gray-platinum", + )}> +
+ +
+ Speaking + {!selectedModules.includes("speaking") && !selectedModules.includes("level") && ( +
+ )} + {selectedModules.includes("level") && } + {selectedModules.includes("speaking") && } +
+
-
- - Assignees ({assignees.length} selected) - -
- {groups.map((g) => ( - - ))} -
-
- {users.map((user) => ( -
toggleAssignee(user)} - className={clsx( - "p-4 flex flex-col gap-2 rounded-xl border cursor-pointer w-72", - "transition ease-in-out duration-300", - assignees.includes(user.id) - ? "border-mti-purple" - : "border-mti-gray-platinum" - )} - key={user.id} - > - - {user.name} - {user.email} - - - - Groups:{" "} - {groups - .filter((g) => g.participants.includes(user.id)) - .map((g) => g.name) - .join(", ")} - -
- ))} -
-
-
- - setVariant((prev) => (prev === "full" ? "partial" : "full")) - } - > - Full length exams - - setGenerateMultiple((d) => !d)} - > - Generate different exams - -
-
- - {assignment && ( - <> - - - - )} - -
-
-
- ); + setName(e)} defaultValue={name} label="Assignment Name" required /> + +
+
+ + moment(date).isSameOrAfter(new Date())} + dateFormat="dd/MM/yyyy HH:mm" + selected={startDate} + showTimeSelect + onChange={(date) => setStartDate(date)} + /> +
+
+ + moment(date).isAfter(startDate)} + dateFormat="dd/MM/yyyy HH:mm" + selected={endDate} + showTimeSelect + onChange={(date) => setEndDate(date)} + /> +
+
+ + {selectedModules.includes("speaking") && ( +
+ + e.module === module)?.id || null, + label: examIDs.find((e) => e.module === module)?.id || "", + }} + onChange={(value) => + value + ? setExamIDs((prev) => [...prev.filter((x) => x.module !== module), {id: value.value!, module}]) + : setExamIDs((prev) => prev.filter((x) => x.module !== module)) + } + options={exams + .filter((x) => !x.isDiagnostic && x.module === module) + .map((x) => ({value: x.id, label: x.id}))} + /> +
+ ))} + + )} + + )} + +
+ Assignees ({assignees.length} selected) +
+ {groups.map((g) => ( + + ))} +
+
+ {users.map((user) => ( +
toggleAssignee(user)} + className={clsx( + "p-4 flex flex-col gap-2 rounded-xl border cursor-pointer w-72", + "transition ease-in-out duration-300", + assignees.includes(user.id) ? "border-mti-purple" : "border-mti-gray-platinum", + )} + key={user.id}> + + {user.name} + {user.email} + + + + Groups:{" "} + {groups + .filter((g) => g.participants.includes(user.id)) + .map((g) => g.name) + .join(", ")} + +
+ ))} +
+
+
+ setVariant((prev) => (prev === "full" ? "partial" : "full"))}> + Full length exams + + setGenerateMultiple((d) => !d)}> + Generate different exams + +
+
+ + {assignment && ( + <> + + + + )} + +
+ +
+ ); }