ENCOA-149, ENCOA-150, ENCOA-152, ENCOA-153, ENCOA-155, ENCOA-156, ENCOA-157, ENCOA-158, ENCOA-161 -> Updated the mc buttons, no longer shows a context only div on parts that have context, removed line numbers on lines between paragraphs, applied bold and underline to 'not correct' in underline prompts, added another pop up to confirm submission.

This commit is contained in:
Carlos Mesquita
2024-09-04 02:26:22 +01:00
parent 60554d8e16
commit 5168e70edc
8 changed files with 106 additions and 94 deletions

View File

@@ -173,23 +173,11 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
className={clsx( className={clsx(
"flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base", "flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base",
!!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) && !!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) &&
"border-mti-purple-light", "!bg-mti-purple-light !text-white",
)}> )}>
<span className="font-semibold">{key}.</span> <span className="font-semibold">{key}.</span>
<span>{value}</span> <span>{value}</span>
</div> </div>
/*<button
className={clsx(
"border border-mti-purple-light rounded-full px-3 py-0.5 transition ease-in-out duration-300",
!!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) &&
"bg-mti-purple-dark text-white",
)}
key={v4()}
onClick={() => onSelection(currentMCSelection.id, value)}
>
{value}
</button>;*/
})} })}
</div> </div>
</div> </div>

View File

@@ -60,7 +60,7 @@ function Question({
onClick={() => (onSelectOption ? onSelectOption(option.id.toString()) : null)} onClick={() => (onSelectOption ? onSelectOption(option.id.toString()) : null)}
className={clsx( className={clsx(
"flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base select-none", "flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base select-none",
userSolution === option.id.toString() && "border-mti-purple-light", userSolution === option.id.toString() && "!bg-mti-purple-light !text-white",
)}> )}>
<span className="font-semibold">{option.id.toString()}.</span> <span className="font-semibold">{option.id.toString()}.</span>
<span>{option.text}</span> <span>{option.text}</span>

View File

@@ -1,15 +1,13 @@
import { Module } from "@/interfaces"; import { Module } from "@/interfaces";
import { moduleLabels } from "@/utils/moduleUtils"; import { moduleLabels } from "@/utils/moduleUtils";
import clsx from "clsx"; import clsx from "clsx";
import { Fragment, ReactNode, useCallback, useState } from "react"; import { ReactNode, useState } from "react";
import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsStopwatch } from "react-icons/bs"; import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsStopwatch } from "react-icons/bs";
import ProgressBar from "../Low/ProgressBar"; import ProgressBar from "../Low/ProgressBar";
import Timer from "./Timer"; import Timer from "./Timer";
import { Exam, Exercise, LevelExam, MultipleChoiceExercise, ShuffleMap, UserSolution } from "@/interfaces/exam"; import { Exercise, LevelExam, MultipleChoiceExercise, ShuffleMap, UserSolution } from "@/interfaces/exam";
import { BsFillGrid3X3GapFill } from "react-icons/bs"; import { BsFillGrid3X3GapFill } from "react-icons/bs";
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import Button from "../Low/Button"; import Button from "../Low/Button";
import { Dialog, Transition } from "@headlessui/react";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import Modal from "../Modal"; import Modal from "../Modal";
import React from "react"; import React from "react";
@@ -145,7 +143,7 @@ export default function ModuleTitle({
return ( return (
<div key={index} className="text-2xl font-semibold flex flex-col gap-2"> <div key={index} className="text-2xl font-semibold flex flex-col gap-2">
{partInstructions.split("\\n").map((line, lineIndex) => ( {partInstructions.split("\\n").map((line, lineIndex) => (
<span key={lineIndex}>{line}</span> <span key={lineIndex} dangerouslySetInnerHTML={{__html: line.replace('that is not correct', 'that is <span class="font-bold"><u>not correct</u></span>')}}></span>
))} ))}
</div> </div>
); );

View File

@@ -7,10 +7,11 @@ interface Props {
onClose: () => void; onClose: () => void;
title?: string; title?: string;
className?: string; className?: string;
titleClassName?: string;
children?: ReactElement; children?: ReactElement;
} }
export default function Modal({isOpen, title, className, onClose, children}: Props) { export default function Modal({isOpen, title, className, titleClassName, onClose, children}: Props) {
return ( return (
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-[200]" onClose={onClose}> <Dialog as="div" className="relative z-[200]" onClose={onClose}>
@@ -41,7 +42,7 @@ export default function Modal({isOpen, title, className, onClose, children}: Pro
className, className,
)}> )}>
{title && ( {title && (
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900"> <Dialog.Title as="h3" className={clsx(titleClassName ? titleClassName : "text-lg font-medium leading-6 text-gray-900")}>
{title} {title}
</Dialog.Title> </Dialog.Title>
)} )}

View File

@@ -25,7 +25,7 @@ const PartDivider: React.FC<Props> = ({ partIndex, part, onNext }) => {
<div className={clsx("flex flex-col h-fit border bg-white rounded-3xl p-12 gap-8", part.intro ? "w-3/6" : "items-center my-auto")}> <div className={clsx("flex flex-col h-fit border bg-white rounded-3xl p-12 gap-8", part.intro ? "w-3/6" : "items-center my-auto")}>
{/** only level for now */} {/** only level for now */}
<div className="flex flex-row gap-4 items-center"><div className="w-12 h-12 bg-ielts-level flex items-center justify-center rounded-lg">{moduleIcon["level"]}</div><p className="text-3xl">{part.intro ? `Part ${partIndex + 1}` : "Placement Test"}</p></div> <div className="flex flex-row gap-4 items-center"><div className="w-12 h-12 bg-ielts-level flex items-center justify-center rounded-lg">{moduleIcon["level"]}</div><p className="text-3xl">{part.intro ? `Part ${partIndex + 1}` : "Placement Test"}</p></div>
{part.intro && part.intro.split('\\n\\n').map((x, index) => <p key={`line-${index}`} className="text-2xl text-clip">{x}</p>)} {part.intro && part.intro.split('\\n\\n').map((x, index) => <p key={`line-${index}`} className="text-2xl text-clip" dangerouslySetInnerHTML={{__html: x.replace('that is not correct', 'that is <span class="font-bold"><u>not correct</u></span>')}}></p>)}
<div className="flex items-center justify-center mt-4"> <div className="flex items-center justify-center mt-4">
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full text-2xl"> <Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full text-2xl">
{partIndex === 0 ? `Start now`: `Start Part ${partIndex + 1}`} {partIndex === 0 ? `Start now`: `Start Part ${partIndex + 1}`}

View File

@@ -11,6 +11,7 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
const textRef = useRef<HTMLDivElement>(null); const textRef = useRef<HTMLDivElement>(null);
const [lineNumbers, setLineNumbers] = useState<number[]>([]); const [lineNumbers, setLineNumbers] = useState<number[]>([]);
const [lineHeight, setLineHeight] = useState<number>(0); const [lineHeight, setLineHeight] = useState<number>(0);
const [addBreaksTo, setAddBreaksTo] = useState<number[]>([]);
const calculateLineNumbers = () => { const calculateLineNumbers = () => {
if (textRef.current) { if (textRef.current) {
@@ -31,17 +32,33 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign; offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
const textContent = textRef.current.textContent || ''; const textContent = textRef.current.textContent || '';
const lines = textContent.split(/\n/).map(line =>
line.split(/(\s+)/).map(word => { const paragraphs = textContent.split(/\n\n/);
const betweenParagraphs: string[][] = Array.from({ length: paragraphs.length }, () => []);
const lines = paragraphs.map((line, lineIndex) => {
const paragraphWords = line.split(/(\s+)/);
return paragraphWords.map((word, wordIndex) => {
if (lineIndex !== 0 && wordIndex == 0 && lineIndex < paragraphs.length) {
betweenParagraphs[lineIndex - 1][1] = word;
}
if (wordIndex == paragraphWords.length - 1 && lineIndex < paragraphs.length) {
betweenParagraphs[lineIndex][0] = word;
}
const span = document.createElement('span'); const span = document.createElement('span');
span.textContent = word; span.textContent = word;
return span; return span;
}) })
}
); );
// Append all spans to offscreenElement
lines.forEach(line => { lines.forEach(line => {
line.forEach(span => offscreenElement.appendChild(span)); line.forEach((span, index) => {
offscreenElement.appendChild(span);
});
offscreenElement.appendChild(document.createElement('br')); offscreenElement.appendChild(document.createElement('br'));
}); });
@@ -59,10 +76,21 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
const spans = offscreenElement.querySelectorAll<HTMLSpanElement>('span'); const spans = offscreenElement.querySelectorAll<HTMLSpanElement>('span');
spans.forEach(span => { let betweenIndex = 0;
const addBreaksTo: number[] = [];
spans.forEach((span, index)=> {
const rect = span.getBoundingClientRect(); const rect = span.getBoundingClientRect();
const top = rect.top; const top = rect.top;
if (
betweenIndex < paragraphs.length - 1 &&
span.textContent === betweenParagraphs[betweenIndex][1] &&
spans[index - 1].textContent === betweenParagraphs[betweenIndex][0]
) {
addBreaksTo.push(currentLine);
betweenIndex = betweenIndex + 1;
}
if (currentLineTop !== undefined && top > currentLineTop) { if (currentLineTop !== undefined && top > currentLineTop) {
currentLine++; currentLine++;
currentLineTop = top; currentLineTop = top;
@@ -75,6 +103,9 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
contextWordLine = currentLine; contextWordLine = currentLine;
} }
}); });
setAddBreaksTo(addBreaksTo);
setLineNumbers(processedLines.map((_, index) => index + 1)); setLineNumbers(processedLines.map((_, index) => index + 1));
if (contextWordLine) { if (contextWordLine) {
setContextWordLine(contextWordLine); setContextWordLine(contextWordLine);
@@ -85,7 +116,6 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
}; };
useEffect(() => { useEffect(() => {
calculateLineNumbers(); calculateLineNumbers();
@@ -107,30 +137,17 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [part.context, contextWord]); }, [part.context, contextWord]);
/*if (typeof part.showContextLines === "undefined") {
return (
<div className="flex flex-col gap-2 w-full">
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
{!!part.context &&
part.context
.split(/\n|(\\n)/g)
.filter((x) => x && x.length > 0 && x !== "\\n")
.map((line, index) => (
<Fragment key={index}>
<p key={index}>{line}</p>
</Fragment>
))}
</div>
);
}*/
return ( return (
<div className="flex mt-2"> <div className="flex mt-2">
<div className="flex-shrink-0 w-8 pr-2"> <div className="flex-shrink-0 w-8 pr-2">
{lineNumbers.map(num => ( {lineNumbers.map(num => (
<>
<div key={num} className="text-gray-400 flex justify-end" style={{ lineHeight: `${lineHeight}px` }}> <div key={num} className="text-gray-400 flex justify-end" style={{ lineHeight: `${lineHeight}px` }}>
{num} {num}
</div> </div>
{/* Do not delete the space between the span or else the lines get messed up */}
{addBreaksTo.includes(num) && <span className={`h-[${lineHeight}px] whitespace-pre-wrap`}> </span>}
</>
))} ))}
</div> </div>
<div ref={textRef} className="h-fit whitespace-pre-wrap ml-2"> <div ref={textRef} className="h-fit whitespace-pre-wrap ml-2">

View File

@@ -14,6 +14,7 @@ import PartDivider from "./PartDivider";
import Timer from "@/components/Medium/Timer"; import Timer from "@/components/Medium/Timer";
import shuffleExamExercise from "./Shuffle"; import shuffleExamExercise from "./Shuffle";
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
import Modal from "@/components/Modal";
interface Props { interface Props {
exam: LevelExam; exam: LevelExam;
@@ -51,7 +52,10 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
setCurrentSolution setCurrentSolution
} = useExamStore((state) => state); } = useExamStore((state) => state);
const [multipleChoicesDone, setMultipleChoicesDone] = useState<{ id: string; amount: number }[]>([]); // In case client want to switch back
const textRenderDisabled = true;
const [showSubmissionModal, setShowSubmissionModal] = useState(false);
const [showQuestionsModal, setShowQuestionsModal] = useState(false); const [showQuestionsModal, setShowQuestionsModal] = useState(false);
const [continueAnyways, setContinueAnyways] = useState(false); const [continueAnyways, setContinueAnyways] = useState(false);
const [textRender, setTextRender] = useState(false); const [textRender, setTextRender] = useState(false);
@@ -59,7 +63,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
const [nextExerciseCalled, setNextExerciseCalled] = useState(false); const [nextExerciseCalled, setNextExerciseCalled] = useState(false);
const [currentSolutionSet, setCurrentSolutionSet] = useState(false); const [currentSolutionSet, setCurrentSolutionSet] = useState(false);
const [seenParts, setSeenParts] = useState<number[]>(showSolutions ? exam.parts.map((_, index) => index) : [0]); const [seenParts, setSeenParts] = useState<Set<number>>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0]));
const [questionModalKwargs, setQuestionModalKwargs] = useState<{ const [questionModalKwargs, setQuestionModalKwargs] = useState<{
type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined; type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
@@ -147,24 +151,25 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
} }
if (partIndex + 1 < exam.parts.length && !hasExamEnded) { if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.includes(partIndex + 1)) { if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) {
modalKwargs(); modalKwargs();
setShowQuestionsModal(true); setShowQuestionsModal(true);
return; return;
} }
if (!showSolutions && exam.parts[0].intro && !seenParts.includes(partIndex + 1)) { if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) {
setShowPartDivider(true); setShowPartDivider(true);
setBgColor(levelBgColor); setBgColor(levelBgColor);
} }
setSeenParts((prev) => [...prev, partIndex + 1])
if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context) { setSeenParts(prev => new Set(prev).add(partIndex + 1));
if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) {
setTextRender(true); setTextRender(true);
} }
setPartIndex(partIndex + 1); setPartIndex(partIndex + 1);
setExerciseIndex(0); setExerciseIndex(0);
setQuestionIndex(0); setQuestionIndex(0);
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : questionIndex }]);
setCurrentSolutionSet(false); setCurrentSolutionSet(false);
return; return;
} }
@@ -194,7 +199,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (exam.parts[partIndex].context && questionIndex === 0 && !textRender) { if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) {
setTextRender(true); setTextRender(true);
return; return;
} }
@@ -221,31 +226,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
if (previousExercise.type === "multipleChoice") { if (previousExercise.type === "multipleChoice") {
setQuestionIndex(previousExercise.questions.length - 1) setQuestionIndex(previousExercise.questions.length - 1)
} }
const multipleChoiceQuestionsDone = [];
for (let i = 0; i < exam.parts.length; i++) {
if (i == (partIndex - 1)) break;
for (let j = 0; j < exam.parts[i].exercises.length; j++) {
const exercise = exam.parts[i].exercises[j];
switch(exercise.type) {
case 'multipleChoice':
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.questions.length - 1 })
break;
case 'fillBlanks':
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.words.length - 1 })
break;
case 'writeBlanks':
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.solutions.length - 1 })
break;
case 'matchSentences':
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.sentences.length - 1})
break;
case 'trueFalse':
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.questions.length - 1})
break;
}
}
}
setMultipleChoicesDone(multipleChoiceQuestionsDone);
} }
}; };
@@ -264,7 +244,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
<div className={clsx("flex flex-col gap-6 w-full bg-mti-gray-seasalt rounded-xl mt-4 relative py-8 px-16")}> <div className={clsx("flex flex-col gap-6 w-full bg-mti-gray-seasalt rounded-xl mt-4 relative py-8 px-16")}>
<> <>
<div className="flex flex-col w-full gap-2"> <div className="flex flex-col w-full gap-2">
{textRender ? ( {textRender && !textRenderDisabled ? (
<> <>
<h4 className="text-xl font-semibold"> <h4 className="text-xl font-semibold">
Please read the following excerpt attentively, you will then be asked questions about the text you&apos;ve read. Please read the following excerpt attentively, you will then be asked questions about the text you&apos;ve read.
@@ -286,7 +266,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
</div> </div>
</> </>
</div> </div>
{textRender && ( {textRender && !textRenderDisabled && (
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8"> <div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<Button <Button
color="purple" color="purple"
@@ -306,16 +286,17 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
); );
const partLabel = () => { const partLabel = () => {
const partCategory = exam.parts[partIndex].category ? ` (${exam.parts[partIndex].category})` : '';
if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words)) if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words))
return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})\n\n${currentExercise.prompt}` return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})${partCategory}\n\n${currentExercise.prompt}`
if (currentExercise?.type === "multipleChoice") { if (currentExercise?.type === "multipleChoice") {
return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})\n\n${currentExercise.prompt}` return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})${partCategory}\n\n${currentExercise.prompt}`
} }
if (typeof exam.parts[partIndex].context === "string") { if (typeof exam.parts[partIndex].context === "string") {
const nextExercise = exam.parts[partIndex].exercises[0] as MultipleChoiceExercise; const nextExercise = exam.parts[partIndex].exercises[0] as MultipleChoiceExercise;
return `Part ${partIndex + 1} (Questions ${nextExercise.questions[0].id} - ${nextExercise.questions[nextExercise.questions.length - 1].id})\n\n${nextExercise.prompt}` return `Part ${partIndex + 1} (Questions ${nextExercise.questions[0].id} - ${nextExercise.questions[nextExercise.questions.length - 1].id})${partCategory}\n\n${nextExercise.prompt}`
} }
} }
@@ -388,7 +369,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
if (partIndex === exam.parts.length - 1) { if (partIndex === exam.parts.length - 1) {
kwargs.type = "submit" kwargs.type = "submit"
kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex)); kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex));
kwargs.onClose = function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }; kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } };
} }
setQuestionModalKwargs(kwargs); setQuestionModalKwargs(kwargs);
} }
@@ -408,7 +389,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
setChangedPrompt(false); setChangedPrompt(false);
return ( return (
<> <>
{textRender ? {textRender && !textRenderDisabled ?
renderText() : renderText() :
<> <>
{exam.parts[partIndex].context && renderText()} {exam.parts[partIndex].context && renderText()}
@@ -425,6 +406,25 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
return ( return (
<> <>
<div className={clsx("flex flex-col h-full w-full gap-8 items-center", showPartDivider && "justify-center")}> <div className={clsx("flex flex-col h-full w-full gap-8 items-center", showPartDivider && "justify-center")}>
<Modal
className={"!w-2/6 !p-8"}
titleClassName={"font-bold text-3xl text-mti-rose-light"}
isOpen={showSubmissionModal}
onClose={() => { }}
title={"Confirm Submission"}
>
<>
<p className="text-xl mt-8 mb-12">Are you sure you want to proceed with the submission?</p>
<div className="w-full flex justify-between">
<Button color="purple" onClick={() => setShowSubmissionModal(false)} variant="outline" className="max-w-[200px] self-end w-full !text-xl">
Cancel
</Button>
<Button color="rose" onClick={() => { setShowSubmissionModal(false); setContinueAnyways(true)}} className="max-w-[200px] self-end w-full !text-xl">
Confirm
</Button>
</div>
</>
</Modal>
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} /> <QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
{ {
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) && !(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
@@ -438,20 +438,27 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-level/20 p-1"> <Tab.List className="flex space-x-1 rounded-xl bg-ielts-level/20 p-1">
{exam.parts.map((_, index) => {exam.parts.map((_, index) =>
<Tab key={index} onClick={(e) => { <Tab key={index} onClick={(e) => {
if (!seenParts.includes(index)) { /*
// If client wants to revert uncomment and remove the added if statement
if (!seenParts.has(index)) {
e.preventDefault(); e.preventDefault();
} else { } else {
*/
setExerciseIndex(0); setExerciseIndex(0);
setQuestionIndex(0); setQuestionIndex(0);
if (!seenParts.has(index)) {
setShowPartDivider(true);
setBgColor(levelBgColor);
setSeenParts(prev => new Set(prev).add(index));
} }
}} }}
className={({ selected }) => className={({ selected }) =>
clsx( clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-level/80", "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-level/80",
"ring-white ring-opacity-60 focus:outline-none", "ring-white ring-opacity-60 focus:outline-none",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out hover:bg-white/70",
selected && "bg-white shadow", selected && "bg-white shadow",
seenParts.includes(index) ? "hover:bg-white/70" : "cursor-not-allowed" // seenParts.includes(index) ? "hover:bg-white/70" : "cursor-not-allowed"
) )
} }
>{`Part ${index + 1}`}</Tab> >{`Part ${index + 1}`}</Tab>

View File

@@ -39,6 +39,7 @@ export interface LevelExam extends ExamBase {
export interface LevelPart { export interface LevelPart {
context?: string; context?: string;
intro?: string; intro?: string;
category?: string;
exercises: Exercise[]; exercises: Exercise[];
} }