diff --git a/src/dashboards/AssignmentView.tsx b/src/dashboards/AssignmentView.tsx
new file mode 100644
index 00000000..27b2a5a1
--- /dev/null
+++ b/src/dashboards/AssignmentView.tsx
@@ -0,0 +1,270 @@
+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 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 clsx from "clsx";
+import {uniqBy} from "lodash";
+import moment from "moment";
+import {useRouter} from "next/router";
+import {BsBook, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
+
+interface Props {
+ isOpen: boolean;
+ assignment?: Assignment;
+ onClose: () => void;
+}
+
+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 formatTimestamp = (timestamp: string) => {
+ const date = moment(parseInt(timestamp));
+ const formatter = "YYYY/MM/DD - HH:mm";
+
+ return date.format(formatter);
+ };
+
+ const calculateAverageModuleScore = (module: Module) => {
+ if (!assignment) return -1;
+
+ 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);
+ });
+
+ 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,
+ },
+ };
+
+ 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]}));
+ };
+
+ 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 timeSpent = stats[0].timeSpent;
+
+ 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");
+ }
+ });
+ };
+
+ 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" && }
+ {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 (
+
+
+
+
+
+ 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" && }
+ {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...}
+
+
+
+
+ );
+}
diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx
index 562cf771..53468023 100644
--- a/src/dashboards/Teacher.tsx
+++ b/src/dashboards/Teacher.tsx
@@ -44,6 +44,7 @@ import Button from "@/components/Low/Button";
import clsx from "clsx";
import ProgressBar from "@/components/Low/ProgressBar";
import AssignmentCreator from "./AssignmentCreator";
+import AssignmentView from "./AssignmentView";
interface Props {
user: User;
@@ -150,24 +151,14 @@ export default function TeacherDashboard({user}: Props) {
return (
<>
- setSelectedAssignment(undefined)}
- title={selectedAssignment?.name}>
-
-
+ onClose={() => {
+ setSelectedAssignment(undefined);
+ setIsCreatingAssignment(false);
+ }}
+ assignment={selectedAssignment}
+ />
x.admin === user.id || x.participants.includes(user.id))}
diff --git a/src/pages/record.tsx b/src/pages/record.tsx
index 85d40294..d0ed31ab 100644
--- a/src/pages/record.tsx
+++ b/src/pages/record.tsx
@@ -23,6 +23,7 @@ import Select from "react-select";
import useGroups from "@/hooks/useGroups";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import useAssignments from "@/hooks/useAssignments";
+import {uuidv4} from "@firebase/util";
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user;
@@ -57,7 +58,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
export default function History({user}: {user: User}) {
const [statsUserId, setStatsUserId] = useState(user.id);
const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>();
- const [filter, setFilter] = useState<"months" | "weeks" | "days">();
+ const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">();
const {assignments} = useAssignments({});
const {users} = useUsers();
@@ -73,16 +74,19 @@ export default function History({user}: {user: User}) {
useEffect(() => {
if (stats && !isStatsLoading) {
+ console.log(stats);
setGroupedStats(groupByDate(stats));
}
}, [stats, isStatsLoading]);
- const toggleFilter = (value: "months" | "weeks" | "days") => {
+ const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
setFilter((prev) => (prev === value ? undefined : value));
};
const filterStatsByDate = (stats: {[key: string]: Stat[]}) => {
- if (filter) {
+ console.log(filter);
+
+ if (filter && filter !== "assignments") {
const filterDate = moment()
.subtract({[filter as string]: 1})
.format("x");
@@ -95,6 +99,19 @@ export default function History({user}: {user: User}) {
return filteredStats;
}
+ if (filter && filter === "assignments") {
+ const filteredStats: {[key: string]: Stat[]} = {};
+
+ Object.keys(stats).forEach((timestamp) => {
+ if (stats[timestamp].map((s) => s.assignment === undefined).includes(false))
+ filteredStats[timestamp] = [...stats[timestamp].filter((s) => !!s.assignment)];
+ });
+
+ console.log(filteredStats);
+
+ return filteredStats;
+ }
+
return stats;
};
@@ -234,7 +251,7 @@ export default function History({user}: {user: User}) {
return (
<>
= 0.7 && "hover:border-mti-purple",
@@ -246,7 +263,7 @@ export default function History({user}: {user: User}) {
{content}
= 0.7 && "hover:border-mti-purple",
@@ -309,6 +326,15 @@ export default function History({user}: {user: User}) {
)}
+