Updated the reading to a new format

This commit is contained in:
Tiago Ribeiro
2023-09-28 14:43:43 +01:00
parent a568950aa9
commit 169ae2c959
9 changed files with 100 additions and 31 deletions

View File

@@ -19,11 +19,13 @@ export default function FillBlanksSolutions({id, type, prompt, solutions, text,
const renderLines = (line: string) => { const renderLines = (line: string) => {
return ( return (
<span> <span>
{reactStringReplace(line, /({{\d}})/g, (match) => { {reactStringReplace(line, /({{\d+}})/g, (match) => {
const id = match.replaceAll(/[\{\}]/g, ""); const id = match.replaceAll(/[\{\}]/g, "");
const userSolution = userSolutions.find((x) => x.id === id); const userSolution = userSolutions.find((x) => x.id === id);
const solution = solutions.find((x) => x.id === id)!; const solution = solutions.find((x) => x.id === id)!;
console.log(id, match);
if (!userSolution) { if (!userSolution) {
return ( return (
<button <button

View File

@@ -53,13 +53,22 @@ export default function Finish({user, scores, modules, isLoading, onViewResults}
}, },
}; };
const getTotalExercises = () => {
const exam = exams.find((x) => x.module === selectedModule)!;
if (exam.module === "reading") {
return exam.parts.flatMap((x) => x.exercises).length;
}
return exam.exercises.length;
};
return ( return (
<> <>
<div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8"> <div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8">
<ModuleTitle <ModuleTitle
module={selectedModule} module={selectedModule}
totalExercises={exams.find((x) => x.module === selectedModule)!.exercises.length} totalExercises={getTotalExercises()}
exerciseIndex={exams.find((x) => x.module === selectedModule)!.exercises.length} exerciseIndex={getTotalExercises()}
minTimer={exams.find((x) => x.module === selectedModule)!.minTimer} minTimer={exams.find((x) => x.module === selectedModule)!.minTimer}
disableTimer disableTimer
/> />

View File

@@ -81,8 +81,11 @@ function TextModal({isOpen, title, content, onClose}: {isOpen: boolean; title: s
export default function Reading({exam, showSolutions = false, onFinish}: Props) { export default function Reading({exam, showSolutions = false, onFinish}: Props) {
const [exerciseIndex, setExerciseIndex] = useState(showSolutions ? 0 : -1); const [exerciseIndex, setExerciseIndex] = useState(showSolutions ? 0 : -1);
const [partIndex, setPartIndex] = useState(0);
const [showTextModal, setShowTextModal] = useState(false); const [showTextModal, setShowTextModal] = useState(false);
const [userSolutions, setUserSolutions] = useState<UserSolution[]>(exam.exercises.map((x) => defaultUserSolutions(x, exam))); const [userSolutions, setUserSolutions] = useState<UserSolution[]>(
exam.parts.flatMap((x) => x.exercises).map((x) => defaultUserSolutions(x, exam)),
);
const [showBlankModal, setShowBlankModal] = useState(false); const [showBlankModal, setShowBlankModal] = useState(false);
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]); const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
@@ -121,11 +124,17 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
setUserSolutions((prev) => [...prev.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions((prev) => [...prev.filter((x) => x.exercise !== solution.exercise), solution]);
} }
if (exerciseIndex + 1 < exam.exercises.length && !hasExamEnded) { if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) {
setExerciseIndex((prev) => prev + 1); setExerciseIndex((prev) => prev + 1);
return; return;
} }
if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
setPartIndex((prev) => prev + 1);
setExerciseIndex(showSolutions ? 0 : -1);
return;
}
if ( if (
solution && solution &&
![...userSolutions.filter((x) => x.exercise !== solution?.exercise).map((x) => x.score.missing), solution?.score.missing].every( ![...userSolutions.filter((x) => x.exercise !== solution?.exercise).map((x) => x.score.missing), solution?.score.missing].every(
@@ -158,7 +167,7 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
}; };
const getExercise = () => { const getExercise = () => {
const exercise = exam.exercises[exerciseIndex]; const exercise = exam.parts[partIndex].exercises[exerciseIndex];
return { return {
...exercise, ...exercise,
userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [], userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
@@ -174,10 +183,10 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
<span className="text-base">You will be allowed to read the text while doing the exercises</span> <span className="text-base">You will be allowed to read the text while doing the exercises</span>
</div> </div>
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full">
<h3 className="text-xl font-semibold">{exam.text.title}</h3> <h3 className="text-xl font-semibold">{exam.parts[partIndex].text.title}</h3>
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" /> <div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
<span className="overflow-auto"> <span className="overflow-auto">
{exam.text.content.split("\\n").map((line, index) => ( {exam.parts[partIndex].text.content.split("\\n").map((line, index) => (
<p key={index}>{line}</p> <p key={index}>{line}</p>
))} ))}
</span> </span>
@@ -189,27 +198,33 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
<> <>
<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} />
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} /> <TextModal {...exam.parts[partIndex].text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
<ModuleTitle <ModuleTitle
minTimer={exam.minTimer} minTimer={exam.minTimer}
exerciseIndex={exerciseIndex + 1} exerciseIndex={
(exam.parts
.flatMap((x) => x.exercises)
.findIndex(
(x) => x.id === exam.parts[partIndex].exercises[exerciseIndex === -1 ? exerciseIndex + 1 : exerciseIndex]?.id,
) || 0) + (exerciseIndex === -1 ? 0 : 1)
}
module="reading" module="reading"
totalExercises={exam.exercises.length} totalExercises={exam.parts.flatMap((x) => x.exercises).length}
disableTimer={showSolutions} disableTimer={showSolutions}
label={exerciseIndex === -1 ? undefined : convertCamelCaseToReadable(exam.exercises[exerciseIndex].type)} label={exerciseIndex === -1 ? undefined : convertCamelCaseToReadable(exam.parts[partIndex].exercises[exerciseIndex].type)}
/> />
<div className={clsx("mb-20 w-full", exerciseIndex > -1 && "grid grid-cols-2 gap-4")}> <div className={clsx("mb-20 w-full", exerciseIndex > -1 && "grid grid-cols-2 gap-4")}>
{renderText()} {renderText()}
{exerciseIndex > -1 && {exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length && exerciseIndex < exam.parts[partIndex].exercises.length &&
!showSolutions && !showSolutions &&
renderExercise(getExercise(), nextExercise, previousExercise)} renderExercise(getExercise(), nextExercise, previousExercise)}
{exerciseIndex > -1 && {exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length && exerciseIndex < exam.parts[partIndex].exercises.length &&
showSolutions && showSolutions &&
renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)} renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise)}
</div> </div>
{exerciseIndex > -1 && exerciseIndex < exam.exercises.length && ( {exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && (
<Button <Button
color="purple" color="purple"
variant="outline" variant="outline"
@@ -219,7 +234,25 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
</Button> </Button>
)} )}
</div> </div>
{exerciseIndex === -1 && ( {exerciseIndex === -1 && partIndex > 0 && (
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<Button
color="purple"
variant="outline"
onClick={() => {
setExerciseIndex(exam.parts[partIndex - 1].exercises.length - 1);
setPartIndex((prev) => prev - 1);
}}
className="max-w-[200px] w-full">
Back
</Button>
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
Next
</Button>
</div>
)}
{exerciseIndex === -1 && partIndex === 0 && (
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full"> <Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
Start now Start now
</Button> </Button>

View File

@@ -3,12 +3,14 @@ import {Module} from ".";
export type Exam = ReadingExam | ListeningExam | WritingExam | SpeakingExam; export type Exam = ReadingExam | ListeningExam | WritingExam | SpeakingExam;
export interface ReadingExam { export interface ReadingExam {
parts: {
text: { text: {
title: string; title: string;
content: string; content: string;
}; };
id: string;
exercises: Exercise[]; exercises: Exercise[];
}[];
id: string;
module: "reading"; module: "reading";
minTimer: number; minTimer: number;
type: "academic" | "general"; type: "academic" | "general";

View File

@@ -45,6 +45,15 @@ export default function ExamList() {
router.push("/exercises"); router.push("/exercises");
}; };
const getTotalExercises = (module: Module) => {
const exam = exams.find((x) => x.module === module)!;
if (exam.module === "reading") {
return exam.parts.flatMap((x) => x.exercises).length;
}
return exam.exercises.length;
};
const defaultColumns = [ const defaultColumns = [
columnHelper.accessor("id", { columnHelper.accessor("id", {
header: "ID", header: "ID",
@@ -54,7 +63,7 @@ export default function ExamList() {
header: "Module", header: "Module",
cell: (info) => <span className={CLASSES[info.getValue()]}>{capitalize(info.getValue())}</span>, cell: (info) => <span className={CLASSES[info.getValue()]}>{capitalize(info.getValue())}</span>,
}), }),
columnHelper.accessor((x) => x.exercises.length, { columnHelper.accessor((x) => getTotalExercises(x.module), {
header: "Exercises", header: "Exercises",
cell: (info) => info.getValue(), cell: (info) => info.getValue(),
}), }),

View File

@@ -21,7 +21,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const {module, avoidRepeated} = req.query as {module: string; avoidRepeated: string}; const {module, avoidRepeated} = req.query as {module: string; avoidRepeated: string};
const moduleRef = collection(db, module); const moduleRef = collection(db, module);
const q = query(moduleRef, where("isDiagnostic", "==", false)); const q = query(moduleRef, where("isDiagnostic", "==", true));
const snapshot = await getDocs(q); const snapshot = await getDocs(q);
const exams: Exam[] = shuffle( const exams: Exam[] = shuffle(

View File

@@ -137,10 +137,16 @@ export default function Page() {
}; };
const updateExamWithUserSolutions = (exam: Exam): Exam => { const updateExamWithUserSolutions = (exam: Exam): Exam => {
const exercises = exam.exercises.map((x) => if (exam.module === "reading") {
Object.assign(x, !x.userSolutions ? {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions} : x.userSolutions), 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}); return Object.assign(exam, {exercises});
}; };

View File

@@ -141,8 +141,16 @@ export default function Page() {
}; };
const updateExamWithUserSolutions = (exam: Exam): Exam => { const updateExamWithUserSolutions = (exam: Exam): Exam => {
const exercises = exam.exercises.map((x) => Object.assign(x, {userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions})); if (exam.module === "reading") {
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}); return Object.assign(exam, {exercises});
}; };

View File

@@ -1,9 +1,9 @@
import {Evaluation, Exam, InteractiveSpeakingExercise, SpeakingExercise, UserSolution, WritingExercise} from "@/interfaces/exam"; import {Evaluation, Exam, SpeakingExam, SpeakingExercise, UserSolution, WritingExam, WritingExercise} from "@/interfaces/exam";
import axios from "axios"; import axios from "axios";
import {speakingReverseMarking, writingReverseMarking} from "./score"; import {speakingReverseMarking, writingReverseMarking} from "./score";
export const evaluateWritingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => { export const evaluateWritingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => {
const writingExam = exams.find((x) => x.id === examId)!; const writingExam = exams.find((x) => x.id === examId)! as WritingExam;
const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise; const exercise = writingExam.exercises.find((x) => x.id === exerciseId)! as WritingExercise;
const response = await axios.post<Evaluation>("/api/evaluate/writing", { const response = await axios.post<Evaluation>("/api/evaluate/writing", {
@@ -27,7 +27,7 @@ export const evaluateWritingAnswer = async (exams: Exam[], examId: string, exerc
}; };
export const evaluateSpeakingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => { export const evaluateSpeakingAnswer = async (exams: Exam[], examId: string, exerciseId: string, solution: UserSolution) => {
const speakingExam = exams.find((x) => x.id === examId)!; const speakingExam = exams.find((x) => x.id === examId)! as SpeakingExam;
const exercise = speakingExam.exercises.find((x) => x.id === exerciseId); const exercise = speakingExam.exercises.find((x) => x.id === exerciseId);
switch (exercise?.type) { switch (exercise?.type) {