diff --git a/src/pages/stats.tsx b/src/pages/stats.tsx index 82186557..17d3cc30 100644 --- a/src/pages/stats.tsx +++ b/src/pages/stats.tsx @@ -2,12 +2,20 @@ import Head from "next/head"; import Navbar from "@/components/Navbar"; import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone} from "react-icons/bs"; -import {ArcElement} from "chart.js"; +import {ArcElement, LinearScale, Chart as ChartJS, CategoryScale, PointElement, LineElement, Legend, Tooltip} from "chart.js"; import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import {useEffect, useState} from "react"; import useStats from "@/hooks/useStats"; -import {averageScore, totalExams, totalExamsByModule, groupBySession} from "@/utils/stats"; +import { + averageScore, + totalExams, + totalExamsByModule, + groupBySession, + groupByModule, + formatModuleAverageScoreStats, + calculateModuleAverageScoreStats, +} from "@/utils/stats"; import useUser from "@/hooks/useUser"; import Sidebar from "@/components/Sidebar"; import Diagnostic from "@/components/Diagnostic"; @@ -16,10 +24,14 @@ import {capitalize} from "lodash"; import {Module} from "@/interfaces"; import ProgressBar from "@/components/Low/ProgressBar"; import Layout from "@/components/High/Layout"; -import {calculateAverageLevel} from "@/utils/score"; +import {calculateAverageLevel, calculateBandScore} from "@/utils/score"; import {MODULE_ARRAY} from "@/utils/moduleUtils"; import {Chart} from "react-chartjs-2"; +ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, Legend, Tooltip); + +const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8"]; + export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -54,6 +66,42 @@ export default function Stats() { ], }; + const calculateTotalScorePerSession = () => { + const groupedBySession = groupBySession(stats); + const sessionAverage = Object.keys(groupedBySession).map((x: string) => { + const session = groupedBySession[x]; + const moduleStats = groupByModule(session); + const moduleScores = Object.keys(moduleStats).map((y) => { + const correct = moduleStats[y].reduce((accumulator, current) => accumulator + current.score.correct, 0); + const total = moduleStats[y].reduce((accumulator, current) => accumulator + current.score.total, 0); + + return { + module: y, + score: calculateBandScore(correct, total, y as Module, user?.focus || "academic"), + }; + }); + + return moduleScores.reduce((acc, curr) => acc + curr.score, 0) / 4; + }); + + return sessionAverage; + }; + + const calculateModularScorePerSession = (module: Module) => { + const groupedBySession = groupBySession(stats); + const sessionAverage = Object.keys(groupedBySession).map((x: string) => { + const session = groupedBySession[x]; + const moduleStats = groupByModule(session); + if (!Object.keys(moduleStats).includes(module)) return null; + const correct = moduleStats[module].reduce((acc, curr) => acc + curr.score.correct, 0); + const total = moduleStats[module].reduce((acc, curr) => acc + curr.score.total, 0); + + return calculateBandScore(correct, total, module, user?.focus || "academic"); + }); + + return sessionAverage; + }; + return ( <> @@ -121,9 +169,9 @@ export default function Stats() {
- Module Statistics -
-
+
+ {/* Exams per module */} +
Exams per Module
@@ -188,6 +236,48 @@ export default function Stats() {
+ +
+ Total Score Band per Session + index), + datasets: [ + { + type: "line", + label: "Total", + fill: false, + borderColor: "#6A5FB1", + backgroundColor: "#7872BF", + borderWidth: 2, + spanGaps: true, + data: calculateTotalScorePerSession(), + }, + ], + }} + /> +
+ +
+ Module Score Band per Session + index), + datasets: [ + ...MODULE_ARRAY.map((module, index) => ({ + type: "line" as const, + label: capitalize(module), + borderColor: COLORS[index], + backgroundColor: COLORS[index], + borderWidth: 2, + data: calculateModularScorePerSession(module), + })), + ], + }} + /> +
diff --git a/src/utils/stats.ts b/src/utils/stats.ts index 0ff7063f..d42c5c2e 100644 --- a/src/utils/stats.ts +++ b/src/utils/stats.ts @@ -3,6 +3,7 @@ import {capitalize, groupBy} from "lodash"; import {convertCamelCaseToReadable} from "@/utils/string"; import {UserSolution} from "@/interfaces/exam"; import {Module} from "@/interfaces"; +import {MODULES} from "@/constants/ielts"; export const totalExams = (stats: Stat[]): number => { const moduleStats = formatModuleTotalStats(stats); @@ -52,7 +53,7 @@ export const totalExamsByModule = (stats: Stat[], module: Module): number => { return moduleSessions[module]?.length || 0; }; -export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => { +export const calculateModuleAverageScoreStats = (stats: Stat[]): {module: Module; value: number}[] => { const moduleScores: {[key: string]: {correct: number; total: number}} = {}; stats.forEach((stat) => { @@ -66,16 +67,20 @@ export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; va } }); - return ["reading", "listening", "writing", "speaking"].map((x) => { + return MODULES.map((x) => { const score = moduleScores[x as keyof typeof moduleScores]; return { - label: capitalize(x), + module: x, value: score ? parseFloat(((score.correct / score.total) * 100).toFixed(2)) : 0, }; }); }; +export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => { + return calculateModuleAverageScoreStats(stats).map((x) => ({label: capitalize(x.module), value: x.value})); +}; + export const formatExerciseTotalStats = (stats: Stat[]): {label: string; value: number}[] => { const totalExercises = stats.map((stat) => ({ label: convertCamelCaseToReadable(stat.type),