ENCOA-222 & ENCOA-223
ENCOA-222: Added an option for non-assignment exams to view the transcript of a Listening audio; ENCOA-223: Updated the Listening exam to show all of the exercises/questions of each part on a single page;
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
||||
import {MatchSentenceExerciseOption, MatchSentenceExerciseSentence, MatchSentencesExercise} from "@/interfaces/exam";
|
||||
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
||||
import { errorButtonStyle, infoButtonStyle } from "@/constants/buttonStyles";
|
||||
import { MatchSentenceExerciseOption, MatchSentenceExerciseSentence, MatchSentencesExercise } from "@/interfaces/exam";
|
||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import clsx from "clsx";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import LineTo from "react-lineto";
|
||||
import {CommonProps} from ".";
|
||||
import { CommonProps } from ".";
|
||||
import Button from "../Low/Button";
|
||||
import Xarrow from "react-xarrows";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import {DndContext, DragEndEvent, useDraggable, useDroppable} from "@dnd-kit/core";
|
||||
import { DndContext, DragEndEvent, useDraggable, useDroppable } from "@dnd-kit/core";
|
||||
|
||||
function DroppableQuestionArea({question, answer}: {question: MatchSentenceExerciseSentence; answer?: string}) {
|
||||
const {isOver, setNodeRef} = useDroppable({id: `droppable_sentence_${question.id}`});
|
||||
function DroppableQuestionArea({ question, answer }: { question: MatchSentenceExerciseSentence; answer?: string }) {
|
||||
const { isOver, setNodeRef } = useDroppable({ id: `droppable_sentence_${question.id}` });
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4" ref={setNodeRef}>
|
||||
@@ -35,16 +35,16 @@ function DroppableQuestionArea({question, answer}: {question: MatchSentenceExerc
|
||||
);
|
||||
}
|
||||
|
||||
function DraggableOptionArea({option}: {option: MatchSentenceExerciseOption}) {
|
||||
const {attributes, listeners, setNodeRef, transform} = useDraggable({
|
||||
function DraggableOptionArea({ option }: { option: MatchSentenceExerciseOption }) {
|
||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||
id: `draggable_option_${option.id}`,
|
||||
});
|
||||
|
||||
const style = transform
|
||||
? {
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||
zIndex: 99,
|
||||
}
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||
zIndex: 99,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
@@ -63,15 +63,25 @@ function DraggableOptionArea({option}: {option: MatchSentenceExerciseOption}) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function MatchSentences({id, options, type, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
|
||||
const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions);
|
||||
export default function MatchSentences({
|
||||
id,
|
||||
options,
|
||||
type,
|
||||
prompt,
|
||||
sentences,
|
||||
userSolutions,
|
||||
onNext,
|
||||
onBack,
|
||||
disableProgressButtons = false
|
||||
}: MatchSentencesExercise & CommonProps) {
|
||||
const [answers, setAnswers] = useState<{ question: string; option: string }[]>(userSolutions);
|
||||
|
||||
const hasExamEnded = useExamStore((state) => state.hasExamEnded);
|
||||
|
||||
const setCurrentSolution = useExamStore((state) => state.setCurrentSolution);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentSolution({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [answers, setAnswers]);
|
||||
|
||||
@@ -80,7 +90,7 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
||||
const optionID = event.active.id.toString().replace("draggable_option_", "");
|
||||
const sentenceID = event.over.id.toString().replace("droppable_sentence_", "");
|
||||
|
||||
setAnswers((prev) => [...prev.filter((x) => x.question.toString() !== sentenceID), {question: sentenceID, option: optionID}]);
|
||||
setAnswers((prev) => [...prev.filter((x) => x.question.toString() !== sentenceID), { question: sentenceID, option: optionID }]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,34 +101,43 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
||||
).length;
|
||||
const missing = total - answers.filter((x) => sentences.find((y) => y.id.toString() === x.question.toString())).length;
|
||||
|
||||
return {total, correct, missing};
|
||||
return { total, correct, missing };
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||
if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [answers, disableProgressButtons])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasExamEnded]);
|
||||
|
||||
const progressButtons = () => (
|
||||
<div className="flex justify-between w-full gap-8">
|
||||
<Button
|
||||
color="purple"
|
||||
variant="outline"
|
||||
onClick={() => onBack({ exercise: id, solutions: answers, score: calculateScore(), type })}
|
||||
className="max-w-[200px] w-full">
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="purple"
|
||||
onClick={() => onNext({ exercise: id, solutions: answers, score: calculateScore(), type })}
|
||||
className="max-w-[200px] self-end w-full">
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<div className="flex justify-between w-full gap-8">
|
||||
<Button
|
||||
color="purple"
|
||||
variant="outline"
|
||||
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||
className="max-w-[200px] w-full">
|
||||
Back
|
||||
</Button>
|
||||
{!disableProgressButtons && progressButtons()}
|
||||
|
||||
<Button
|
||||
color="purple"
|
||||
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||
className="max-w-[200px] self-end w-full">
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 mt-4 h-full w-full mb-20">
|
||||
<div className={clsx("flex flex-col gap-4 mt-4", !disableProgressButtons && "mb-20")}>
|
||||
<span className="text-sm w-full leading-6">
|
||||
{prompt.split("\\n").map((line, index) => (
|
||||
<Fragment key={index}>
|
||||
@@ -151,22 +170,7 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
||||
</DndContext>
|
||||
</div>
|
||||
|
||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||
<Button
|
||||
color="purple"
|
||||
variant="outline"
|
||||
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||
className="max-w-[200px] w-full">
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="purple"
|
||||
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||
className="max-w-[200px] self-end w-full">
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
{!disableProgressButtons && progressButtons()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user