diff --git a/src/dashboards/AssignmentCard.tsx b/src/dashboards/AssignmentCard.tsx
index 52b000c1..9744fdd8 100644
--- a/src/dashboards/AssignmentCard.tsx
+++ b/src/dashboards/AssignmentCard.tsx
@@ -1,79 +1,115 @@
import ProgressBar from "@/components/Low/ProgressBar";
import useUsers from "@/hooks/useUsers";
-import {Module} from "@/interfaces";
-import {Assignment} from "@/interfaces/results";
-import {calculateBandScore} from "@/utils/score";
+import { Module } from "@/interfaces";
+import { Assignment } from "@/interfaces/results";
+import { calculateBandScore } from "@/utils/score";
import clsx from "clsx";
import moment from "moment";
-import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
+import {
+ BsBook,
+ BsClipboard,
+ BsHeadphones,
+ BsMegaphone,
+ BsPen,
+} from "react-icons/bs";
import { usePDFDownload } from "@/hooks/usePDFDownload";
+import { uniqBy } from "lodash";
interface Props {
- onClick?: () => void;
- allowDownload?: boolean;
+ onClick?: () => void;
+ allowDownload?: boolean;
}
-export default function AssignmentCard({id, name, assigner, startDate, endDate, assignees, results, exams, onClick, allowDownload}: Assignment & Props) {
- const {users} = useUsers();
- const renderPdfIcon = usePDFDownload("assignments");
+export default function AssignmentCard({
+ id,
+ name,
+ assigner,
+ startDate,
+ endDate,
+ assignees,
+ results,
+ exams,
+ onClick,
+ allowDownload,
+}: Assignment & Props) {
+ const { users } = useUsers();
+ const renderPdfIcon = usePDFDownload("assignments");
- const calculateAverageModuleScore = (module: Module) => {
- const resultModuleBandScores = results.map((r) => {
- const moduleStats = r.stats.filter((s) => s.module === module);
+ const calculateAverageModuleScore = (module: Module) => {
+ const resultModuleBandScores = results.map((r) => {
+ const moduleStats = r.stats.filter((s) => s.module === module);
- const correct = moduleStats.reduce((acc, curr) => acc + curr.score.correct, 0);
- const total = moduleStats.reduce((acc, curr) => acc + curr.score.total, 0);
- return calculateBandScore(correct, total, module, r.type);
- });
+ const correct = moduleStats.reduce(
+ (acc, curr) => acc + curr.score.correct,
+ 0,
+ );
+ const total = moduleStats.reduce(
+ (acc, curr) => acc + curr.score.total,
+ 0,
+ );
+ return calculateBandScore(correct, total, module, r.type);
+ });
- return resultModuleBandScores.length === 0 ? -1 : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) / results.length;
- };
+ return resultModuleBandScores.length === 0
+ ? -1
+ : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) /
+ results.length;
+ };
- return (
-
-
-
-
{name}
- {allowDownload && renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
-
-
-
-
- {moment(startDate).format("DD/MM/YY, HH:mm")}
- -
- {moment(endDate).format("DD/MM/YY, HH:mm")}
-
-
- {exams.map(({module}) => (
-
- {module === "reading" && }
- {module === "listening" && }
- {module === "writing" && }
- {module === "speaking" && }
- {module === "level" && }
- {calculateAverageModuleScore(module) > -1 && (
- {calculateAverageModuleScore(module).toFixed(1)}
- )}
-
- ))}
-
-
- );
+ return (
+
+
+
+
{name}
+ {allowDownload &&
+ renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
+
+
+
+
+ {moment(startDate).format("DD/MM/YY, HH:mm")}
+ -
+ {moment(endDate).format("DD/MM/YY, HH:mm")}
+
+
+ {uniqBy(exams, (x) => x.module).map(({ module }) => (
+
+ {module === "reading" && }
+ {module === "listening" && }
+ {module === "writing" && }
+ {module === "speaking" && }
+ {module === "level" && }
+ {calculateAverageModuleScore(module) > -1 && (
+
+ {calculateAverageModuleScore(module).toFixed(1)}
+
+ )}
+
+ ))}
+
+
+ );
}
diff --git a/src/dashboards/AssignmentView.tsx b/src/dashboards/AssignmentView.tsx
index 4bd73762..42a68a7f 100644
--- a/src/dashboards/AssignmentView.tsx
+++ b/src/dashboards/AssignmentView.tsx
@@ -1,280 +1,354 @@
import ProgressBar from "@/components/Low/ProgressBar";
import Modal from "@/components/Modal";
import useUsers from "@/hooks/useUsers";
-import {Module} from "@/interfaces";
-import {Assignment} from "@/interfaces/results";
-import {Stat, User} from "@/interfaces/user";
+import { Module } from "@/interfaces";
+import { Assignment } from "@/interfaces/results";
+import { Stat, User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore";
-import {getExamById} from "@/utils/exams";
-import {sortByModule} from "@/utils/moduleUtils";
-import {calculateBandScore} from "@/utils/score";
-import {convertToUserSolutions} from "@/utils/stats";
+import { getExamById } from "@/utils/exams";
+import { sortByModule } from "@/utils/moduleUtils";
+import { calculateBandScore } from "@/utils/score";
+import { convertToUserSolutions } from "@/utils/stats";
import clsx from "clsx";
-import {capitalize, uniqBy} from "lodash";
+import { capitalize, uniqBy } from "lodash";
import moment from "moment";
-import {useRouter} from "next/router";
-import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
+import { useRouter } from "next/router";
+import {
+ BsBook,
+ BsClipboard,
+ BsHeadphones,
+ BsMegaphone,
+ BsPen,
+} from "react-icons/bs";
interface Props {
- isOpen: boolean;
- assignment?: Assignment;
- onClose: () => void;
+ isOpen: boolean;
+ assignment?: Assignment;
+ onClose: () => void;
}
-export default function AssignmentView({isOpen, assignment, onClose}: Props) {
- const {users} = useUsers();
- const router = useRouter();
+export default function AssignmentView({ isOpen, assignment, onClose }: Props) {
+ const { users } = useUsers();
+ const router = useRouter();
- const setExams = useExamStore((state) => state.setExams);
- const setShowSolutions = useExamStore((state) => state.setShowSolutions);
- const setUserSolutions = useExamStore((state) => state.setUserSolutions);
- const setSelectedModules = useExamStore((state) => state.setSelectedModules);
+ const setExams = useExamStore((state) => state.setExams);
+ const setShowSolutions = useExamStore((state) => state.setShowSolutions);
+ const setUserSolutions = useExamStore((state) => state.setUserSolutions);
+ const setSelectedModules = useExamStore((state) => state.setSelectedModules);
- const formatTimestamp = (timestamp: string) => {
- const date = moment(parseInt(timestamp));
- const formatter = "YYYY/MM/DD - HH:mm";
+ const formatTimestamp = (timestamp: string) => {
+ const date = moment(parseInt(timestamp));
+ const formatter = "YYYY/MM/DD - HH:mm";
- return date.format(formatter);
- };
+ return date.format(formatter);
+ };
- const calculateAverageModuleScore = (module: Module) => {
- if (!assignment) return -1;
+ const calculateAverageModuleScore = (module: Module) => {
+ if (!assignment) return -1;
- const resultModuleBandScores = assignment.results.map((r) => {
- const moduleStats = r.stats.filter((s) => s.module === module);
+ const resultModuleBandScores = assignment.results.map((r) => {
+ const moduleStats = r.stats.filter((s) => s.module === module);
- const correct = moduleStats.reduce((acc, curr) => acc + curr.score.correct, 0);
- const total = moduleStats.reduce((acc, curr) => acc + curr.score.total, 0);
- return calculateBandScore(correct, total, module, r.type);
- });
+ const correct = moduleStats.reduce(
+ (acc, curr) => acc + curr.score.correct,
+ 0,
+ );
+ const total = moduleStats.reduce(
+ (acc, curr) => acc + curr.score.total,
+ 0,
+ );
+ return calculateBandScore(correct, total, module, r.type);
+ });
- return resultModuleBandScores.length === 0 ? -1 : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) / assignment.results.length;
- };
+ return resultModuleBandScores.length === 0
+ ? -1
+ : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) /
+ assignment.results.length;
+ };
- const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => {
- const scores: {[key in Module]: {total: number; missing: number; correct: number}} = {
- reading: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- listening: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- writing: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- speaking: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- level: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- };
+ const aggregateScoresByModule = (
+ stats: Stat[],
+ ): { module: Module; total: number; missing: number; correct: number }[] => {
+ const scores: {
+ [key in Module]: { total: number; missing: number; correct: number };
+ } = {
+ reading: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ listening: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ writing: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ speaking: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ level: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ };
- stats.forEach((x) => {
- scores[x.module!] = {
- total: scores[x.module!].total + x.score.total,
- correct: scores[x.module!].correct + x.score.correct,
- missing: scores[x.module!].missing + x.score.missing,
- };
- });
+ stats.forEach((x) => {
+ scores[x.module!] = {
+ total: scores[x.module!].total + x.score.total,
+ correct: scores[x.module!].correct + x.score.correct,
+ missing: scores[x.module!].missing + x.score.missing,
+ };
+ });
- return Object.keys(scores)
- .filter((x) => scores[x as Module].total > 0)
- .map((x) => ({module: x as Module, ...scores[x as Module]}));
- };
+ return Object.keys(scores)
+ .filter((x) => scores[x as Module].total > 0)
+ .map((x) => ({ module: x as Module, ...scores[x as Module] }));
+ };
- const customContent = (stats: Stat[], user: string, focus: "academic" | "general") => {
- const correct = stats.reduce((accumulator, current) => accumulator + current.score.correct, 0);
- const total = stats.reduce((accumulator, current) => accumulator + current.score.total, 0);
- const aggregatedScores = aggregateScoresByModule(stats).filter((x) => x.total > 0);
+ const customContent = (
+ stats: Stat[],
+ user: string,
+ focus: "academic" | "general",
+ ) => {
+ const correct = stats.reduce(
+ (accumulator, current) => accumulator + current.score.correct,
+ 0,
+ );
+ const total = stats.reduce(
+ (accumulator, current) => accumulator + current.score.total,
+ 0,
+ );
+ const aggregatedScores = aggregateScoresByModule(stats).filter(
+ (x) => x.total > 0,
+ );
- const aggregatedLevels = aggregatedScores.map((x) => ({
- module: x.module,
- level: calculateBandScore(x.correct, x.total, x.module, focus),
- }));
+ const aggregatedLevels = aggregatedScores.map((x) => ({
+ module: x.module,
+ level: calculateBandScore(x.correct, x.total, x.module, focus),
+ }));
- const timeSpent = stats[0].timeSpent;
+ const timeSpent = stats[0].timeSpent;
- const selectExam = () => {
- const examPromises = uniqBy(stats, "exam").map((stat) => getExamById(stat.module, stat.exam));
+ const selectExam = () => {
+ const examPromises = uniqBy(stats, "exam").map((stat) =>
+ getExamById(stat.module, stat.exam),
+ );
- Promise.all(examPromises).then((exams) => {
- if (exams.every((x) => !!x)) {
- setUserSolutions(convertToUserSolutions(stats));
- setShowSolutions(true);
- setExams(exams.map((x) => x!).sort(sortByModule));
- setSelectedModules(
- exams
- .map((x) => x!)
- .sort(sortByModule)
- .map((x) => x!.module),
- );
- router.push("/exercises");
- }
- });
- };
+ Promise.all(examPromises).then((exams) => {
+ if (exams.every((x) => !!x)) {
+ setUserSolutions(convertToUserSolutions(stats));
+ setShowSolutions(true);
+ setExams(exams.map((x) => x!).sort(sortByModule));
+ setSelectedModules(
+ exams
+ .map((x) => x!)
+ .sort(sortByModule)
+ .map((x) => x!.module),
+ );
+ router.push("/exercises");
+ }
+ });
+ };
- const content = (
- <>
-
-
- {formatTimestamp(stats[0].date.toString())}
- {timeSpent && (
- <>
- •
- {Math.floor(timeSpent / 60)} minutes
- >
- )}
-
-
= 0.7 && "text-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
- correct / total < 0.3 && "text-mti-rose",
- )}>
- Level{" "}
- {(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
-
-
+ const content = (
+ <>
+
+
+
+ {formatTimestamp(stats[0].date.toString())}
+
+ {timeSpent && (
+ <>
+ •
+
+ {Math.floor(timeSpent / 60)} minutes
+
+ >
+ )}
+
+
= 0.7 && "text-mti-purple",
+ correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
+ correct / total < 0.3 && "text-mti-rose",
+ )}
+ >
+ Level{" "}
+ {(
+ aggregatedLevels.reduce(
+ (accumulator, current) => accumulator + current.level,
+ 0,
+ ) / aggregatedLevels.length
+ ).toFixed(1)}
+
+
-
-
- {aggregatedLevels.map(({module, level}) => (
-
- {module === "reading" && }
- {module === "listening" && }
- {module === "writing" && }
- {module === "speaking" && }
- {module === "level" && }
- {level.toFixed(1)}
-
- ))}
-
-
- >
- );
+
+
+ {aggregatedLevels.map(({ module, level }) => (
+
+ {module === "reading" && }
+ {module === "listening" && }
+ {module === "writing" && }
+ {module === "speaking" && }
+ {module === "level" && }
+ {level.toFixed(1)}
+
+ ))}
+
+
+ >
+ );
- return (
-
-
- {(() => {
- const student = users.find((u) => u.id === user);
- return `${student?.name} (${student?.email})`;
- })()}
-
-
= 0.7 && "hover:border-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
- correct / total < 0.3 && "hover:border-mti-rose",
- )}
- onClick={selectExam}
- role="button">
- {content}
-
-
= 0.7 && "hover:border-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
- correct / total < 0.3 && "hover:border-mti-rose",
- )}
- data-tip="Your screen size is too small to view previous exams."
- role="button">
- {content}
-
-
- );
- };
+ return (
+
+
+ {(() => {
+ const student = users.find((u) => u.id === user);
+ return `${student?.name} (${student?.email})`;
+ })()}
+
+
= 0.7 && "hover:border-mti-purple",
+ correct / total >= 0.3 &&
+ correct / total < 0.7 &&
+ "hover:border-mti-red",
+ correct / total < 0.3 && "hover:border-mti-rose",
+ )}
+ onClick={selectExam}
+ role="button"
+ >
+ {content}
+
+
= 0.7 && "hover:border-mti-purple",
+ correct / total >= 0.3 &&
+ correct / total < 0.7 &&
+ "hover:border-mti-red",
+ correct / total < 0.3 && "hover:border-mti-rose",
+ )}
+ data-tip="Your screen size is too small to view previous exams."
+ role="button"
+ >
+ {content}
+
+
+ );
+ };
- return (
-
-
-
-
-
- Start Date: {moment(assignment?.startDate).format("DD/MM/YY, HH:mm")}
- End Date: {moment(assignment?.endDate).format("DD/MM/YY, HH:mm")}
-
-
- Assignees:{" "}
- {users
- .filter((u) => assignment?.assignees.includes(u.id))
- .map((u) => `${u.name} (${u.email})`)
- .join(", ")}
-
-
-
-
Average Scores
-
- {assignment?.exams.map(({module}) => (
-
- {module === "reading" && }
- {module === "listening" && }
- {module === "writing" && }
- {module === "speaking" && }
- {module === "level" && }
- {calculateAverageModuleScore(module) > -1 && (
- {calculateAverageModuleScore(module).toFixed(1)}
- )}
-
- ))}
-
-
-
-
- Results ({assignment?.results.length}/{assignment?.assignees.length})
-
-
- {assignment && assignment?.results.length > 0 && (
-
- {assignment.results.map((r) => customContent(r.stats, r.user, r.type))}
-
- )}
- {assignment && assignment?.results.length === 0 &&
No results yet...}
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+ Start Date:{" "}
+ {moment(assignment?.startDate).format("DD/MM/YY, HH:mm")}
+
+
+ End Date: {moment(assignment?.endDate).format("DD/MM/YY, HH:mm")}
+
+
+
+ Assignees:{" "}
+ {users
+ .filter((u) => assignment?.assignees.includes(u.id))
+ .map((u) => `${u.name} (${u.email})`)
+ .join(", ")}
+
+
+
+
Average Scores
+
+ {assignment &&
+ uniqBy(assignment.exams, (x) => x.module).map(({ module }) => (
+
+ {module === "reading" && }
+ {module === "listening" && (
+
+ )}
+ {module === "writing" && }
+ {module === "speaking" && }
+ {module === "level" && }
+ {calculateAverageModuleScore(module) > -1 && (
+
+ {calculateAverageModuleScore(module).toFixed(1)}
+
+ )}
+
+ ))}
+
+
+
+
+ Results ({assignment?.results.length}/{assignment?.assignees.length}
+ )
+
+
+ {assignment && assignment?.results.length > 0 && (
+
+ {assignment.results.map((r) =>
+ customContent(r.stats, r.user, r.type),
+ )}
+
+ )}
+ {assignment && assignment?.results.length === 0 && (
+
No results yet...
+ )}
+
+
+
+
+ );
}