diff --git a/src/pages/(exam)/ExamPage.tsx b/src/pages/(exam)/ExamPage.tsx index 99b102c4..d583284b 100644 --- a/src/pages/(exam)/ExamPage.tsx +++ b/src/pages/(exam)/ExamPage.tsx @@ -1,6 +1,6 @@ /* eslint-disable @next/next/no-img-element */ -import { Module } from "@/interfaces"; -import { useEffect, useState } from "react"; +import {Module} from "@/interfaces"; +import {useEffect, useState} from "react"; import AbandonPopup from "@/components/AbandonPopup"; import Layout from "@/components/High/Layout"; @@ -12,567 +12,447 @@ import Selection from "@/exams/Selection"; import Speaking from "@/exams/Speaking"; import Writing from "@/exams/Writing"; import useUser from "@/hooks/useUser"; -import { Exam, UserSolution, Variant } from "@/interfaces/exam"; -import { Stat } from "@/interfaces/user"; +import {Exam, UserSolution, Variant} from "@/interfaces/exam"; +import {Stat} from "@/interfaces/user"; import useExamStore from "@/stores/examStore"; -import { - evaluateSpeakingAnswer, - evaluateWritingAnswer, -} from "@/utils/evaluation"; -import { defaultExamUserSolutions, getExam } from "@/utils/exams"; +import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation"; +import {defaultExamUserSolutions, getExam} from "@/utils/exams"; import axios from "axios"; -import { useRouter } from "next/router"; -import { toast, ToastContainer } from "react-toastify"; -import { v4 as uuidv4 } from "uuid"; +import {useRouter} from "next/router"; +import {toast, ToastContainer} from "react-toastify"; +import {v4 as uuidv4} from "uuid"; import useSessions from "@/hooks/useSessions"; import ShortUniqueId from "short-unique-id"; interface Props { - page: "exams" | "exercises"; + page: "exams" | "exercises"; } -export default function ExamPage({ page }: Props) { - const [variant, setVariant] = useState("full"); - const [avoidRepeated, setAvoidRepeated] = useState(false); - const [hasBeenUploaded, setHasBeenUploaded] = useState(false); - const [showAbandonPopup, setShowAbandonPopup] = useState(false); - const [isEvaluationLoading, setIsEvaluationLoading] = useState(false); - const [statsAwaitingEvaluation, setStatsAwaitingEvaluation] = useState< - string[] - >([]); - const [timeSpent, setTimeSpent] = useState(0); +export default function ExamPage({page}: Props) { + const [variant, setVariant] = useState("full"); + const [avoidRepeated, setAvoidRepeated] = useState(false); + const [hasBeenUploaded, setHasBeenUploaded] = useState(false); + const [showAbandonPopup, setShowAbandonPopup] = useState(false); + const [isEvaluationLoading, setIsEvaluationLoading] = useState(false); + const [statsAwaitingEvaluation, setStatsAwaitingEvaluation] = useState([]); + const [timeSpent, setTimeSpent] = useState(0); - const resetStore = useExamStore((state) => state.reset); - const assignment = useExamStore((state) => state.assignment); - const initialTimeSpent = useExamStore((state) => state.timeSpent); + const resetStore = useExamStore((state) => state.reset); + const assignment = useExamStore((state) => state.assignment); + const initialTimeSpent = useExamStore((state) => state.timeSpent); - const examStore = useExamStore; + const examStore = useExamStore; - const { exam, setExam } = useExamStore((state) => state); - const { exams, setExams } = useExamStore((state) => state); - const { sessionId, setSessionId } = useExamStore((state) => state); - const { partIndex, setPartIndex } = useExamStore((state) => state); - const { moduleIndex, setModuleIndex } = useExamStore((state) => state); - const { questionIndex, setQuestionIndex } = useExamStore((state) => state); - const { exerciseIndex, setExerciseIndex } = useExamStore((state) => state); - const { userSolutions, setUserSolutions } = useExamStore((state) => state); - const { showSolutions, setShowSolutions } = useExamStore((state) => state); - const { selectedModules, setSelectedModules } = useExamStore( - (state) => state, - ); + const {exam, setExam} = useExamStore((state) => state); + const {exams, setExams} = useExamStore((state) => state); + const {sessionId, setSessionId} = useExamStore((state) => state); + const {partIndex, setPartIndex} = useExamStore((state) => state); + const {moduleIndex, setModuleIndex} = useExamStore((state) => state); + const {questionIndex, setQuestionIndex} = useExamStore((state) => state); + const {exerciseIndex, setExerciseIndex} = useExamStore((state) => state); + const {userSolutions, setUserSolutions} = useExamStore((state) => state); + const {showSolutions, setShowSolutions} = useExamStore((state) => state); + const {selectedModules, setSelectedModules} = useExamStore((state) => state); - const { user } = useUser({ redirectTo: "/login" }); - const router = useRouter(); + const {user} = useUser({redirectTo: "/login"}); + const router = useRouter(); - const reset = () => { - resetStore(); - setVariant("full"); - setAvoidRepeated(false); - setHasBeenUploaded(false); - setShowAbandonPopup(false); - setIsEvaluationLoading(false); - setStatsAwaitingEvaluation([]); - setTimeSpent(0); - }; + const reset = () => { + resetStore(); + setVariant("full"); + setAvoidRepeated(false); + setHasBeenUploaded(false); + setShowAbandonPopup(false); + setIsEvaluationLoading(false); + setStatsAwaitingEvaluation([]); + setTimeSpent(0); + }; - // eslint-disable-next-line react-hooks/exhaustive-deps - const saveSession = async () => { - console.log("Saving your session..."); + // eslint-disable-next-line react-hooks/exhaustive-deps + const saveSession = async () => { + console.log("Saving your session..."); - await axios.post("/api/sessions", { - id: sessionId, - sessionId, - date: new Date().toISOString(), - userSolutions, - moduleIndex, - selectedModules, - assignment, - timeSpent, - exams, - exam, - partIndex, - exerciseIndex, - questionIndex, - user: user?.id, - }); - }; + await axios.post("/api/sessions", { + id: sessionId, + sessionId, + date: new Date().toISOString(), + userSolutions, + moduleIndex, + selectedModules, + assignment, + timeSpent, + exams, + exam, + partIndex, + exerciseIndex, + questionIndex, + user: user?.id, + }); + }; - useEffect( - () => setTimeSpent((prev) => prev + initialTimeSpent), - [initialTimeSpent], - ); + useEffect(() => setTimeSpent((prev) => prev + initialTimeSpent), [initialTimeSpent]); - useEffect(() => { - if (userSolutions.length === 0 && exams.length > 0) { - const defaultSolutions = exams.map(defaultExamUserSolutions).flat(); - setUserSolutions(defaultSolutions); - } - }, [exams, setUserSolutions, userSolutions]); + useEffect(() => { + if (userSolutions.length === 0 && exams.length > 0) { + const defaultSolutions = exams.map(defaultExamUserSolutions).flat(); + setUserSolutions(defaultSolutions); + } + }, [exams, setUserSolutions, userSolutions]); - useEffect(() => { - if ( - sessionId.length > 0 && - userSolutions.length > 0 && - selectedModules.length > 0 && - exams.length > 0 && - !!exam && - timeSpent > 0 && - !showSolutions && - moduleIndex < selectedModules.length - ) - saveSession(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - assignment, - exam, - exams, - moduleIndex, - selectedModules, - sessionId, - userSolutions, - user, - exerciseIndex, - partIndex, - questionIndex, - ]); + useEffect(() => { + if ( + sessionId.length > 0 && + userSolutions.length > 0 && + selectedModules.length > 0 && + exams.length > 0 && + !!exam && + timeSpent > 0 && + !showSolutions && + moduleIndex < selectedModules.length + ) + saveSession(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [assignment, exam, exams, moduleIndex, selectedModules, sessionId, userSolutions, user, exerciseIndex, partIndex, questionIndex]); - useEffect(() => { - if ( - timeSpent % 20 === 0 && - timeSpent > 0 && - moduleIndex < selectedModules.length && - !showSolutions - ) - saveSession(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timeSpent]); + useEffect(() => { + if (timeSpent % 20 === 0 && timeSpent > 0 && moduleIndex < selectedModules.length && !showSolutions) saveSession(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timeSpent]); - useEffect(() => { - if (selectedModules.length > 0 && sessionId.length === 0) { - const shortUID = new ShortUniqueId(); - setSessionId(shortUID.randomUUID(8)); - } - }, [setSessionId, selectedModules, sessionId]); + useEffect(() => { + if (selectedModules.length > 0 && sessionId.length === 0) { + const shortUID = new ShortUniqueId(); + setSessionId(shortUID.randomUUID(8)); + } + }, [setSessionId, selectedModules, sessionId]); - useEffect(() => { - if (user?.type === "developer") console.log(exam); - }, [exam, user]); + useEffect(() => { + if (user?.type === "developer") console.log(exam); + }, [exam, user]); - useEffect(() => { - if (selectedModules.length > 0 && timeSpent === 0 && !showSolutions) { - const timerInterval = setInterval(() => { - setTimeSpent((prev) => prev + 1); - }, 1000); + useEffect(() => { + if (selectedModules.length > 0 && timeSpent === 0 && !showSolutions) { + const timerInterval = setInterval(() => { + setTimeSpent((prev) => prev + 1); + }, 1000); - return () => { - clearInterval(timerInterval); - }; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedModules.length]); + return () => { + clearInterval(timerInterval); + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules.length]); - useEffect(() => { - if (showSolutions) setModuleIndex(-1); - }, [setModuleIndex, showSolutions]); + useEffect(() => { + if (showSolutions) setModuleIndex(-1); + }, [setModuleIndex, showSolutions]); - useEffect(() => { - (async () => { - if ( - selectedModules.length > 0 && - exams.length > 0 && - moduleIndex < selectedModules.length - ) { - const nextExam = exams[moduleIndex]; + useEffect(() => { + (async () => { + if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) { + const nextExam = exams[moduleIndex]; - if (partIndex === -1 && nextExam.module !== "listening") - setPartIndex(0); - if ( - exerciseIndex === -1 && - !["reading", "listening"].includes(nextExam?.module) - ) - setExerciseIndex(0); - setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedModules, moduleIndex, exams]); + if (partIndex === -1 && nextExam.module !== "listening") setPartIndex(0); + if (exerciseIndex === -1 && !["reading", "listening"].includes(nextExam?.module)) setExerciseIndex(0); + setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules, moduleIndex, exams]); - useEffect(() => { - (async () => { - if (selectedModules.length > 0 && exams.length === 0) { - const examPromises = selectedModules.map((module) => - getExam( - module, - avoidRepeated, - variant, - user?.type === "student" || user?.type === "developer" - ? user.preferredGender - : undefined, - ), - ); - Promise.all(examPromises).then((values) => { - if (values.every((x) => !!x)) { - setExams(values.map((x) => x!)); - } else { - toast.error("Something went wrong, please try again"); - setTimeout(router.reload, 500); - } - }); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedModules, setExams, exams]); + useEffect(() => { + (async () => { + if (selectedModules.length > 0 && exams.length === 0) { + const examPromises = selectedModules.map((module) => + getExam( + module, + avoidRepeated, + variant, + user?.type === "student" || user?.type === "developer" ? user.preferredGender : undefined, + ), + ); + Promise.all(examPromises).then((values) => { + if (values.every((x) => !!x)) { + setExams(values.map((x) => x!)); + } else { + toast.error("Something went wrong, please try again"); + setTimeout(router.reload, 500); + } + }); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules, setExams, exams]); - useEffect(() => { - if ( - selectedModules.length > 0 && - exams.length !== 0 && - moduleIndex >= selectedModules.length && - !hasBeenUploaded && - !showSolutions - ) { - const newStats: Stat[] = userSolutions.map((solution) => ({ - ...solution, - id: solution.id || uuidv4(), - timeSpent, - session: sessionId, - exam: solution.exam!, - module: solution.module!, - user: user?.id || "", - date: new Date().getTime(), - isDisabled: solution.isDisabled, - ...(assignment ? { assignment: assignment.id } : {}), - })); + useEffect(() => { + if (selectedModules.length > 0 && exams.length !== 0 && moduleIndex >= selectedModules.length && !hasBeenUploaded && !showSolutions) { + const newStats: Stat[] = userSolutions.map((solution) => ({ + ...solution, + id: solution.id || uuidv4(), + timeSpent, + session: sessionId, + exam: solution.exam!, + module: solution.module!, + user: user?.id || "", + date: new Date().getTime(), + isDisabled: solution.isDisabled, + ...(assignment ? {assignment: assignment.id} : {}), + })); - axios - .post<{ ok: boolean }>("/api/stats", newStats) - .then((response) => setHasBeenUploaded(response.data.ok)) - .catch(() => setHasBeenUploaded(false)); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedModules, moduleIndex, hasBeenUploaded]); + axios + .post<{ok: boolean}>("/api/stats", newStats) + .then((response) => setHasBeenUploaded(response.data.ok)) + .catch(() => setHasBeenUploaded(false)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedModules, moduleIndex, hasBeenUploaded]); - useEffect(() => { - setIsEvaluationLoading(statsAwaitingEvaluation.length !== 0); - }, [statsAwaitingEvaluation]); + useEffect(() => { + setIsEvaluationLoading(statsAwaitingEvaluation.length !== 0); + }, [statsAwaitingEvaluation]); - useEffect(() => { - if (statsAwaitingEvaluation.length > 0) { - checkIfStatsHaveBeenEvaluated(statsAwaitingEvaluation); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [statsAwaitingEvaluation]); + useEffect(() => { + if (statsAwaitingEvaluation.length > 0) { + checkIfStatsHaveBeenEvaluated(statsAwaitingEvaluation); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [statsAwaitingEvaluation]); - const checkIfStatsHaveBeenEvaluated = (ids: string[]) => { - setTimeout(async () => { - try { - const awaitedStats = await Promise.all( - ids.map( - async (id) => (await axios.get(`/api/stats/${id}`)).data, - ), - ); - const solutionsEvaluated = awaitedStats.every((stat) => - stat.solutions.every((x) => x.evaluation !== null), - ); - if (solutionsEvaluated) { - const statsUserSolutions: UserSolution[] = awaitedStats.map( - (stat) => ({ - id: stat.id, - exercise: stat.exercise, - score: stat.score, - solutions: stat.solutions, - type: stat.type, - exam: stat.exam, - module: stat.module, - }), - ); + const checkIfStatsHaveBeenEvaluated = (ids: string[]) => { + setTimeout(async () => { + try { + const awaitedStats = await Promise.all(ids.map(async (id) => (await axios.get(`/api/stats/${id}`)).data)); + const solutionsEvaluated = awaitedStats.every((stat) => stat.solutions.every((x) => x.evaluation !== null)); + if (solutionsEvaluated) { + const statsUserSolutions: UserSolution[] = awaitedStats.map((stat) => ({ + id: stat.id, + exercise: stat.exercise, + score: stat.score, + solutions: stat.solutions, + type: stat.type, + exam: stat.exam, + module: stat.module, + })); - const updatedUserSolutions = userSolutions.map((x) => { - const respectiveSolution = statsUserSolutions.find( - (y) => y.exercise === x.exercise, - ); - return respectiveSolution ? respectiveSolution : x; - }); + const updatedUserSolutions = userSolutions.map((x) => { + const respectiveSolution = statsUserSolutions.find((y) => y.exercise === x.exercise); + return respectiveSolution ? respectiveSolution : x; + }); - setUserSolutions(updatedUserSolutions); - return setStatsAwaitingEvaluation((prev) => - prev.filter((x) => !ids.includes(x)), - ); - } + setUserSolutions(updatedUserSolutions); + return setStatsAwaitingEvaluation((prev) => prev.filter((x) => !ids.includes(x))); + } - return checkIfStatsHaveBeenEvaluated(ids); - } catch { - return checkIfStatsHaveBeenEvaluated(ids); - } - }, 5 * 1000); - }; + return checkIfStatsHaveBeenEvaluated(ids); + } catch { + return checkIfStatsHaveBeenEvaluated(ids); + } + }, 5 * 1000); + }; - const updateExamWithUserSolutions = (exam: Exam): Exam => { - if (exam.module === "reading" || exam.module === "listening") { - const parts = exam.parts.map((p) => - Object.assign(p, { - exercises: p.exercises.map((x) => - Object.assign(x, { - userSolutions: userSolutions.find((y) => x.id === y.exercise) - ?.solutions, - }), - ), - }), - ); - return Object.assign(exam, { parts }); - } + const updateExamWithUserSolutions = (exam: Exam): Exam => { + if (exam.module === "reading" || exam.module === "listening") { + const parts = exam.parts.map((p) => + Object.assign(p, { + exercises: p.exercises.map((x) => + Object.assign(x, { + userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions, + }), + ), + }), + ); + return Object.assign(exam, {parts}); + } - const exercises = exam.exercises.map((x) => - Object.assign(x, { - userSolutions: userSolutions.find((y) => x.id === y.exercise) - ?.solutions, - }), - ); - return Object.assign(exam, { exercises }); - }; + const exercises = exam.exercises.map((x) => + Object.assign(x, { + userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions, + }), + ); + return Object.assign(exam, {exercises}); + }; - const onFinish = async (solutions: UserSolution[]) => { - const solutionIds = solutions.map((x) => x.exercise); - const solutionExams = solutions.map((x) => x.exam); + const onFinish = async (solutions: UserSolution[]) => { + const solutionIds = solutions.map((x) => x.exercise); + const solutionExams = solutions.map((x) => x.exam); - let newSolutions = [...solutions]; + let newSolutions = [...solutions]; - if (exam && !solutionExams.includes(exam.id)) return; + if (exam && !solutionExams.includes(exam.id)) return; - if ( - exam && - (exam.module === "writing" || exam.module === "speaking") && - solutions.length > 0 && - !showSolutions - ) { - setHasBeenUploaded(true); - setIsEvaluationLoading(true); + if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) { + setHasBeenUploaded(true); + setIsEvaluationLoading(true); - const responses: UserSolution[] = ( - await Promise.all( - exam.exercises.map(async (exercise, index) => { - const evaluationID = uuidv4(); - if (exercise.type === "writing") - return await evaluateWritingAnswer( - exercise, - index + 1, - solutions.find((x) => x.exercise === exercise.id)!, - evaluationID, - ); + const responses: UserSolution[] = ( + await Promise.all( + exam.exercises.map(async (exercise, index) => { + const evaluationID = uuidv4(); + if (exercise.type === "writing") + return await evaluateWritingAnswer(exercise, index + 1, solutions.find((x) => x.exercise === exercise.id)!, evaluationID); - if ( - exercise.type === "interactiveSpeaking" || - exercise.type === "speaking" - ) - return await evaluateSpeakingAnswer( - exercise, - solutions.find((x) => x.exercise === exercise.id)!, - evaluationID, - ); - }), - ) - ).filter((x) => !!x) as UserSolution[]; + if (exercise.type === "interactiveSpeaking" || exercise.type === "speaking") + return await evaluateSpeakingAnswer( + exercise, + solutions.find((x) => x.exercise === exercise.id)!, + evaluationID, + index === 0 ? 1 : 2, + ); + }), + ) + ).filter((x) => !!x) as UserSolution[]; - newSolutions = [ - ...newSolutions.filter( - (x) => !responses.map((y) => y.exercise).includes(x.exercise), - ), - ...responses, - ]; - setStatsAwaitingEvaluation((prev) => [ - ...prev, - ...responses.filter((x) => !!x).map((r) => (r as any).id), - ]); - setHasBeenUploaded(false); - } + newSolutions = [...newSolutions.filter((x) => !responses.map((y) => y.exercise).includes(x.exercise)), ...responses]; + setStatsAwaitingEvaluation((prev) => [...prev, ...responses.filter((x) => !!x).map((r) => (r as any).id)]); + setHasBeenUploaded(false); + } - axios.get("/api/stats/update"); + axios.get("/api/stats/update"); - setUserSolutions([ - ...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), - ...newSolutions, - ]); - setModuleIndex(moduleIndex + 1); + setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...newSolutions]); + setModuleIndex(moduleIndex + 1); - setPartIndex(-1); - setExerciseIndex(-1); - setQuestionIndex(0); - }; + setPartIndex(-1); + setExerciseIndex(-1); + setQuestionIndex(0); + }; - const aggregateScoresByModule = (): { - module: Module; - total: number; - missing: number; - correct: number; - }[] => { - const scores: { - [key in Module]: { total: number; missing: number; correct: number }; - } = { - reading: { - total: 0, - correct: 0, - missing: 0, - }, - listening: { - total: 0, - correct: 0, - missing: 0, - }, - writing: { - total: 0, - correct: 0, - missing: 0, - }, - speaking: { - total: 0, - correct: 0, - missing: 0, - }, - level: { - total: 0, - correct: 0, - missing: 0, - }, - }; + const aggregateScoresByModule = (): { + module: Module; + total: number; + missing: number; + correct: number; + }[] => { + const scores: { + [key in Module]: {total: number; missing: number; correct: number}; + } = { + reading: { + total: 0, + correct: 0, + missing: 0, + }, + listening: { + total: 0, + correct: 0, + missing: 0, + }, + writing: { + total: 0, + correct: 0, + missing: 0, + }, + speaking: { + total: 0, + correct: 0, + missing: 0, + }, + level: { + total: 0, + correct: 0, + missing: 0, + }, + }; - userSolutions.forEach((x) => { - const examModule = - x.module || - (x.type === "writing" - ? "writing" - : x.type === "speaking" || x.type === "interactiveSpeaking" - ? "speaking" - : undefined); + userSolutions.forEach((x) => { + const examModule = + x.module || (x.type === "writing" ? "writing" : x.type === "speaking" || x.type === "interactiveSpeaking" ? "speaking" : undefined); - scores[examModule!] = { - total: scores[examModule!].total + x.score.total, - correct: scores[examModule!].correct + x.score.correct, - missing: scores[examModule!].missing + x.score.missing, - }; - }); + scores[examModule!] = { + total: scores[examModule!].total + x.score.total, + correct: scores[examModule!].correct + x.score.correct, + missing: scores[examModule!].missing + x.score.missing, + }; + }); - return Object.keys(scores) - .filter((x) => scores[x as Module].total > 0) - .map((x) => ({ module: x as Module, ...scores[x as Module] })); - }; + return Object.keys(scores) + .filter((x) => scores[x as Module].total > 0) + .map((x) => ({module: x as Module, ...scores[x as Module]})); + }; - const renderScreen = () => { - if (selectedModules.length === 0) { - return ( - { - setModuleIndex(0); - setAvoidRepeated(avoid); - setSelectedModules(modules); - setVariant(variant); - }} - /> - ); - } + const renderScreen = () => { + if (selectedModules.length === 0) { + return ( + { + setModuleIndex(0); + setAvoidRepeated(avoid); + setSelectedModules(modules); + setVariant(variant); + }} + /> + ); + } - if (moduleIndex >= selectedModules.length || moduleIndex === -1) { - return ( - { - setShowSolutions(true); - setModuleIndex(index || 0); - setExerciseIndex( - ["reading", "listening"].includes(exams[0].module) ? -1 : 0, - ); - setPartIndex(exams[0].module === "listening" ? -1 : 0); - setExam(exams[0]); - }} - scores={aggregateScoresByModule()} - /> - ); - } + if (moduleIndex >= selectedModules.length || moduleIndex === -1) { + return ( + { + setShowSolutions(true); + setModuleIndex(index || 0); + setExerciseIndex(["reading", "listening"].includes(exams[0].module) ? -1 : 0); + setPartIndex(exams[0].module === "listening" ? -1 : 0); + setExam(exams[0]); + }} + scores={aggregateScoresByModule()} + /> + ); + } - if (exam && exam.module === "reading") { - return ( - - ); - } + if (exam && exam.module === "reading") { + return ; + } - if (exam && exam.module === "listening") { - return ( - - ); - } + if (exam && exam.module === "listening") { + return ; + } - if (exam && exam.module === "writing") { - return ( - - ); - } + if (exam && exam.module === "writing") { + return ; + } - if (exam && exam.module === "speaking") { - return ( - - ); - } + if (exam && exam.module === "speaking") { + return ; + } - if (exam && exam.module === "level") { - return ( - - ); - } + if (exam && exam.module === "level") { + return ; + } - return <>Loading...; - }; + return <>Loading...; + }; - return ( - <> - - {user && ( - setShowAbandonPopup(true)} - > - <> - {renderScreen()} - {!showSolutions && moduleIndex < selectedModules.length && ( - { - reset(); - }} - onCancel={() => setShowAbandonPopup(false)} - /> - )} - - - )} - - ); + return ( + <> + + {user && ( + setShowAbandonPopup(true)}> + <> + {renderScreen()} + {!showSolutions && moduleIndex < selectedModules.length && ( + { + reset(); + }} + onCancel={() => setShowAbandonPopup(false)} + /> + )} + + + )} + + ); } diff --git a/src/pages/api/evaluate/speaking.ts b/src/pages/api/evaluate/speaking.ts index 79293783..3f9431b9 100644 --- a/src/pages/api/evaluate/speaking.ts +++ b/src/pages/api/evaluate/speaking.ts @@ -30,6 +30,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const audioFile = files.audio; const audioFileRef = ref(storage, `speaking_recordings/${fields.id}.wav`); + const task = parseInt(fields.task.toString()); const binary = fs.readFileSync((audioFile as any).path).buffer; const snapshot = await uploadBytes(audioFileRef, binary); @@ -39,7 +40,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { res.status(200).json(null); console.log("🌱 - Still processing"); - const backendRequest = await evaluate({answers: [{question: fields.question, answer: path}]}); + const backendRequest = await evaluate({answer: path, question: fields.question}, task); console.log("🌱 - Process complete"); const correspondingStat = await getCorrespondingStat(fields.id, 1); @@ -76,14 +77,14 @@ async function getCorrespondingStat(id: string, index: number): Promise { return getCorrespondingStat(id, index + 1); } -async function evaluate(body: {answers: object[]}): Promise { - const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_3`, body, { +async function evaluate(body: {answer: string; question: string}, task: number): Promise { + const backendRequest = await axios.post(`${process.env.BACKEND_URL}/speaking_task_${task}`, body, { headers: { Authorization: `Bearer ${process.env.BACKEND_JWT}`, }, }); - if (typeof backendRequest.data === "string") return evaluate(body); + if (typeof backendRequest.data === "string") return evaluate(body, task); return backendRequest; } diff --git a/src/utils/evaluation.ts b/src/utils/evaluation.ts index cbcc9fae..82283a4b 100644 --- a/src/utils/evaluation.ts +++ b/src/utils/evaluation.ts @@ -45,10 +45,11 @@ export const evaluateSpeakingAnswer = async ( exercise: SpeakingExercise | InteractiveSpeakingExercise, solution: UserSolution, id: string, + task: number, ): Promise => { switch (exercise?.type) { case "speaking": - return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id)), id} as UserSolution; + return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id, task)), id} as UserSolution; case "interactiveSpeaking": return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id)), id} as UserSolution; default: @@ -66,6 +67,7 @@ const evaluateSpeakingExercise = async ( exerciseId: string, solution: UserSolution, id: string, + task: number, ): Promise => { const formData = new FormData(); @@ -81,6 +83,7 @@ const evaluateSpeakingExercise = async ( `${exercise.text.replaceAll("\n", "")}` + (exercise.prompts.length > 0 ? `You should talk about: ${exercise.prompts.join(", ")}` : ""); formData.append("question", evaluationQuestion); formData.append("id", id); + formData.append("task", task.toString()); const config = { headers: {