From b2dc9b2e31f0572b5670ddc02e2689e26dcc772c Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Tue, 12 Nov 2024 11:03:19 +0000 Subject: [PATCH] ENCOA-233: Added the option for certain exercises to not count towards scores --- src/components/Exercises/FillBlanks/index.tsx | 10 ++- .../Exercises/InteractiveSpeaking.tsx | 40 +++++----- src/components/Exercises/MatchSentences.tsx | 11 +-- src/components/Exercises/MultipleChoice.tsx | 11 +-- src/components/Exercises/Speaking.tsx | 6 +- src/components/Exercises/TrueFalse.tsx | 11 +-- src/components/Exercises/WriteBlanks.tsx | 11 +-- src/components/Exercises/Writing.tsx | 35 ++++---- src/components/Medium/StatGridItem.tsx | 50 ++++++------ src/interfaces/exam.ts | 11 ++- src/interfaces/user.ts | 29 +++---- src/pages/(exam)/ExamPage.tsx | 10 +-- src/pages/record.tsx | 79 +++++++++---------- 13 files changed, 165 insertions(+), 149 deletions(-) diff --git a/src/components/Exercises/FillBlanks/index.tsx b/src/components/Exercises/FillBlanks/index.tsx index 927a1bbf..d138f66e 100644 --- a/src/components/Exercises/FillBlanks/index.tsx +++ b/src/components/Exercises/FillBlanks/index.tsx @@ -11,6 +11,7 @@ import MCDropdown from "./MCDropdown"; const FillBlanks: React.FC = ({ id, type, + isPractice = false, prompt, solutions, text, @@ -40,6 +41,11 @@ const FillBlanks: React.FC = ({ const shuffleMaps = shuffles.find((x) => x.exerciseID == id)?.shuffles; const dropdownRef = useRef(null); + useEffect(() => { + if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [answers, disableProgressButtons]) + const excludeWordMCType = (x: any) => { return typeof x === "string" ? x : (x as { letter: string; word: string }); }; @@ -169,7 +175,7 @@ const FillBlanks: React.FC = ({ {preview ? ( + {questionIndex + 1 < prompts.length ? "Next Prompt" : "Submit"} + ) : ( + {questionIndex + 1 < prompts.length ? "Next Prompt" : "Submit"} + )} diff --git a/src/components/Exercises/MatchSentences.tsx b/src/components/Exercises/MatchSentences.tsx index 1235de66..0e00a90c 100644 --- a/src/components/Exercises/MatchSentences.tsx +++ b/src/components/Exercises/MatchSentences.tsx @@ -72,6 +72,7 @@ export default function MatchSentences({ userSolutions, onNext, onBack, + isPractice = false, disableProgressButtons = false }: MatchSentencesExercise & CommonProps) { const [answers, setAnswers] = useState<{ question: string; option: string }[]>(userSolutions); @@ -81,7 +82,7 @@ export default function MatchSentences({ const setCurrentSolution = useExamStore((state) => state.setCurrentSolution); useEffect(() => { - setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type }); + setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, setAnswers]); @@ -105,12 +106,12 @@ export default function MatchSentences({ }; useEffect(() => { - if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, disableProgressButtons]) useEffect(() => { - if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); @@ -119,14 +120,14 @@ export default function MatchSentences({ diff --git a/src/components/Exercises/MultipleChoice.tsx b/src/components/Exercises/MultipleChoice.tsx index 849cf74c..bed48996 100644 --- a/src/components/Exercises/MultipleChoice.tsx +++ b/src/components/Exercises/MultipleChoice.tsx @@ -78,6 +78,7 @@ export default function MultipleChoice({ type, questions, userSolutions, + isPractice = false, onNext, onBack, disableProgressButtons = false @@ -93,7 +94,7 @@ export default function MultipleChoice({ const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0)); useEffect(() => { - if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); @@ -102,7 +103,7 @@ export default function MultipleChoice({ }; useEffect(() => { - setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }); + setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, setAnswers]); @@ -140,13 +141,13 @@ export default function MultipleChoice({ }; useEffect(() => { - if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }); + if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, disableProgressButtons]) const next = () => { if (questionIndex + 1 >= questions.length - 1) { - onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }); + onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps, isPractice }); } else { setQuestionIndex(questionIndex + 2); } @@ -155,7 +156,7 @@ export default function MultipleChoice({ const back = () => { if (questionIndex === 0) { - onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }); + onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps, isPractice }); } else { if (exam?.module === "level" && typeof exam.parts[0].intro !== "undefined" && questionIndex === 0) return; setQuestionIndex(questionIndex - 2); diff --git a/src/components/Exercises/Speaking.tsx b/src/components/Exercises/Speaking.tsx index b96714c9..2614aca6 100644 --- a/src/components/Exercises/Speaking.tsx +++ b/src/components/Exercises/Speaking.tsx @@ -14,7 +14,7 @@ const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mo ssr: false, }); -export default function Speaking({ id, title, text, video_url, type, prompts, suffix, userSolutions, onNext, onBack, preview = false }: SpeakingExercise & CommonProps) { +export default function Speaking({ id, title, text, video_url, type, prompts, suffix, userSolutions, isPractice = false, onNext, onBack, preview = false }: SpeakingExercise & CommonProps) { const [recordingDuration, setRecordingDuration] = useState(0); const [isRecording, setIsRecording] = useState(false); const [mediaBlob, setMediaBlob] = useState(); @@ -81,7 +81,7 @@ export default function Speaking({ id, title, text, video_url, type, prompts, su exercise: id, solutions: mediaBlob ? [{ id, solution: mediaBlob }] : [], score: { correct: 0, total: 100, missing: 0 }, - type, + type, isPractice }); }; @@ -90,7 +90,7 @@ export default function Speaking({ id, title, text, video_url, type, prompts, su exercise: id, solutions: mediaBlob ? [{ id, solution: mediaBlob }] : [], score: { correct: 0, total: 100, missing: 0 }, - type, + type, isPractice }); }; diff --git a/src/components/Exercises/TrueFalse.tsx b/src/components/Exercises/TrueFalse.tsx index 21ad6314..4bb469ba 100644 --- a/src/components/Exercises/TrueFalse.tsx +++ b/src/components/Exercises/TrueFalse.tsx @@ -11,6 +11,7 @@ export default function TrueFalse({ prompt, questions, userSolutions, + isPractice = false, onNext, onBack, disableProgressButtons = false @@ -21,7 +22,7 @@ export default function TrueFalse({ const setCurrentSolution = useExamStore((state) => state.setCurrentSolution); useEffect(() => { - if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); @@ -40,7 +41,7 @@ export default function TrueFalse({ }; useEffect(() => { - setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type }); + setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, setAnswers]); @@ -55,7 +56,7 @@ export default function TrueFalse({ }; useEffect(() => { - if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, disableProgressButtons]) @@ -64,14 +65,14 @@ export default function TrueFalse({ diff --git a/src/components/Exercises/WriteBlanks.tsx b/src/components/Exercises/WriteBlanks.tsx index a91b2455..4492e10f 100644 --- a/src/components/Exercises/WriteBlanks.tsx +++ b/src/components/Exercises/WriteBlanks.tsx @@ -53,6 +53,7 @@ export default function WriteBlanks({ maxWords, solutions, userSolutions, + isPractice = false, text, onNext, onBack, @@ -63,7 +64,7 @@ export default function WriteBlanks({ const { hasExamEnded, setCurrentSolution } = useExamStore((state) => state); useEffect(() => { - if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); @@ -82,12 +83,12 @@ export default function WriteBlanks({ }; useEffect(() => { - setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type }); + setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, setAnswers]); useEffect(() => { - if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); + if (disableProgressButtons) onNext({ exercise: id, solutions: answers, score: calculateScore(), type, isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, disableProgressButtons]) @@ -112,14 +113,14 @@ export default function WriteBlanks({ diff --git a/src/components/Exercises/Writing.tsx b/src/components/Exercises/Writing.tsx index e173e671..00d16d1d 100644 --- a/src/components/Exercises/Writing.tsx +++ b/src/components/Exercises/Writing.tsx @@ -1,10 +1,10 @@ /* eslint-disable @next/next/no-img-element */ -import {WritingExercise} from "@/interfaces/exam"; -import {CommonProps} from "."; -import React, {Fragment, useEffect, useRef, useState} from "react"; -import {toast} from "react-toastify"; +import { WritingExercise } from "@/interfaces/exam"; +import { CommonProps } from "."; +import React, { Fragment, useEffect, useRef, useState } from "react"; +import { toast } from "react-toastify"; import Button from "../Low/Button"; -import {Dialog, Transition} from "@headlessui/react"; +import { Dialog, Transition } from "@headlessui/react"; import useExamStore from "@/stores/examStore"; export default function Writing({ @@ -16,6 +16,7 @@ export default function Writing({ wordCounter, attachment, userSolutions, + isPractice = false, onNext, onBack, enableNavigation = false @@ -25,7 +26,7 @@ export default function Writing({ const [isSubmitEnabled, setIsSubmitEnabled] = useState(false); const [saveTimer, setSaveTimer] = useState(0); - const {userSolutions: storeUserSolutions, setUserSolutions} = useExamStore((state) => state); + const { userSolutions: storeUserSolutions, setUserSolutions } = useExamStore((state) => state); const hasExamEnded = useExamStore((state) => state.hasExamEnded); useEffect(() => { @@ -42,7 +43,7 @@ export default function Writing({ if (inputText.length > 0 && saveTimer % 10 === 0) { setUserSolutions([ ...storeUserSolutions.filter((x) => x.exercise !== id), - {exercise: id, solutions: [{id, solution: inputText}], score: {correct: 100, total: 100, missing: 0}, type, module: "writing"}, + { exercise: id, solutions: [{ id, solution: inputText }], score: { correct: 100, total: 100, missing: 0 }, type, module: "writing" }, ]); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -66,7 +67,7 @@ export default function Writing({ useEffect(() => { if (hasExamEnded) - onNext({exercise: id, solutions: [{id, solution: inputText}], score: {correct: 100, total: 100, missing: 0}, type, module: "writing"}); + onNext({ exercise: id, solutions: [{ id, solution: inputText }], score: { correct: 100, total: 100, missing: 0 }, type, module: "writing", isPractice }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); @@ -78,7 +79,7 @@ export default function Writing({ } else { setIsSubmitEnabled(true); if (wordCounter.limit < words.length) { - toast.warning(`You have reached your word limit of ${wordCounter.limit} words!`, {toastId: "word-limit"}); + toast.warning(`You have reached your word limit of ${wordCounter.limit} words!`, { toastId: "word-limit" }); setInputText(words.slice(0, words.length - 1).join(" ")); } } @@ -91,7 +92,7 @@ export default function Writing({ color="purple" variant="outline" onClick={() => - onBack({exercise: id, solutions: [{id, solution: inputText}], score: {correct: 100, total: 100, missing: 0}, type}) + onBack({ exercise: id, solutions: [{ id, solution: inputText }], score: { correct: 100, total: 100, missing: 0 }, type, isPractice }) } className="max-w-[200px] self-end w-full"> Back @@ -102,10 +103,10 @@ export default function Writing({ onClick={() => onNext({ exercise: id, - solutions: [{id, solution: inputText.replaceAll(/\s{2,}/g, " ")}], - score: {correct: 100, total: 100, missing: 0}, + solutions: [{ id, solution: inputText.replaceAll(/\s{2,}/g, " ") }], + score: { correct: 100, total: 100, missing: 0 }, type, - module: "writing", + module: "writing", isPractice }) } className="max-w-[200px] self-end w-full"> @@ -177,7 +178,7 @@ export default function Writing({ color="purple" variant="outline" onClick={() => - onBack({exercise: id, solutions: [{id, solution: inputText}], score: {correct: 100, total: 100, missing: 0}, type}) + onBack({ exercise: id, solutions: [{ id, solution: inputText }], score: { correct: 100, total: 100, missing: 0 }, type, isPractice }) } className="max-w-[200px] self-end w-full"> Back @@ -188,10 +189,10 @@ export default function Writing({ onClick={() => onNext({ exercise: id, - solutions: [{id, solution: inputText.replaceAll(/\s{2,}/g, " ")}], - score: {correct: 100, total: 100, missing: 0}, + solutions: [{ id, solution: inputText.replaceAll(/\s{2,}/g, " ") }], + score: { correct: 100, total: 100, missing: 0 }, type, - module: "writing", + module: "writing", isPractice }) } className="max-w-[200px] self-end w-full"> diff --git a/src/components/Medium/StatGridItem.tsx b/src/components/Medium/StatGridItem.tsx index 82aaf0e3..be9da7b7 100644 --- a/src/components/Medium/StatGridItem.tsx +++ b/src/components/Medium/StatGridItem.tsx @@ -1,19 +1,19 @@ import React from "react"; -import {BsClock, BsXCircle} from "react-icons/bs"; +import { BsClock, BsXCircle } from "react-icons/bs"; import clsx from "clsx"; -import {Stat, User} from "@/interfaces/user"; -import {Module, Step} from "@/interfaces"; +import { Stat, User } from "@/interfaces/user"; +import { Module, Step } from "@/interfaces"; import ai_usage from "@/utils/ai.detection"; -import {calculateBandScore} from "@/utils/score"; +import { calculateBandScore } from "@/utils/score"; import moment from "moment"; -import {Assignment} from "@/interfaces/results"; -import {uuidv4} from "@firebase/util"; -import {useRouter} from "next/router"; -import {uniqBy} from "lodash"; -import {sortByModule} from "@/utils/moduleUtils"; -import {convertToUserSolutions} from "@/utils/stats"; -import {getExamById} from "@/utils/exams"; -import {Exam, UserSolution} from "@/interfaces/exam"; +import { Assignment } from "@/interfaces/results"; +import { uuidv4 } from "@firebase/util"; +import { useRouter } from "next/router"; +import { uniqBy } from "lodash"; +import { sortByModule } from "@/utils/moduleUtils"; +import { convertToUserSolutions } from "@/utils/stats"; +import { getExamById } from "@/utils/exams"; +import { Exam, UserSolution } from "@/interfaces/exam"; import ModuleBadge from "../ModuleBadge"; const formatTimestamp = (timestamp: string | number) => { @@ -23,9 +23,9 @@ const formatTimestamp = (timestamp: string | number) => { return date.format(formatter); }; -const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => { +const aggregateScoresByModule = (stats: Stat[]): { module: Module; total: number; missing: number; 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, @@ -54,7 +54,7 @@ const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; }, }; - stats.forEach((x) => { + stats.filter(x => !x.isPractice).forEach((x) => { scores[x.module!] = { total: scores[x.module!].total + x.score.total, correct: scores[x.module!].correct + x.score.correct, @@ -64,7 +64,7 @@ const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; 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] })); }; interface StatsGridItemProps { @@ -133,7 +133,7 @@ const StatsGridItem: React.FC = ({ correct / total < 0.3 && "text-mti-rose", ); - const {timeSpent, inactivity, session} = stats[0]; + const { timeSpent, inactivity, session } = stats[0]; const selectExam = () => { if ( @@ -247,7 +247,7 @@ const StatsGridItem: React.FC = ({
{!!assignment && (assignment.released || assignment.released === undefined) && - aggregatedLevels.map(({module, level}) => )} + aggregatedLevels.map(({ module, level }) => )}
{assignment && ( @@ -270,9 +270,9 @@ const StatsGridItem: React.FC = ({ correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red", correct / total < 0.3 && "hover:border-mti-rose", typeof selectedTrainingExams !== "undefined" && - typeof timestamp === "string" && - selectedTrainingExams.some((exam) => exam.includes(timestamp)) && - "border-2 border-slate-600", + typeof timestamp === "string" && + selectedTrainingExams.some((exam) => exam.includes(timestamp)) && + "border-2 border-slate-600", )} onClick={() => { if (!!assignment && !assignment.released) return; @@ -280,8 +280,8 @@ const StatsGridItem: React.FC = ({ return; }} style={{ - ...(width !== undefined && {width}), - ...(height !== undefined && {height}), + ...(width !== undefined && { width }), + ...(height !== undefined && { height }), }} data-tip={isDisabled ? "This exam is still being evaluated..." : "This exam is still locked by its assigner..."} role="button"> @@ -297,8 +297,8 @@ const StatsGridItem: React.FC = ({ )} data-tip="Your screen size is too small to view previous exams." style={{ - ...(width !== undefined && {width}), - ...(height !== undefined && {height}), + ...(width !== undefined && { width }), + ...(height !== undefined && { height }), }} role="button"> {content} diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts index cd7001e6..1bce344a 100644 --- a/src/interfaces/exam.ts +++ b/src/interfaces/exam.ts @@ -88,6 +88,7 @@ export interface UserSolution { exercise: string; isDisabled?: boolean; shuffleMaps?: ShuffleMap[]; + isPractice?: boolean } export interface WritingExam extends ExamBase { @@ -162,6 +163,7 @@ export interface WritingExercise extends Section { evaluation?: WritingEvaluation; }[]; topic?: string; + isPractice?: boolean } export interface AIDetectionAttributes { @@ -196,6 +198,7 @@ export interface SpeakingExercise extends Section { evaluation?: SpeakingEvaluation; }[]; topic?: string; + isPractice?: boolean } export interface InteractiveSpeakingExercise extends Section { @@ -215,6 +218,7 @@ export interface InteractiveSpeakingExercise extends Section { first_topic?: string; second_topic?: string; variant?: "initial" | "final"; + isPractice?: boolean } export interface FillBlanksMCOption { @@ -243,6 +247,7 @@ export interface FillBlanksExercise { solution: string; // *EXAMPLE: "preserve" }[]; variant?: string; + isPractice?: boolean } export interface TrueFalseExercise { @@ -251,6 +256,7 @@ export interface TrueFalseExercise { prompt: string; // *EXAMPLE: "Select the appropriate option." questions: TrueFalseQuestion[]; userSolutions: { id: string; solution: "true" | "false" | "not_given" }[]; + isPractice?: boolean } export interface TrueFalseQuestion { @@ -274,6 +280,7 @@ export interface WriteBlanksExercise { solution: string; }[]; variant?: string; + isPractice?: boolean } export interface MatchSentencesExercise { @@ -285,6 +292,7 @@ export interface MatchSentencesExercise { allowRepetition: boolean; options: MatchSentenceExerciseOption[]; variant?: string; + isPractice?: boolean } export interface MatchSentenceExerciseSentence { @@ -308,7 +316,8 @@ export interface MultipleChoiceExercise { passage?: { title: string; content: string; - } + } + isPractice?: boolean } export interface MultipleChoiceQuestion { diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index 54fba92a..8b5245e1 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -1,6 +1,6 @@ -import {Module} from "."; -import {InstructorGender, ShuffleMap} from "./exam"; -import {PermissionType} from "./permissions"; +import { Module } from "."; +import { InstructorGender, ShuffleMap } from "./exam"; +import { PermissionType } from "./permissions"; export type User = StudentUser | TeacherUser | CorporateUser | AgentUser | AdminUser | DeveloperUser | MasterCorporateUser; export type UserStatus = "active" | "disabled" | "paymentDue"; @@ -12,8 +12,8 @@ export interface BasicUser { id: string; isFirstLogin: boolean; focus: "academic" | "general"; - levels: {[key in Module]: number}; - desiredLevels: {[key in Module]: number}; + levels: { [key in Module]: number }; + desiredLevels: { [key in Module]: number }; type: Type; bio: string; isVerified: boolean; @@ -22,7 +22,7 @@ export interface BasicUser { status: UserStatus; permissions: PermissionType[]; lastLogin?: Date; - entities: {id: string; role: string}[]; + entities: { id: string; role: string }[]; } export interface StudentUser extends BasicUser { @@ -109,13 +109,13 @@ export interface DemographicCorporateInformation { export type Gender = "male" | "female" | "other"; export type EmploymentStatus = "employed" | "student" | "self-employed" | "unemployed" | "retired" | "other"; -export const EMPLOYMENT_STATUS: {status: EmploymentStatus; label: string}[] = [ - {status: "student", label: "Student"}, - {status: "employed", label: "Employed"}, - {status: "unemployed", label: "Unemployed"}, - {status: "self-employed", label: "Self-employed"}, - {status: "retired", label: "Retired"}, - {status: "other", label: "Other"}, +export const EMPLOYMENT_STATUS: { status: EmploymentStatus; label: string }[] = [ + { status: "student", label: "Student" }, + { status: "employed", label: "Employed" }, + { status: "unemployed", label: "Unemployed" }, + { status: "self-employed", label: "Self-employed" }, + { status: "retired", label: "Retired" }, + { status: "other", label: "Other" }, ]; export interface Stat { @@ -142,6 +142,7 @@ export interface Stat { path: string; version: string; }; + isPractice?: boolean } export interface Group { @@ -174,4 +175,4 @@ export interface Code { export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent" | "mastercorporate"; export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent", "mastercorporate"]; -export type WithUser = T extends {participants: string[]} ? Omit & {participants: User[]} : T; +export type WithUser = T extends { participants: string[] } ? Omit & { participants: User[] } : T; diff --git a/src/pages/(exam)/ExamPage.tsx b/src/pages/(exam)/ExamPage.tsx index 480310ac..2cd6b108 100644 --- a/src/pages/(exam)/ExamPage.tsx +++ b/src/pages/(exam)/ExamPage.tsx @@ -11,7 +11,6 @@ import Reading from "@/exams/Reading"; 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 useExamStore from "@/stores/examStore"; @@ -21,12 +20,7 @@ import axios from "axios"; 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"; -import useGradingSystem from "@/hooks/useGrading"; -import { Assignment } from "@/interfaces/results"; -import { mapBy } from "@/utils"; interface Props { page: "exams" | "exercises"; @@ -214,7 +208,6 @@ export default function ExamPage({ page, user, destination = "/exam", hideSideba }, [setModuleIndex, showSolutions]); useEffect(() => { - console.log(selectedModules) if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) { const nextExam = exams[moduleIndex]; @@ -264,6 +257,7 @@ export default function ExamPage({ page, user, destination = "/exam", hideSideba isDisabled: solution.isDisabled, shuffleMaps: solution.shuffleMaps, ...(assignment ? { assignment: assignment.id } : {}), + isPractice: solution.isPractice })); axios @@ -422,7 +416,7 @@ export default function ExamPage({ page, user, destination = "/exam", hideSideba }, }; - userSolutions.forEach((x) => { + userSolutions.filter(x => !x.isPractice).forEach((x) => { const examModule = x.module || (x.type === "writing" ? "writing" : x.type === "speaking" || x.type === "interactiveSpeaking" ? "speaking" : undefined); diff --git a/src/pages/record.tsx b/src/pages/record.tsx index 5acf236b..f4d29152 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -1,27 +1,27 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {Stat, User} from "@/interfaces/user"; -import {useEffect, useMemo, useState} from "react"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { Stat, User } from "@/interfaces/user"; +import { useEffect, useMemo, useState } from "react"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; -import {groupByDate} from "@/utils/stats"; +import { groupByDate } from "@/utils/stats"; import moment from "moment"; import useExamStore from "@/stores/examStore"; -import {ToastContainer} from "react-toastify"; +import { ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; import clsx from "clsx"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; -import {uuidv4} from "@firebase/util"; -import {usePDFDownload} from "@/hooks/usePDFDownload"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { uuidv4 } from "@firebase/util"; +import { usePDFDownload } from "@/hooks/usePDFDownload"; import useRecordStore from "@/stores/recordStore"; import StatsGridItem from "@/components/Medium/StatGridItem"; import RecordFilter from "@/components/Medium/RecordFilter"; -import {useRouter} from "next/router"; +import { useRouter } from "next/router"; import useTrainingContentStore from "@/stores/trainingContentStore"; -import {Assignment} from "@/interfaces/results"; -import {getEntitiesUsers, getUsers} from "@/utils/users.be"; -import {getAssignments, getEntitiesAssignments} from "@/utils/assignments.be"; +import { Assignment } from "@/interfaces/results"; +import { getEntitiesUsers, getUsers } from "@/utils/users.be"; +import { getAssignments, getEntitiesAssignments } from "@/utils/assignments.be"; import useGradingSystem from "@/hooks/useGrading"; import { mapBy, redirect, serialize } from "@/utils"; import { getEntitiesWithRoles } from "@/utils/entities.be"; @@ -33,7 +33,7 @@ import { EntityWithRoles } from "@/interfaces/entity"; import CardList from "@/components/High/CardList"; import { requestUser } from "@/utils/api"; -export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { +export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { const user = await requestUser(req, res) if (!user) return redirect("/login") @@ -43,12 +43,10 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const entities = await getEntitiesWithRoles(checkAccess(user, ["admin", "developer"]) ? undefined : entityIDs) const users = await (checkAccess(user, ["admin", "developer"]) ? getUsers() : getEntitiesUsers(mapBy(entities, 'id'))) - const groups = await (checkAccess(user, ["admin", "developer"]) ? getGroups() : getGroupsByEntities(mapBy(entities, 'id'))) const assignments = await (checkAccess(user, ["admin", "developer"]) ? getAssignments() : getEntitiesAssignments(mapBy(entities, 'id'))) - const gradingSystems = await Promise.all(entityIDs.map(getGradingSystemByEntity)) return { - props: serialize({user, users, assignments, entities, gradingSystems}), + props: serialize({ user, users, assignments, entities }), }; }, sessionOptions); @@ -58,13 +56,12 @@ interface Props { user: User; users: User[]; assignments: Assignment[]; - gradingSystems: Grading[] entities: EntityWithRoles[] } const MAX_TRAINING_EXAMS = 10; -export default function History({user, users, assignments, entities, gradingSystems}: Props) { +export default function History({ user, users, assignments, entities }: Props) { const router = useRouter(); const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [ state.selectedUser, @@ -75,8 +72,8 @@ export default function History({user, users, assignments, entities, gradingSyst const [filter, setFilter] = useState(); - const {data: stats, isLoading: isStatsLoading} = useFilterRecordsByUser(statsUserId || user?.id); - const {gradingSystem} = useGradingSystem(); + const { data: stats, isLoading: isStatsLoading } = useFilterRecordsByUser(statsUserId || user?.id); + const { gradingSystem } = useGradingSystem(); const setExams = useExamStore((state) => state.setExams); const setShowSolutions = useExamStore((state) => state.setShowSolutions); @@ -113,12 +110,12 @@ export default function History({user, users, assignments, entities, gradingSyst }; }, [router.events, setTraining]); - const filterStatsByDate = (stats: {[key: string]: Stat[]}) => { + const filterStatsByDate = (stats: { [key: string]: Stat[] }) => { if (filter && filter !== "assignments") { const filterDate = moment() - .subtract({[filter as string]: 1}) + .subtract({ [filter as string]: 1 }) .format("x"); - const filteredStats: {[key: string]: Stat[]} = {}; + const filteredStats: { [key: string]: Stat[] } = {}; Object.keys(stats).forEach((timestamp) => { if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp]; @@ -127,7 +124,7 @@ export default function History({user, users, assignments, entities, gradingSyst } if (filter && filter === "assignments") { - const filteredStats: {[key: string]: Stat[]} = {}; + const filteredStats: { [key: string]: Stat[] } = {}; Object.keys(stats).forEach((timestamp) => { if (stats[timestamp].map((s) => s.assignment === undefined).includes(false)) @@ -140,21 +137,21 @@ export default function History({user, users, assignments, entities, gradingSyst return stats; }; -const handleTrainingContentSubmission = () => { - if (groupedStats) { - const groupedStatsByDate = filterStatsByDate(groupedStats); - const allStats = Object.keys(groupedStatsByDate); - const selectedStats = selectedTrainingExams.reduce>((accumulator, moduleAndTimestamp) => { - const timestamp = moduleAndTimestamp.split("-")[1]; - if (allStats.includes(timestamp) && !accumulator.hasOwnProperty(timestamp)) { - accumulator[timestamp] = groupedStatsByDate[timestamp]; - } - return accumulator; - }, {}); - setTrainingStats(Object.values(selectedStats).flat()); - router.push("/training"); - } -}; + const handleTrainingContentSubmission = () => { + if (groupedStats) { + const groupedStatsByDate = filterStatsByDate(groupedStats); + const allStats = Object.keys(groupedStatsByDate); + const selectedStats = selectedTrainingExams.reduce>((accumulator, moduleAndTimestamp) => { + const timestamp = moduleAndTimestamp.split("-")[1]; + if (allStats.includes(timestamp) && !accumulator.hasOwnProperty(timestamp)) { + accumulator[timestamp] = groupedStatsByDate[timestamp]; + } + return accumulator; + }, {}); + setTrainingStats(Object.values(selectedStats).flat()); + router.push("/training"); + } + }; const filteredStats = useMemo(() => Object.keys(filterStatsByDate(groupedStats)) @@ -203,7 +200,7 @@ const handleTrainingContentSubmission = () => { {user && ( - + {training && (