ENCOA-268
This commit is contained in:
26
src/components/PracticeModal.tsx
Normal file
26
src/components/PracticeModal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -132,7 +134,7 @@ 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, assignment, timesListened, setShowTextModal, setTimesListened])
|
/>, [partIndex, assignment, timesListened, setShowTextModal, setTimesListened])
|
||||||
|
|
||||||
const memoizedInstructions = useMemo(()=>
|
const memoizedInstructions = useMemo(() =>
|
||||||
<RenderAudioInstructionsPlayer />
|
<RenderAudioInstructionsPlayer />
|
||||||
, [])
|
, [])
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -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)} />*/}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,7 +82,7 @@ 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]);
|
||||||
|
|
||||||
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user