import Button from "@/components/Low/Button"; import Checkbox from "@/components/Low/Checkbox"; import Input from "@/components/Low/Input"; import ProgressBar from "@/components/Low/ProgressBar"; import Select from "@/components/Low/Select"; import Separator from "@/components/Low/Separator"; import useExams from "@/hooks/useExams"; import { useListSearch } from "@/hooks/useListSearch"; import usePagination from "@/hooks/usePagination"; import { Module } from "@/interfaces"; import { EntityWithRoles } from "@/interfaces/entity"; import { InstructorGender, Variant } from "@/interfaces/exam"; import { Assignment } from "@/interfaces/results"; import { Group, User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import { mapBy, redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { getAssignment } from "@/utils/assignments.be"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { getGroups, getGroupsByEntities } from "@/utils/groups.be"; import { checkAccess, doesEntityAllow, findAllowedEntities, } from "@/utils/permissions"; import { calculateAverageLevel } from "@/utils/score"; import { getEntitiesUsers, getUsers } from "@/utils/users.be"; import axios from "axios"; import clsx from "clsx"; import { withIronSessionSsr } from "iron-session/next"; import { capitalize } from "lodash"; import moment from "moment"; import Head from "next/head"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useMemo, useState } from "react"; import ReactDatePicker from "react-datepicker"; import { BsBook, BsCheckCircle, BsChevronLeft, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle, } from "react-icons/bs"; import { toast } from "react-toastify"; export const getServerSideProps = withIronSessionSsr( async ({ req, res, params }) => { const user = await requestUser(req, res); if (!user) return redirect("/login"); res.setHeader( "Cache-Control", "public, s-maxage=10, stale-while-revalidate=59" ); const { id } = params as { id: string }; const entityIDS = mapBy(user.entities, "id") || []; const isAdmin = checkAccess(user, ["developer", "admin"]); const [assignment, entities] = await Promise.all([ getAssignment(id), getEntitiesWithRoles(isAdmin ? undefined : entityIDS), ]); if (!assignment) return redirect("/assignments"); const entity = entities.find((e) => e.id === assignment.entity); if (!entity) return redirect("/assignments"); if (!doesEntityAllow(user, entity, "edit_assignment")) return redirect("/assignments"); const allowedEntities = findAllowedEntities( user, entities, "edit_assignment" ); const allowEntitiesIDs = mapBy(allowedEntities, "id"); const [users, groups] = await Promise.all([ isAdmin ? getUsers( {}, 0, {}, { _id: 0, id: 1, type: 1, name: 1, email: 1, levels: 1, } ) : getEntitiesUsers(allowEntitiesIDs, {}, 0, { _id: 0, id: 1, type: 1, name: 1, email: 1, levels: 1, }), isAdmin ? getGroups() : getGroupsByEntities(allowEntitiesIDs), ]); return { props: serialize({ user, users, entities: allowedEntities, assignment, groups, }), }; }, sessionOptions ); interface Props { assignment: Assignment; groups: Group[]; user: User; users: User[]; entities: EntityWithRoles[]; } const SIZE = 9; export default function AssignmentsPage({ assignment, user, users, entities, groups, }: Props) { const [selectedModules, setSelectedModules] = useState( assignment.exams.map((e) => e.module) ); const [assignees, setAssignees] = useState(assignment.assignees); const [teachers, setTeachers] = useState(assignment.teachers || []); const [entity, setEntity] = useState( assignment.entity || entities[0]?.id ); const [name, setName] = useState(assignment.name); const [isLoading, setIsLoading] = useState(false); const [startDate, setStartDate] = useState( moment(assignment.startDate).toDate() ); const [endDate, setEndDate] = useState( moment(assignment.endDate).toDate() ); const [variant, setVariant] = useState("full"); const [instructorGender, setInstructorGender] = useState( assignment?.instructorGender || "varied" ); const [generateMultiple, setGenerateMultiple] = useState(false); const [released, setReleased] = useState( assignment.released || false ); const [autoStart, setAutostart] = useState( assignment.autoStart || false ); const [useRandomExams, setUseRandomExams] = useState(true); const [examIDs, setExamIDs] = useState<{ id: string; module: Module }[]>([]); const { exams } = useExams(); const router = useRouter(); const classrooms = useMemo( () => groups.filter((e) => e.entity === entity), [entity, groups] ); const userStudents = useMemo( () => users.filter((x) => x.type === "student"), [users] ); const userTeachers = useMemo( () => users.filter((x) => x.type === "teacher"), [users] ); const { rows: filteredStudentsRows, renderSearch: renderStudentSearch } = useListSearch([["name"], ["email"]], userStudents); const { rows: filteredTeachersRows, renderSearch: renderTeacherSearch } = useListSearch([["name"], ["email"]], userTeachers); const { items: studentRows, renderMinimal: renderStudentPagination } = usePagination(filteredStudentsRows, SIZE); const { items: teacherRows, renderMinimal: renderTeacherPagination } = usePagination(filteredTeachersRows, SIZE); useEffect(() => { setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module)) ); }, [selectedModules]); useEffect(() => { setAssignees([]); setTeachers([]); }, [entity]); 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 toggleTeacher = (user: User) => { setTeachers((prev) => prev.includes(user.id) ? prev.filter((a) => a !== user.id) : [...prev, user.id] ); }; const createAssignment = () => { setIsLoading(true); (assignment ? axios.patch : axios.post)( `/api/assignments/${assignment.id}`, { assignees, name, startDate, examIDs: !useRandomExams ? examIDs : undefined, endDate, selectedModules, generateMultiple, entity, teachers, variant, instructorGender, released, autoStart, } ) .then(() => { toast.success( `The assignment "${name}" has been updated successfully!` ); router.push(`/assignments/${assignment.id}`); }) .catch((e) => { console.log(e); toast.error("Something went wrong, please try again later!"); }) .finally(() => setIsLoading(false)); }; const deleteAssignment = () => { if ( !confirm( `Are you sure you want to delete the "${assignment.name}" assignment?` ) ) return; console.log("GOT HERE"); setIsLoading(true); axios .delete(`/api/assignments/${assignment.id}`) .then(() => { toast.success( `The assignment "${name}" has been deleted successfully!` ); router.push("/assignments"); }) .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!` ); router.push(`/assignments/${assignment.id}`); }) .catch((e) => { console.log(e); toast.error("Something went wrong, please try again later!"); }) .finally(() => setIsLoading(false)); } }; const copyLink = async () => { const origin = window.location.origin; await navigator.clipboard.writeText( `${origin}/exam?assignment=${assignment.id}` ); toast.success( "The URL to the assignment has been copied to your clipboard!" ); }; return ( <> Edit {assignment.name} | EnCoach <>

Edit {assignment.name}

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") && ( )}
setName(e)} defaultValue={name} label="Assignment Name" required /> value ? setInstructorGender(value.value as InstructorGender) : null } disabled={!selectedModules.includes("speaking") || !!assignment} options={[ { value: "male", label: "Male" }, { value: "female", label: "Female" }, { value: "varied", label: "Varied" }, ]} />
)} {selectedModules.length > 0 && (
Random Exams {!useRandomExams && (
{selectedModules.map((module) => (