ENCOA-268

This commit is contained in:
Tiago Ribeiro
2024-12-12 16:14:54 +00:00
parent 6bb817f9af
commit 61d1bbbe13
8 changed files with 76 additions and 29 deletions

View File

@@ -0,0 +1,26 @@
import { useState } from "react";
import Button from "./Low/Button";
import Modal from "./Modal";
interface Props {
open?: boolean
}
export default function PracticeModal({ open }: Props) {
const [isOpen, setIsOpen] = useState<boolean>(open || false)
return (
<Modal title="Practice Questions" isOpen={isOpen} onClose={() => setIsOpen(false)}>
<div className="py-4 flex flex-col gap-4 items-center">
<span className="w-full">
To acquaint yourself with the question types in this section, please respond to the practice questions provided.
<br />
<b>Do note that these questions are for practice purposes only and are not graded.</b>
<br />
You may choose to skip them if you prefer.
</span>
<Button onClick={() => setIsOpen(false)} className="w-full max-w-[200px]">Understood</Button>
</div>
</Modal>
)
}

View File

@@ -14,6 +14,7 @@ import RenderAudioInstructionsPlayer from "./components/RenderAudioInstructionsP
import RenderAudioPlayer from "./components/RenderAudioPlayer"; import RenderAudioPlayer from "./components/RenderAudioPlayer";
import SectionNavbar from "./Navigation/SectionNavbar"; import SectionNavbar from "./Navigation/SectionNavbar";
import ProgressButtons from "./components/ProgressButtons"; import ProgressButtons from "./components/ProgressButtons";
import PracticeModal from "@/components/PracticeModal";
const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = false, preview = false }) => { const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = false, preview = false }) => {
@@ -83,8 +84,11 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [], userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
})) }))
const hasPractice = exercises.some(e => e.isPractice)
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<PracticeModal open={hasPractice} />
{formattedExercises.map(e => showSolutions {formattedExercises.map(e => showSolutions
? renderSolution(e) ? renderSolution(e)
: (!startNow && !showPartDivider && !isBetweenParts && !showSolutions) && renderExercise(e, exam.id, registerSolution, preview))} : (!startNow && !showPartDivider && !isBetweenParts && !showSolutions) && renderExercise(e, exam.id, registerSolution, preview))}
@@ -93,8 +97,6 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [partIndex, startNow, showPartDivider, isBetweenParts, showSolutions]); }, [partIndex, startNow, showPartDivider, isBetweenParts, showSolutions]);
const confirmFinishModule = (keepGoing?: boolean) => { const confirmFinishModule = (keepGoing?: boolean) => {
if (!keepGoing) { if (!keepGoing) {
setShowBlankModal(false); setShowBlankModal(false);
@@ -129,12 +131,12 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
timesListened={timesListened} timesListened={timesListened}
setShowTextModal={setShowTextModal} setShowTextModal={setShowTextModal}
setTimesListened={setTimesListened} setTimesListened={setTimesListened}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
/>, [partIndex, assignment, timesListened, setShowTextModal, setTimesListened]) />, [partIndex, assignment, timesListened, setShowTextModal, setTimesListened])
const memoizedInstructions = useMemo(()=> const memoizedInstructions = useMemo(() =>
<RenderAudioInstructionsPlayer /> <RenderAudioInstructionsPlayer />
, []) , [])
return ( return (
<> <>
@@ -180,9 +182,9 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
{/* Exercise renderer */} {/* Exercise renderer */}
<> <>
{(!startNow && !showPartDivider) && progressButtons} {(!startNow && !showPartDivider) && progressButtons}
{renderPartExercises} {renderPartExercises}
{(!startNow && !showPartDivider && !isBetweenParts) && progressButtons} {(!startNow && !showPartDivider && !isBetweenParts) && progressButtons}
</> </>
</div> </div>

View File

@@ -10,13 +10,14 @@ import { countExercises } from "@/utils/moduleUtils";
import PartDivider from "./Navigation/SectionDivider"; import PartDivider from "./Navigation/SectionDivider";
import ReadingPassage from "./components/ReadingPassage"; import ReadingPassage from "./components/ReadingPassage";
//import ReadingPassageModal from "./components/ReadingPassageModal"; //import ReadingPassageModal from "./components/ReadingPassageModal";
import {calculateExerciseIndex} from "./utils/calculateExerciseIndex"; import { calculateExerciseIndex } from "./utils/calculateExerciseIndex";
import useExamNavigation from "./Navigation/useExamNavigation"; import useExamNavigation from "./Navigation/useExamNavigation";
import { ExamProps } from "./types"; import { ExamProps } from "./types";
import useExamStore, { usePersistentExamStore } from "@/stores/exam"; import useExamStore, { usePersistentExamStore } from "@/stores/exam";
import useExamTimer from "@/hooks/useExamTimer"; import useExamTimer from "@/hooks/useExamTimer";
import SectionNavbar from "./Navigation/SectionNavbar"; import SectionNavbar from "./Navigation/SectionNavbar";
import ProgressButtons from "./components/ProgressButtons"; import ProgressButtons from "./components/ProgressButtons";
import PracticeModal from "@/components/PracticeModal";
const Reading: React.FC<ExamProps<ReadingExam>> = ({ exam, showSolutions = false, preview = false }) => { const Reading: React.FC<ExamProps<ReadingExam>> = ({ exam, showSolutions = false, preview = false }) => {
const updateTimers = useExamTimer(exam.module, preview || showSolutions); const updateTimers = useExamTimer(exam.module, preview || showSolutions);
@@ -50,6 +51,13 @@ const Reading: React.FC<ExamProps<ReadingExam>> = ({ exam, showSolutions = false
startNow startNow
} = useExamNavigation({ exam, module: "reading", showBlankModal, setShowBlankModal, showSolutions, preview, disableBetweenParts: showSolutions }); } = useExamNavigation({ exam, module: "reading", showBlankModal, setShowBlankModal, showSolutions, preview, disableBetweenParts: showSolutions });
const hasPractice = useMemo(() => {
if (partIndex > -1 && partIndex < exam.parts.length) {
return exam.parts[partIndex].exercises.some(e => e.isPractice)
}
return false
}, [partIndex, exam.parts])
useEffect(() => { useEffect(() => {
if (finalizeModule || timeIsUp) { if (finalizeModule || timeIsUp) {
updateTimers(); updateTimers();
@@ -130,7 +138,7 @@ const Reading: React.FC<ExamProps<ReadingExam>> = ({ exam, showSolutions = false
const progressButtons = useMemo(() => const progressButtons = useMemo(() =>
// Do not remove the ()=> in handle next // Do not remove the ()=> in handle next
<ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} /> <ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} />
, [nextExercise, previousExercise]); , [nextExercise, previousExercise]);
return ( return (
<> <>
@@ -146,6 +154,7 @@ const Reading: React.FC<ExamProps<ReadingExam>> = ({ exam, showSolutions = false
/> />
</div> : ( </div> : (
<> <>
<PracticeModal key={partIndex} open={hasPractice} />
<div className="flex flex-col h-full w-full gap-8"> <div className="flex flex-col h-full w-full gap-8">
<BlankQuestionsModal isOpen={showBlankModal} onClose={confirmFinishModule} /> <BlankQuestionsModal isOpen={showBlankModal} onClose={confirmFinishModule} />
{/*<ReadingPassageModal text={exam.parts[partIndex].text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />*/} {/*<ReadingPassageModal text={exam.parts[partIndex].text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />*/}

View File

@@ -13,6 +13,7 @@ import useExamNavigation from "./Navigation/useExamNavigation";
import ProgressButtons from "./components/ProgressButtons"; import ProgressButtons from "./components/ProgressButtons";
import { calculateExerciseIndexSpeaking } from "./utils/calculateExerciseIndex"; import { calculateExerciseIndexSpeaking } from "./utils/calculateExerciseIndex";
import SectionNavbar from "./Navigation/SectionNavbar"; import SectionNavbar from "./Navigation/SectionNavbar";
import PracticeModal from "@/components/PracticeModal";
const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = false, preview = false }) => { const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = false, preview = false }) => {
@@ -33,6 +34,7 @@ const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = fal
const { finalizeModule, timeIsUp } = flags; const { finalizeModule, timeIsUp } = flags;
const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60); const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60);
const hasPractice = useMemo(() => exam.exercises.some(e => e.isPractice), [exam.exercises])
const { const {
nextExercise, previousExercise, nextExercise, previousExercise,
@@ -110,6 +112,7 @@ const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = fal
onNext={handlePartDividerClick} onNext={handlePartDividerClick}
/> : ( /> : (
<> <>
<PracticeModal open={hasPractice} />
{exam.exercises.length > 1 && <SectionNavbar {exam.exercises.length > 1 && <SectionNavbar
module="speaking" module="speaking"
sectionLabel="Part" sectionLabel="Part"

View File

@@ -11,6 +11,7 @@ import useExamTimer from "@/hooks/useExamTimer";
import useExamNavigation from "./Navigation/useExamNavigation"; import useExamNavigation from "./Navigation/useExamNavigation";
import SectionNavbar from "./Navigation/SectionNavbar"; import SectionNavbar from "./Navigation/SectionNavbar";
import ProgressButtons from "./components/ProgressButtons"; import ProgressButtons from "./components/ProgressButtons";
import PracticeModal from "@/components/PracticeModal";
const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false, preview = false }) => { const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false, preview = false }) => {
const updateTimers = useExamTimer(exam.module, preview || showSolutions); const updateTimers = useExamTimer(exam.module, preview || showSolutions);
@@ -27,6 +28,7 @@ const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false
} = !preview ? examState : persistentExamState; } = !preview ? examState : persistentExamState;
const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60); const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60);
const hasPractice = useMemo(() => exam.exercises.some(e => e.isPractice), [exam.exercises])
const { finalizeModule, timeIsUp } = flags; const { finalizeModule, timeIsUp } = flags;
const { nextDisabled } = navigation; const { nextDisabled } = navigation;
@@ -80,8 +82,8 @@ const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false
const progressButtons = useMemo(() => const progressButtons = useMemo(() =>
// Do not remove the ()=> in handle next // Do not remove the ()=> in handle next
<ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} nextDisabled={nextDisabled}/> <ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} nextDisabled={nextDisabled} />
, [nextExercise, previousExercise, nextDisabled]); , [nextExercise, previousExercise, nextDisabled]);
return ( return (
@@ -96,6 +98,7 @@ const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false
onNext={handlePartDividerClick} onNext={handlePartDividerClick}
/> : ( /> : (
<div className="flex flex-col h-full w-full gap-8 items-center"> <div className="flex flex-col h-full w-full gap-8 items-center">
<PracticeModal open={hasPractice} />
{exam.exercises.length > 1 && <SectionNavbar {exam.exercises.length > 1 && <SectionNavbar
module="writing" module="writing"
sectionLabel="Part" sectionLabel="Part"

View File

@@ -1,8 +1,8 @@
import {ExamState} from "@/stores/exam/types"; import { ExamState } from "@/stores/exam/types";
import axios from "axios"; import axios from "axios";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
export type Session = ExamState & {user: string; id: string; date: string}; export type Session = ExamState & { user: string; id: string; date: string };
export default function useSessions(user?: string) { export default function useSessions(user?: string) {
const [sessions, setSessions] = useState<Session[]>([]); const [sessions, setSessions] = useState<Session[]>([]);
@@ -19,5 +19,5 @@ export default function useSessions(user?: string) {
useEffect(getData, [user]); useEffect(getData, [user]);
return {sessions, isLoading, isError, reload: getData}; return { sessions, isLoading, isError, reload: getData };
} }

View File

@@ -22,6 +22,7 @@ import ShortUniqueId from "short-unique-id";
import { ExamProps } from "@/exams/types"; import { ExamProps } from "@/exams/types";
import useExamStore from "@/stores/exam"; import useExamStore from "@/stores/exam";
import useEvaluationPolling from "@/hooks/useEvaluationPolling"; import useEvaluationPolling from "@/hooks/useEvaluationPolling";
import PracticeModal from "@/components/PracticeModal";
interface Props { interface Props {
page: "exams" | "exercises"; page: "exams" | "exercises";
@@ -128,7 +129,7 @@ export default function ExamPage({ page, user, destination = "/", hideSidebar =
if (exercise.type === "writing") if (exercise.type === "writing")
await evaluateWritingAnswer(user.id, sessionId, exercise, index + 1, userSolutions.find((x) => x.exercise === exercise.id)!); await evaluateWritingAnswer(user.id, sessionId, exercise, index + 1, userSolutions.find((x) => x.exercise === exercise.id)!);
if (exercise.type === "interactiveSpeaking" || exercise.type === "speaking"){ if (exercise.type === "interactiveSpeaking" || exercise.type === "speaking") {
await evaluateSpeakingAnswer( await evaluateSpeakingAnswer(
user.id, user.id,
sessionId, sessionId,
@@ -144,7 +145,7 @@ export default function ExamPage({ page, user, destination = "/", hideSidebar =
} }
}, [exam, showSolutions, userSolutions, sessionId, user?.id, flags]); }, [exam, showSolutions, userSolutions, sessionId, user?.id, flags]);
useEvaluationPolling({pendingExercises, setPendingExercises}); useEvaluationPolling({ pendingExercises, setPendingExercises });
useEffect(() => { useEffect(() => {
if (flags.finalizeExam && moduleIndex !== -1) { if (flags.finalizeExam && moduleIndex !== -1) {

View File

@@ -1,9 +1,9 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import {withIronSessionApiRoute} from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {Session} from "@/hooks/useSessions"; import { Session } from "@/hooks/useSessions";
import moment from "moment"; import moment from "moment";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -17,14 +17,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
async function get(req: NextApiRequest, res: NextApiResponse) { async function get(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ok: false}); res.status(401).json({ ok: false });
return; return;
} }
const {user} = req.query as {user?: string}; const { user } = req.query as { user?: string };
const q = user ? {user: user} : {}; const q = user ? { user: user } : {};
const sessions = await db.collection("sessions").find<Session>(q).limit(10).toArray(); const sessions = await db.collection("sessions").find<Session>({
...q,
}).limit(12).toArray();
console.log(sessions)
res.status(200).json( res.status(200).json(
sessions.filter((x) => { sessions.filter((x) => {
@@ -37,12 +40,12 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
async function post(req: NextApiRequest, res: NextApiResponse) { async function post(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ok: false}); res.status(401).json({ ok: false });
return; return;
} }
const session = req.body; const session = req.body;
await db.collection("sessions").updateOne({id: session.id}, {$set: session}, {upsert: true}); await db.collection("sessions").updateOne({ id: session.id }, { $set: session }, { upsert: true });
res.status(200).json({ok: true}); res.status(200).json({ ok: true });
} }