Updated the reading to a new format
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
text: {
|
parts: {
|
||||||
title: string;
|
text: {
|
||||||
content: string;
|
title: string;
|
||||||
};
|
content: string;
|
||||||
|
};
|
||||||
|
exercises: Exercise[];
|
||||||
|
}[];
|
||||||
id: string;
|
id: string;
|
||||||
exercises: Exercise[];
|
|
||||||
module: "reading";
|
module: "reading";
|
||||||
minTimer: number;
|
minTimer: number;
|
||||||
type: "academic" | "general";
|
type: "academic" | "general";
|
||||||
|
|||||||
@@ -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(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user