/* eslint-disable @next/next/no-img-element */ import Head from "next/head"; import { withIronSessionSsr } from "iron-session/next"; import { sessionOptions } from "@/lib/session"; import { ToastContainer } from "react-toastify"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { Radio, RadioGroup } from "@headlessui/react"; import clsx from "clsx"; import { MODULE_ARRAY } from "@/utils/moduleUtils"; import { capitalize } from "lodash"; import Input from "@/components/Low/Input"; import { findAllowedEntities, findAllowedEntitiesSomePermissions, groupAllowedEntitiesByPermissions, } from "@/utils/permissions"; import { User } from "@/interfaces/user"; import useExamEditorStore from "@/stores/examEditor"; import ExamEditorStore from "@/stores/examEditor/types"; import ExamEditor from "@/components/ExamEditor"; import { mapBy, redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { Module } from "@/interfaces"; import { getExam } from "@/utils/exams.be"; import { Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise, } from "@/interfaces/exam"; import { useEffect, useState } from "react"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { isAdmin } from "@/utils/users"; import axios from "axios"; import { EntityWithRoles } from "@/interfaces/entity"; type Permission = { [key in Module]: boolean }; export const getServerSideProps = withIronSessionSsr( async ({ req, res, query }) => { const user = await requestUser(req, res); if (!user) return redirect("/login"); if (shouldRedirectHome(user)) return redirect("/"); const entityIDs = mapBy(user.entities, "id"); const entities = await getEntitiesWithRoles( isAdmin(user) ? undefined : entityIDs ); const generatePermissions = groupAllowedEntitiesByPermissions( user, entities, [ "generate_reading", "generate_listening", "generate_writing", "generate_speaking", "generate_level", ] ); const permissions: Permission = { reading: generatePermissions["generate_reading"].length > 0, listening: generatePermissions["generate_listening"].length > 0, writing: generatePermissions["generate_writing"].length > 0, speaking: generatePermissions["generate_speaking"].length > 0, level: generatePermissions["generate_level"].length > 0, }; const { ["update_exam_privacy"]: entitiesAllowEditPrivacy, ["create_confidential_exams"]: entitiesAllowConfExams, ["create_public_exams"]: entitiesAllowPublicExams, } = groupAllowedEntitiesByPermissions(user, entities, [ "update_exam_privacy", "create_confidential_exams", "create_public_exams", ]); if (Object.keys(permissions).every((p) => !permissions[p as Module])) return redirect("/"); const { id, module: examModule } = query as { id?: string; module?: Module; }; if (!id || !examModule) return { props: serialize({ user, permissions }) }; //if (!permissions[module]) return redirect("/generation") const exam = await getExam(examModule, id); if (!exam) return redirect("/generation"); return { props: serialize({ id, user, exam, examModule, permissions, entitiesAllowEditPrivacy, entitiesAllowConfExams, entitiesAllowPublicExams, }), }; }, sessionOptions ); export default function Generation({ id, user, exam, examModule, permissions, entitiesAllowEditPrivacy, entitiesAllowConfExams, entitiesAllowPublicExams, }: { id: string; user: User; exam?: Exam; examModule?: Module; permissions: Permission; entitiesAllowEditPrivacy: EntityWithRoles[]; entitiesAllowPublicExams: EntityWithRoles[]; entitiesAllowConfExams: EntityWithRoles[]; }) { const { title, currentModule, modules, dispatch } = useExamEditorStore(); const [examLevelParts, setExamLevelParts] = useState( undefined ); const updateRoot = (updates: Partial) => { dispatch({ type: "UPDATE_ROOT", payload: { updates } }); }; useEffect(() => { if (id && exam && examModule) { if (examModule === "level" && exam.module === "level") { setExamLevelParts(exam.parts.length); } updateRoot({ currentModule: examModule }); dispatch({ type: "INIT_EXAM_EDIT", payload: { exam, id, examModule } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, exam, module]); useEffect(() => { const fetchAvatars = async () => { const response = await axios.get("/api/exam/avatars"); updateRoot({ speakingAvatars: response.data }); }; fetchAvatars(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // media cleanup on unmount useEffect(() => { return () => { const state = modules; if (state.writing.academic_url) { URL.revokeObjectURL(state.writing.academic_url); } state.listening.sections.forEach((section) => { const listeningPart = section.state as ListeningPart; if (listeningPart.audio?.source) { URL.revokeObjectURL(listeningPart.audio.source); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId: section.sectionId, module: "listening", field: "state", value: { ...listeningPart, audio: undefined }, }, }); } }); if ( state.listening.instructionsState.customInstructionsURL.startsWith( "blob:" ) ) { URL.revokeObjectURL( state.listening.instructionsState.customInstructionsURL ); } state.speaking.sections.forEach((section) => { const sectionState = section.state as Exercise; if (sectionState.type === "speaking") { const speakingExercise = sectionState as SpeakingExercise; URL.revokeObjectURL(speakingExercise.video_url); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId: section.sectionId, module: "listening", field: "state", value: { ...speakingExercise, video_url: undefined }, }, }); } if (sectionState.type === "interactiveSpeaking") { const interactiveSpeaking = sectionState as InteractiveSpeakingExercise; interactiveSpeaking.prompts.forEach((prompt) => { URL.revokeObjectURL(prompt.video_url); }); dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId: section.sectionId, module: "listening", field: "state", value: { ...interactiveSpeaking, prompts: interactiveSpeaking.prompts.map((p) => ({ ...p, video_url: undefined, })), }, }, }); } }); dispatch({ type: "FULL_RESET" }); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> Exam Generation | EnCoach {user && ( <>

Exam Editor

updateRoot({ title })} roundness="xl" value={title} defaultValue={title} required /> updateRoot({ currentModule })} className="flex flex-row flex-wrap w-full gap-4 -md:justify-center justify-between" > {[...MODULE_ARRAY].reduce((acc, x) => { if (permissions[x]) acc.push( {({ checked }) => ( {capitalize(x)} )} ); return acc; }, [] as JSX.Element[])}
)} ); }