window.location.reload()}
- // disabled={user.type === "admin"}
- // TODO: temporarily disabled
- disabled
+ onClick={() => router.push(destination || "/exam")}
+ disabled={!!assignment}
className="bg-mti-purple-light hover:bg-mti-purple flex h-11 w-11 items-center justify-center rounded-full transition duration-300 ease-in-out">
@@ -325,7 +326,7 @@ export default function Finish({user, scores, modules, information, solutions, i
)}
-
+
Dashboard
diff --git a/src/exams/Level/index.tsx b/src/exams/Level/index.tsx
index 3038b617..baef3ca1 100644
--- a/src/exams/Level/index.tsx
+++ b/src/exams/Level/index.tsx
@@ -18,62 +18,64 @@ import { Tab } from "@headlessui/react";
import Modal from "@/components/Modal";
import { typeCheckWordsMC } from "@/utils/type.check";
import SectionNavbar from "../Navigation/SectionNavbar";
+import AudioPlayer from "@/components/Low/AudioPlayer";
interface Props {
- exam: LevelExam;
- showSolutions?: boolean;
- onFinish: (userSolutions: UserSolution[]) => void;
- preview?: boolean;
- partDividers?: boolean;
+ exam: LevelExam;
+ showSolutions?: boolean;
+ onFinish: (userSolutions: UserSolution[]) => void;
+ preview?: boolean;
+ partDividers?: boolean;
}
export default function Level({ exam, showSolutions = false, onFinish, preview = false }: Props) {
- const levelBgColor = "bg-ielts-level-light";
+ const levelBgColor = "bg-ielts-level-light";
- const examState = useExamStore((state) => state);
- const persistentExamState = usePersistentExamStore((state) => state);
+ const examState = useExamStore((state) => state);
+ const persistentExamState = usePersistentExamStore((state) => state);
- const {
- userSolutions,
- hasExamEnded,
- partIndex,
- exerciseIndex,
- questionIndex,
- shuffles,
- currentSolution,
- setBgColor,
- setUserSolutions,
- setHasExamEnded,
- setPartIndex,
- setExerciseIndex,
- setQuestionIndex,
- setShuffles,
- setCurrentSolution
- } = !preview ? examState : persistentExamState;
+ const {
+ userSolutions,
+ hasExamEnded,
+ partIndex,
+ exerciseIndex,
+ questionIndex,
+ shuffles,
+ currentSolution,
+ setBgColor,
+ setUserSolutions,
+ setHasExamEnded,
+ setPartIndex,
+ setExerciseIndex,
+ setQuestionIndex,
+ setShuffles,
+ setCurrentSolution
+ } = !preview ? examState : persistentExamState;
- // In case client want to switch back
- const textRenderDisabled = true;
+ // In case client want to switch back
+ const textRenderDisabled = true;
- const [showSubmissionModal, setShowSubmissionModal] = useState(false);
- const [showQuestionsModal, setShowQuestionsModal] = useState(false);
- const [continueAnyways, setContinueAnyways] = useState(false);
- const [textRender, setTextRender] = useState(false);
- const [changedPrompt, setChangedPrompt] = useState(false);
- const [nextExerciseCalled, setNextExerciseCalled] = useState(false);
- const [currentSolutionSet, setCurrentSolutionSet] = useState(false);
+ const [timesListened, setTimesListened] = useState(0);
+ const [showSubmissionModal, setShowSubmissionModal] = useState(false);
+ const [showQuestionsModal, setShowQuestionsModal] = useState(false);
+ const [continueAnyways, setContinueAnyways] = useState(false);
+ const [textRender, setTextRender] = useState(false);
+ const [changedPrompt, setChangedPrompt] = useState(false);
+ const [nextExerciseCalled, setNextExerciseCalled] = useState(false);
+ const [currentSolutionSet, setCurrentSolutionSet] = useState(false);
- const [seenParts, setSeenParts] = useState
>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0]));
+ const [seenParts, setSeenParts] = useState>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0]));
- const [questionModalKwargs, setQuestionModalKwargs] = useState<{
- type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
- }>({
- type: "blankQuestions",
- onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
- });
+ const [questionModalKwargs, setQuestionModalKwargs] = useState<{
+ type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
+ }>({
+ type: "blankQuestions",
+ onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
+ });
- const [currentExercise, setCurrentExercise] = useState(undefined);
- const [showPartDivider, setShowPartDivider] = useState(typeof exam.parts[0].intro === "string" && !showSolutions);
- const [startNow, setStartNow] = useState(!showSolutions);
+ const [currentExercise, setCurrentExercise] = useState(undefined);
+ const [showPartDivider, setShowPartDivider] = useState(typeof exam.parts[0].intro === "string" && !showSolutions);
+ const [startNow, setStartNow] = useState(!showSolutions);
useEffect(() => {
@@ -84,445 +86,483 @@ export default function Level({ exam, showSolutions = false, onFinish, preview =
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [exerciseIndex]);
- useEffect(() => {
- if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) {
- setCurrentExercise(exam.parts[0].exercises[0]);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentExercise, partIndex, exerciseIndex]);
+ useEffect(() => {
+ if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) {
+ setCurrentExercise(exam.parts[0].exercises[0]);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentExercise, partIndex, exerciseIndex]);
- const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
+ const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
- const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined);
- const [contextWordLines, setContextWordLines] = useState(undefined);
- const [totalLines, setTotalLines] = useState(0);
+ const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined);
+ const [contextWordLines, setContextWordLines] = useState(undefined);
+ const [totalLines, setTotalLines] = useState(0);
- const [showSolutionsSave, setShowSolutionsSave] = useState(showSolutions ? userSolutions.filter((x) => x.module === "level") : undefined)
+ const [showSolutionsSave, setShowSolutionsSave] = useState(showSolutions ? userSolutions.filter((x) => x.module === "level") : undefined)
- useEffect(() => {
- if (typeof currentSolution !== "undefined") {
- setUserSolutions([...userSolutions.filter((x) => x.exercise !== currentSolution.exercise), { ...currentSolution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]);
- setCurrentSolutionSet(true);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentSolution, exam.id, exam.shuffle, shuffles, currentExercise])
+ useEffect(() => {
+ if (typeof currentSolution !== "undefined") {
+ setUserSolutions([...userSolutions.filter((x) => x.exercise !== currentSolution.exercise), { ...currentSolution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]);
+ setCurrentSolutionSet(true);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentSolution, exam.id, exam.shuffle, shuffles, currentExercise])
- useEffect(() => {
- if (typeof currentSolution !== "undefined") {
- setCurrentSolution(undefined);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentSolution]);
+ useEffect(() => {
+ if (typeof currentSolution !== "undefined") {
+ setCurrentSolution(undefined);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentSolution]);
- useEffect(() => {
- if (showSolutions) {
- const solutionShuffles = userSolutions.map(solution => ({
- exerciseID: solution.exercise,
- shuffles: solution.shuffleMaps || []
- }));
- setShuffles(solutionShuffles);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ useEffect(() => {
+ if (showSolutions) {
+ const solutionShuffles = userSolutions.map(solution => ({
+ exerciseID: solution.exercise,
+ shuffles: solution.shuffleMaps || []
+ }));
+ setShuffles(solutionShuffles);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
- const getExercise = () => {
- let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
- exercise = {
- ...exercise,
- userSolutions: userSolutions.find((x) => x.exercise == exercise.id)?.solutions || [],
- };
- exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles);
- return exercise;
- };
+ const getExercise = () => {
+ let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
+ exercise = {
+ ...exercise,
+ userSolutions: userSolutions.find((x) => x.exercise == exercise.id)?.solutions || [],
+ };
+ exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles);
+ return exercise;
+ };
- useEffect(() => {
- setCurrentExercise(getExercise());
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [partIndex, exerciseIndex, questionIndex]);
+ useEffect(() => {
+ setCurrentExercise(getExercise());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [partIndex, exerciseIndex, questionIndex]);
- const next = () => {
- setNextExerciseCalled(true);
- }
+ const next = () => {
+ setNextExerciseCalled(true);
+ }
- const nextExercise = () => {
- scrollToTop();
+ const nextExercise = () => {
+ scrollToTop();
- if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) {
- setExerciseIndex(exerciseIndex + 1);
- setCurrentSolutionSet(false);
- return;
- }
+ if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) {
+ setExerciseIndex(exerciseIndex + 1);
+ setCurrentSolutionSet(false);
+ return;
+ }
- if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) {
- modalKwargs();
- setShowQuestionsModal(true);
- return;
- }
+ if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) {
+ modalKwargs();
+ setShowQuestionsModal(true);
+ return;
+ }
- if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
- if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) {
- modalKwargs();
- setShowQuestionsModal(true);
- return;
- }
+ if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
+ if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) {
+ modalKwargs();
+ setShowQuestionsModal(true);
+ return;
+ }
- if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) {
- setShowPartDivider(true);
- setBgColor(levelBgColor);
- }
+ if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) {
+ setShowPartDivider(true);
+ setBgColor(levelBgColor);
+ }
- setSeenParts(prev => new Set(prev).add(partIndex + 1));
+ setSeenParts(prev => new Set(prev).add(partIndex + 1));
- if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) {
- setTextRender(true);
- }
- setPartIndex(partIndex + 1);
- setExerciseIndex(0);
- setQuestionIndex(0);
- setCurrentSolutionSet(false);
- return;
- }
+ if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) {
+ setTextRender(true);
+ }
- if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) {
- modalKwargs();
- setShowQuestionsModal(true);
- }
+ setTimesListened(0);
+ setPartIndex(partIndex + 1);
+ setExerciseIndex(0);
+ setQuestionIndex(0);
+ setCurrentSolutionSet(false);
+ return;
+ }
- setHasExamEnded(false);
- setCurrentSolutionSet(false);
- if (typeof showSolutionsSave !== "undefined") {
- onFinish(showSolutionsSave);
- } else {
- onFinish(userSolutions);
- }
- }
+ if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) {
+ modalKwargs();
+ setShowQuestionsModal(true);
+ }
- useEffect(() => {
- if (nextExerciseCalled && currentSolutionSet) {
- nextExercise();
- setNextExerciseCalled(false);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [nextExerciseCalled, currentSolutionSet])
+ setHasExamEnded(false);
+ setCurrentSolutionSet(false);
+ if (typeof showSolutionsSave !== "undefined") {
+ onFinish(showSolutionsSave);
+ } else {
+ onFinish(userSolutions);
+ }
+ }
- const previousExercise = (solution?: UserSolution) => {
- scrollToTop();
+ useEffect(() => {
+ if (nextExerciseCalled && currentSolutionSet) {
+ nextExercise();
+ setNextExerciseCalled(false);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [nextExerciseCalled, currentSolutionSet])
- if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) {
- setTextRender(true);
- return;
- }
+ const previousExercise = (solution?: UserSolution) => {
+ scrollToTop();
- if (questionIndex == 0) {
- setPartIndex(partIndex - 1);
- if (!seenParts.has(partIndex - 1)) {
- setBgColor(levelBgColor);
- setShowPartDivider(true);
- setQuestionIndex(0);
- setSeenParts(prev => new Set(prev).add(partIndex - 1));
- return;
- }
+ if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) {
+ setTextRender(true);
+ return;
+ }
- const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
- const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex];
- setExerciseIndex(lastExerciseIndex);
+ if (questionIndex == 0) {
+ setPartIndex(partIndex - 1);
+ if (!seenParts.has(partIndex - 1)) {
+ setBgColor(levelBgColor);
+ setShowPartDivider(true);
+ setQuestionIndex(0);
+ setSeenParts(prev => new Set(prev).add(partIndex - 1));
+ return;
+ }
- if (lastExercise.type === "multipleChoice") {
- setQuestionIndex(lastExercise.questions.length - 1)
- } else {
- setQuestionIndex(0)
- }
- return;
- }
+ const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
+ const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex];
+ setExerciseIndex(lastExerciseIndex);
- setExerciseIndex(exerciseIndex - 1);
- if (exerciseIndex - 1 === -1) {
- setPartIndex(partIndex - 1);
- const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
- const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex];
- if (previousExercise.type === "multipleChoice") {
- setQuestionIndex(previousExercise.questions.length - 1)
- }
- }
+ if (lastExercise.type === "multipleChoice") {
+ setQuestionIndex(lastExercise.questions.length - 1)
+ } else {
+ setQuestionIndex(0)
+ }
+ return;
+ }
- };
+ setExerciseIndex(exerciseIndex - 1);
+ if (exerciseIndex - 1 === -1) {
+ setPartIndex(partIndex - 1);
+ const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
+ const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex];
+ if (previousExercise.type === "multipleChoice") {
+ setQuestionIndex(previousExercise.questions.length - 1)
+ }
+ }
- const calculateExerciseIndex = () => {
- return exam.parts.reduce((acc, curr, index) => {
- if (index < partIndex) {
- return acc + countExercises(curr.exercises)
- }
- return acc;
- }, 0) + (questionIndex + 1);
- };
+ };
- const renderText = () => (
- <>
-
- <>
-
- {textRender && !textRenderDisabled ? (
- <>
-
- Please read the following excerpt attentively, you will then be asked questions about the text you've read.
-
-
You will be allowed to read the text while doing the exercises
- >
- ) : (
-
- Answer the questions on the right based on what you've read.
-
- )}
-
- {exam.parts[partIndex].context &&
-
}
-
- >
-
- {textRender && !textRenderDisabled && (
-
- { setTextRender(false); previousExercise(); }}
- >
- Back
-
+ const calculateExerciseIndex = () => {
+ return exam.parts.reduce((acc, curr, index) => {
+ if (index < partIndex) {
+ return acc + countExercises(curr.exercises)
+ }
+ return acc;
+ }, 0) + (questionIndex + 1);
+ };
- setTextRender(false)} className="max-w-[200px] self-end w-full">
- Next
-
-
- )}
- >
- );
+ const renderAudioPlayer = () => (
+
+ {exam?.parts[partIndex]?.audio?.source ? (
+ <>
+
+
Please listen to the following audio attentively.
+
+ {(() => {
+ const audioRepeatTimes = exam?.parts[partIndex]?.audio?.repeatableTimes;
+ return audioRepeatTimes && audioRepeatTimes > 0
+ ? `You will only be allowed to listen to the audio ${audioRepeatTimes - timesListened} time(s).`
+ : "You may listen to the audio as many times as you would like.";
+ })()}
+
+
+
+
setTimesListened((prev) => prev + 1)}
+ disabled={exam?.parts[partIndex]?.audio?.repeatableTimes != null &&
+ timesListened === exam.parts[partIndex]?.audio?.repeatableTimes}
+ disablePause
+ />
+
+ >
+ ) : (
+
This section will be displayed the audio once it has been generated.
+ )}
- const partLabel = () => {
- const partCategory = exam.parts[partIndex].category ? ` (${exam.parts[partIndex].category})` : '';
- if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words))
- 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") {
- return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})${partCategory}\n\n${currentExercise.prompt}`
- }
+ const renderText = () => (
+ <>
+
+ <>
+
+ {textRender && !textRenderDisabled ? (
+ <>
+
+ Please read the following excerpt attentively, you will then be asked questions about the text you've read.
+
+
You will be allowed to read the text while doing the exercises
+ >
+ ) : (
+
+ Answer the questions on the right based on what you've read.
+
+ )}
+
+ {(exam.parts[partIndex].context || exam.parts[partIndex].text) &&
+
}
+
+ >
+
+ {textRender && !textRenderDisabled && (
+
+ { setTextRender(false); previousExercise(); }}
+ >
+ Back
+
- if (typeof exam.parts[partIndex].context === "string") {
- 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})${partCategory}\n\n${nextExercise.prompt}`
- }
- }
+ setTextRender(false)} className="max-w-[200px] self-end w-full">
+ Next
+
+
+ )}
+ >
+ );
- const answeredEveryQuestion = (partIndex: number) => {
- return exam.parts[partIndex].exercises.every((exercise) => {
- const userSolution = userSolutions.find(x => x.exercise === exercise.id);
- switch (exercise.type) {
- case 'multipleChoice':
- return userSolution?.solutions.length === exercise.questions.length;
- case 'fillBlanks':
- return userSolution?.solutions.length === exercise.words.length;
- case 'writeBlanks':
- return userSolution?.solutions.length === exercise.solutions.length;
- case 'matchSentences':
- return userSolution?.solutions.length === exercise.sentences.length;
- case 'trueFalse':
- return userSolution?.solutions.length === exercise.questions.length;
- }
- return false;
- });
- }
+ const partLabel = () => {
+ const partCategory = exam.parts[partIndex].category ? ` (${exam.parts[partIndex].category})` : '';
+ if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words))
+ return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})${partCategory}\n\n${currentExercise.prompt}`
- useEffect(() => {
- const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
+ if (currentExercise?.type === "multipleChoice") {
+ return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})${partCategory}\n\n${currentExercise.prompt}`
+ }
- const findMatch = (index: number) => {
- if (currentExercise && currentExercise.type === "multipleChoice" && currentExercise!.questions[index]) {
- const match = currentExercise!.questions[index].prompt.match(regex);
- if (match) {
- return { match: match[1], originalLine: match[2] }
- }
- }
- return;
- }
+ if (typeof exam.parts[partIndex].context === "string") {
+ 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})${partCategory}\n\n${nextExercise.prompt}`
+ }
+ }
- // if the client for some whatever random reason decides
- // to add more questions update this
- const numberOfQuestions = 2;
+ const answeredEveryQuestion = (partIndex: number) => {
+ return exam.parts[partIndex].exercises.every((exercise) => {
+ const userSolution = userSolutions.find(x => x.exercise === exercise.id);
+ switch (exercise.type) {
+ case 'multipleChoice':
+ return userSolution?.solutions.length === exercise.questions.length;
+ case 'fillBlanks':
+ return userSolution?.solutions.length === exercise.words.length;
+ case 'writeBlanks':
+ return userSolution?.solutions.length === exercise.solutions.length;
+ case 'matchSentences':
+ return userSolution?.solutions.length === exercise.sentences.length;
+ case 'trueFalse':
+ return userSolution?.solutions.length === exercise.questions.length;
+ }
+ return false;
+ });
+ }
- if (exam.parts[partIndex].context) {
- const hits = Array.from({ length: numberOfQuestions }).reduce<{ match: string, originalLine: string }[]>((acc, _, i) => {
- const result = findMatch(questionIndex + i);
- if (!!result) {
- acc.push(result);
- }
- return acc;
- }, []);
+ useEffect(() => {
+ const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
- if (hits.length > 0) {
- setContextWords(hits)
- }
- }
+ const findMatch = (index: number) => {
+ if (currentExercise && currentExercise.type === "multipleChoice" && currentExercise!.questions[index]) {
+ const match = currentExercise!.questions[index].prompt.match(regex);
+ if (match) {
+ return { match: match[1], originalLine: match[2] }
+ }
+ }
+ return;
+ }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [currentExercise, questionIndex, totalLines]);
+ // if the client for some whatever random reason decides
+ // to add more questions update this
+ const numberOfQuestions = 2;
- useEffect(() => {
- if (
- exerciseIndex !== -1 && currentExercise &&
- currentExercise.type === "multipleChoice" &&
- exam.parts[partIndex].context && contextWordLines
- ) {
- if (contextWordLines.length > 0) {
- contextWordLines.forEach((n, i) => {
- if (contextWords && contextWords[i] && n !== -1) {
- const updatedPrompt = currentExercise!.questions[questionIndex + i].prompt.replace(
- `in line ${contextWords[i].originalLine}`,
- `in line ${n}`
- );
- currentExercise!.questions[questionIndex + i].prompt = updatedPrompt;
- }
- })
- setChangedPrompt(true);
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [contextWordLines]);
+ if (exam.parts[partIndex].context) {
+ const hits = Array.from({ length: numberOfQuestions }).reduce<{ match: string, originalLine: string }[]>((acc, _, i) => {
+ const result = findMatch(questionIndex + i);
+ if (!!result) {
+ acc.push(result);
+ }
+ return acc;
+ }, []);
+
+ if (hits.length > 0) {
+ setContextWords(hits)
+ }
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentExercise, questionIndex, totalLines]);
+
+ useEffect(() => {
+ if (
+ exerciseIndex !== -1 && currentExercise &&
+ currentExercise.type === "multipleChoice" &&
+ exam.parts[partIndex].context && contextWordLines
+ ) {
+ if (contextWordLines.length > 0) {
+ contextWordLines.forEach((n, i) => {
+ if (contextWords && contextWords[i] && n !== -1) {
+ const updatedPrompt = currentExercise!.questions[questionIndex + i].prompt.replace(
+ `in line ${contextWords[i].originalLine}`,
+ `in line ${n}`
+ );
+ currentExercise!.questions[questionIndex + i].prompt = updatedPrompt;
+ }
+ })
+ setChangedPrompt(true);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [contextWordLines]);
- useEffect(() => {
- if (continueAnyways) {
- setContinueAnyways(false);
- nextExercise();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [continueAnyways]);
+ useEffect(() => {
+ if (continueAnyways) {
+ setContinueAnyways(false);
+ nextExercise();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [continueAnyways]);
- const modalKwargs = () => {
- const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = {
- type: "blankQuestions",
- unanswered: false,
- onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }
- };
+ const modalKwargs = () => {
+ const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = {
+ type: "blankQuestions",
+ unanswered: false,
+ onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }
+ };
- if (partIndex === exam.parts.length - 1) {
- kwargs.type = "submit"
- kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex));
- kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } };
- }
- setQuestionModalKwargs(kwargs);
- }
+ if (partIndex === exam.parts.length - 1) {
+ kwargs.type = "submit"
+ kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex));
+ kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } };
+ }
+ setQuestionModalKwargs(kwargs);
+ }
- const mcNavKwargs = {
- userSolutions: userSolutions,
- exam: exam,
- partIndex: partIndex,
- showSolutions: showSolutions,
- setExerciseIndex: setExerciseIndex,
- setPartIndex: setPartIndex,
- runOnClick: setQuestionIndex
- }
+ const mcNavKwargs = {
+ userSolutions: userSolutions,
+ exam: exam,
+ partIndex: partIndex,
+ showSolutions: showSolutions,
+ setExerciseIndex: setExerciseIndex,
+ setPartIndex: setPartIndex,
+ runOnClick: setQuestionIndex
+ }
- const memoizedRender = useMemo(() => {
- setChangedPrompt(false);
- return (
- <>
- {textRender && !textRenderDisabled ?
- renderText() :
- <>
- {exam.parts[partIndex]?.context && renderText()}
- {(showSolutions) ?
- currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) :
- currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise)
- }
- >
- }
- >)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [textRender, currentExercise, changedPrompt]);
+ const memoizedRender = useMemo(() => {
+ setChangedPrompt(false);
+ return (
+ <>
+ {textRender && !textRenderDisabled ?
+ renderText() :
+ <>
+ {exam.parts[partIndex]?.context && renderText()}
+ {exam.parts[partIndex]?.audio && renderAudioPlayer()}
+ {(showSolutions) ?
+ currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) :
+ currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise)
+ }
+ >
+ }
+ >
+ )
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [textRender, currentExercise, changedPrompt]);
- return (
- <>
-
-
{ }}
- title={"Confirm Submission"}
- >
- <>
- Are you sure you want to proceed with the submission?
-
- setShowSubmissionModal(false)} variant="outline" className="max-w-[200px] self-end w-full !text-xl">
- Cancel
-
- { setShowSubmissionModal(false); setContinueAnyways(true) }} className="max-w-[200px] self-end w-full !text-xl">
- Confirm
-
-
- >
-
-
- {
- !(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
-
- }
- {(showPartDivider || startNow) ?
-
{ setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }}
- /> : (
- <>
- {exam.parts[0].intro && exam.parts.length !== 1 && (
- {
- setExerciseIndex(0);
- setQuestionIndex(0);
- if (!seenParts.has(index)) {
- setShowPartDivider(true);
- setBgColor(levelBgColor);
- setSeenParts(prev => new Set(prev).add(index));
- }
- }
- } />
- )}
- x.exercises))}
- disableTimer={showSolutions}
- showTimer={false}
- {...mcNavKwargs}
- />
-
- {memoizedRender}
-
- >
- )}
-
- >
- );
+ return (
+ <>
+
+
{ }}
+ title={"Confirm Submission"}
+ >
+ <>
+ Are you sure you want to proceed with the submission?
+
+ setShowSubmissionModal(false)} variant="outline" className="max-w-[200px] self-end w-full !text-xl">
+ Cancel
+
+ { setShowSubmissionModal(false); setContinueAnyways(true) }} className="max-w-[200px] self-end w-full !text-xl">
+ Confirm
+
+
+ >
+
+
+ {
+ !(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
+
+ }
+ {(showPartDivider || startNow) ?
+
{ setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }}
+ /> : (
+ <>
+ {exam.parts[0].intro && (
+ {
+ setExerciseIndex(0);
+ setQuestionIndex(0);
+ if (!seenParts.has(index)) {
+ setShowPartDivider(true);
+ setBgColor(levelBgColor);
+ setSeenParts(prev => new Set(prev).add(index));
+ }
+ }
+ } />
+ )}
+ x.exercises))}
+ disableTimer={showSolutions}
+ showTimer={false}
+ {...mcNavKwargs}
+ />
+
+ {memoizedRender}
+
+ >
+ )}
+
+ >
+ );
}
diff --git a/src/interfaces/results.ts b/src/interfaces/results.ts
index cd9ff373..58832a4f 100644
--- a/src/interfaces/results.ts
+++ b/src/interfaces/results.ts
@@ -1,8 +1,8 @@
-import {Module} from "@/interfaces";
-import {InstructorGender} from "./exam";
-import {Stat} from "./user";
+import { Module } from "@/interfaces";
+import { InstructorGender } from "./exam";
+import { Stat } from "./user";
-export type UserResults = {[key in Module]: ModuleResult};
+export type UserResults = { [key in Module]: ModuleResult };
interface ModuleResult {
exams: string[];
@@ -22,7 +22,7 @@ export interface Assignment {
assigner: string;
assignees: string[];
results: AssignmentResult[];
- exams: {id: string; module: Module; assignee: string}[];
+ exams: { id: string; module: Module; assignee: string }[];
instructorGender?: InstructorGender;
startDate: Date;
endDate: Date;
@@ -32,9 +32,8 @@ export interface Assignment {
// unless start is active, the assignment is not visible to the assignees
// start date now works as a limit time to start the exam
start?: boolean;
- autoStartDate?: Date;
autoStart?: boolean;
entity?: string;
}
-export type AssignmentWithCorporateId = Assignment & {corporateId: string};
+export type AssignmentWithCorporateId = Assignment & { corporateId: string };
diff --git a/src/pages/(exam)/ExamPage.tsx b/src/pages/(exam)/ExamPage.tsx
index 79069e30..480310ac 100644
--- a/src/pages/(exam)/ExamPage.tsx
+++ b/src/pages/(exam)/ExamPage.tsx
@@ -1,6 +1,6 @@
/* eslint-disable @next/next/no-img-element */
-import {Module} from "@/interfaces";
-import {useEffect, useState} from "react";
+import { Module } from "@/interfaces";
+import { useEffect, useState } from "react";
import AbandonPopup from "@/components/AbandonPopup";
import Layout from "@/components/High/Layout";
@@ -12,15 +12,15 @@ import Selection from "@/exams/Selection";
import Speaking from "@/exams/Speaking";
import Writing from "@/exams/Writing";
import useUser from "@/hooks/useUser";
-import {Exam, LevelExam, UserSolution, Variant} from "@/interfaces/exam";
-import {Stat, User} from "@/interfaces/user";
+import { Exam, LevelExam, UserSolution, Variant } from "@/interfaces/exam";
+import { Stat, User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore";
-import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation";
-import {defaultExamUserSolutions, getExam} from "@/utils/exams";
+import { evaluateSpeakingAnswer, evaluateWritingAnswer } from "@/utils/evaluation";
+import { defaultExamUserSolutions, getExam } from "@/utils/exams";
import axios from "axios";
-import {useRouter} from "next/router";
-import {toast, ToastContainer} from "react-toastify";
-import {v4 as uuidv4} from "uuid";
+import { useRouter } from "next/router";
+import { toast, ToastContainer } from "react-toastify";
+import { v4 as uuidv4 } from "uuid";
import useSessions from "@/hooks/useSessions";
import ShortUniqueId from "short-unique-id";
import clsx from "clsx";
@@ -31,10 +31,11 @@ import { mapBy } from "@/utils";
interface Props {
page: "exams" | "exercises";
user: User;
+ destination?: string
hideSidebar?: boolean
}
-export default function ExamPage({page, user, hideSidebar = false}: Props) {
+export default function ExamPage({ page, user, destination = "/exam", hideSidebar = false }: Props) {
const [variant, setVariant] = useState("full");
const [avoidRepeated, setAvoidRepeated] = useState(false);
const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
@@ -49,18 +50,18 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
const assignment = useExamStore((state) => state.assignment);
const initialTimeSpent = useExamStore((state) => state.timeSpent);
- const {exam, setExam} = useExamStore((state) => state);
- const {exams, setExams} = useExamStore((state) => state);
- const {sessionId, setSessionId} = useExamStore((state) => state);
- const {partIndex, setPartIndex} = useExamStore((state) => state);
- const {moduleIndex, setModuleIndex} = useExamStore((state) => state);
- const {questionIndex, setQuestionIndex} = useExamStore((state) => state);
- const {exerciseIndex, setExerciseIndex} = useExamStore((state) => state);
- const {userSolutions, setUserSolutions} = useExamStore((state) => state);
- const {showSolutions, setShowSolutions} = useExamStore((state) => state);
- const {selectedModules, setSelectedModules} = useExamStore((state) => state);
- const {inactivity, setInactivity} = useExamStore((state) => state);
- const {bgColor, setBgColor} = useExamStore((state) => state);
+ const { exam, setExam } = useExamStore((state) => state);
+ const { exams, setExams } = useExamStore((state) => state);
+ const { sessionId, setSessionId } = useExamStore((state) => state);
+ const { partIndex, setPartIndex } = useExamStore((state) => state);
+ const { moduleIndex, setModuleIndex } = useExamStore((state) => state);
+ const { questionIndex, setQuestionIndex } = useExamStore((state) => state);
+ const { exerciseIndex, setExerciseIndex } = useExamStore((state) => state);
+ const { userSolutions, setUserSolutions } = useExamStore((state) => state);
+ const { showSolutions, setShowSolutions } = useExamStore((state) => state);
+ const { selectedModules, setSelectedModules } = useExamStore((state) => state);
+ const { inactivity, setInactivity } = useExamStore((state) => state);
+ const { bgColor, setBgColor } = useExamStore((state) => state);
const setShuffleMaps = useExamStore((state) => state.setShuffles);
const router = useRouter();
@@ -262,11 +263,11 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
date: new Date().getTime(),
isDisabled: solution.isDisabled,
shuffleMaps: solution.shuffleMaps,
- ...(assignment ? {assignment: assignment.id} : {}),
+ ...(assignment ? { assignment: assignment.id } : {}),
}));
axios
- .post<{ok: boolean}>("/api/stats", newStats)
+ .post<{ ok: boolean }>("/api/stats", newStats)
.then((response) => setHasBeenUploaded(response.data.ok))
.catch(() => setHasBeenUploaded(false));
}
@@ -329,7 +330,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
),
}),
);
- return Object.assign(exam, {parts});
+ return Object.assign(exam, { parts });
}
const exercises = exam.exercises.map((x) =>
@@ -337,7 +338,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions,
}),
);
- return Object.assign(exam, {exercises});
+ return Object.assign(exam, { exercises });
};
const onFinish = async (solutions: UserSolution[]) => {
@@ -392,7 +393,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
correct: number;
}[] => {
const scores: {
- [key in Module]: {total: number; missing: number; correct: number};
+ [key in Module]: { total: number; missing: number; correct: number };
} = {
reading: {
total: 0,
@@ -434,7 +435,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
return Object.keys(scores)
.filter((x) => scores[x as Module].total > 0)
- .map((x) => ({module: x as Module, ...scores[x as Module]}));
+ .map((x) => ({ module: x as Module, ...scores[x as Module] }));
};
const renderScreen = () => {
@@ -465,6 +466,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
timeSpent,
inactivity: totalInactivity,
}}
+ destination={destination}
onViewResults={(index?: number) => {
if (exams[0].module === "level") {
const levelExam = exams[0] as LevelExam;
diff --git a/src/pages/assignments/creator/[id].tsx b/src/pages/assignments/creator/[id].tsx
index 4f3e8bca..0f8dcdf1 100644
--- a/src/pages/assignments/creator/[id].tsx
+++ b/src/pages/assignments/creator/[id].tsx
@@ -6,43 +6,43 @@ import ProgressBar from "@/components/Low/ProgressBar";
import Select from "@/components/Low/Select";
import Separator from "@/components/Low/Separator";
import useExams from "@/hooks/useExams";
-import {useListSearch} from "@/hooks/useListSearch";
+import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";
-import {Module} from "@/interfaces";
-import {EntityWithRoles} from "@/interfaces/entity";
-import {InstructorGender, Variant} from "@/interfaces/exam";
-import {Assignment} from "@/interfaces/results";
-import {Group, User} from "@/interfaces/user";
-import {sessionOptions} from "@/lib/session";
-import {mapBy, redirect, serialize} from "@/utils";
+import { Module } from "@/interfaces";
+import { EntityWithRoles } from "@/interfaces/entity";
+import { InstructorGender, Variant } from "@/interfaces/exam";
+import { Assignment } from "@/interfaces/results";
+import { Group, User } from "@/interfaces/user";
+import { sessionOptions } from "@/lib/session";
+import { mapBy, redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
-import {getAssignment} from "@/utils/assignments.be";
-import {getEntitiesWithRoles} from "@/utils/entities.be";
-import {getGroups, getGroupsByEntities} from "@/utils/groups.be";
-import {checkAccess, doesEntityAllow, findAllowedEntities} from "@/utils/permissions";
-import {calculateAverageLevel} from "@/utils/score";
-import {getEntitiesUsers, getUsers} from "@/utils/users.be";
+import { getAssignment } from "@/utils/assignments.be";
+import { getEntitiesWithRoles } from "@/utils/entities.be";
+import { getGroups, getGroupsByEntities } from "@/utils/groups.be";
+import { checkAccess, doesEntityAllow, findAllowedEntities } from "@/utils/permissions";
+import { calculateAverageLevel } from "@/utils/score";
+import { getEntitiesUsers, getUsers } from "@/utils/users.be";
import axios from "axios";
import clsx from "clsx";
-import {withIronSessionSsr} from "iron-session/next";
-import {capitalize} from "lodash";
+import { withIronSessionSsr } from "iron-session/next";
+import { capitalize } from "lodash";
import moment from "moment";
import Head from "next/head";
import Link from "next/link";
-import {useRouter} from "next/router";
-import {generate} from "random-words";
-import {useEffect, useMemo, useState} from "react";
+import { useRouter } from "next/router";
+import { generate } from "random-words";
+import { useEffect, useMemo, useState } from "react";
import ReactDatePicker from "react-datepicker";
-import {BsBook, BsCheckCircle, BsChevronLeft, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs";
-import {toast} from "react-toastify";
+import { BsBook, BsCheckCircle, BsChevronLeft, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle } from "react-icons/bs";
+import { toast } from "react-toastify";
-export const getServerSideProps = withIronSessionSsr(async ({req, res, params}) => {
+export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
res.setHeader("Cache-Control", "public, s-maxage=10, stale-while-revalidate=59");
- const {id} = params as {id: string};
+ const { id } = params as { id: string };
const entityIDS = mapBy(user.entities, "id") || [];
const assignment = await getAssignment(id);
@@ -59,7 +59,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, params})
const users = await (checkAccess(user, ["developer", "admin"]) ? getUsers() : getEntitiesUsers(mapBy(allowedEntities, 'id')));
const groups = await (checkAccess(user, ["developer", "admin"]) ? getGroups() : getGroupsByEntities(mapBy(allowedEntities, 'id')));
- return {props: serialize({user, users, entities: allowedEntities, assignment, groups})};
+ return { props: serialize({ user, users, entities: allowedEntities, assignment, groups }) };
}, sessionOptions);
interface Props {
@@ -72,7 +72,7 @@ interface Props {
const SIZE = 9;
-export default function AssignmentsPage({assignment, user, users, entities, groups}: Props) {
+export default function AssignmentsPage({ assignment, user, users, entities, groups }: Props) {
const [selectedModules, setSelectedModules] = useState(assignment.exams.map((e) => e.module));
const [assignees, setAssignees] = useState(assignment.assignees);
const [teachers, setTeachers] = useState(assignment.teachers || []);
@@ -90,12 +90,11 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
const [released, setReleased] = useState(assignment.released || false);
const [autoStart, setAutostart] = useState(assignment.autoStart || false);
- const [autoStartDate, setAutoStartDate] = useState(moment(assignment.autoStartDate).toDate());
const [useRandomExams, setUseRandomExams] = useState(true);
- const [examIDs, setExamIDs] = useState<{id: string; module: Module}[]>([]);
+ const [examIDs, setExamIDs] = useState<{ id: string; module: Module }[]>([]);
- const {exams} = useExams();
+ const { exams } = useExams();
const router = useRouter();
const classrooms = useMemo(() => groups.filter((e) => e.entity === entity), [entity, groups]);
@@ -103,11 +102,11 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
const userStudents = useMemo(() => users.filter((x) => x.type === "student"), [users]);
const userTeachers = useMemo(() => users.filter((x) => x.type === "teacher"), [users]);
- const {rows: filteredStudentsRows, renderSearch: renderStudentSearch} = useListSearch([["name"], ["email"]], userStudents);
- const {rows: filteredTeachersRows, renderSearch: renderTeacherSearch} = useListSearch([["name"], ["email"]], userTeachers);
+ const { rows: filteredStudentsRows, renderSearch: renderStudentSearch } = useListSearch([["name"], ["email"]], userStudents);
+ const { rows: filteredTeachersRows, renderSearch: renderTeacherSearch } = useListSearch([["name"], ["email"]], userTeachers);
- const {items: studentRows, renderMinimal: renderStudentPagination} = usePagination(filteredStudentsRows, SIZE);
- const {items: teacherRows, renderMinimal: renderTeacherPagination} = usePagination(filteredTeachersRows, SIZE);
+ const { items: studentRows, renderMinimal: renderStudentPagination } = usePagination(filteredStudentsRows, SIZE);
+ const { items: teacherRows, renderMinimal: renderTeacherPagination } = usePagination(filteredTeachersRows, SIZE);
useEffect(() => {
setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module)));
@@ -148,7 +147,6 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
instructorGender,
released,
autoStart,
- autoStartDate,
})
.then(() => {
toast.success(`The assignment "${name}" has been updated successfully!`);
@@ -316,9 +314,9 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
setName(e)} defaultValue={name} label="Assignment Name" required />
({value: e.id, label: e.label}))}
+ options={entities.map((e) => ({ value: e.id, label: e.label }))}
onChange={(v) => setEntity(v ? v.value! : undefined)}
- defaultValue={{value: entities[0]?.id, label: entities[0]?.label}}
+ defaultValue={{ value: entities[0]?.id, label: entities[0]?.label }}
/>
@@ -355,24 +353,6 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
onChange={(date) => setEndDate(date)}
/>
- {autoStart && (
-