Added a few more stats to the stats page
This commit is contained in:
@@ -2,12 +2,20 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone} from "react-icons/bs";
|
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 {withIronSessionSsr} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import useStats from "@/hooks/useStats";
|
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 useUser from "@/hooks/useUser";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import Diagnostic from "@/components/Diagnostic";
|
import Diagnostic from "@/components/Diagnostic";
|
||||||
@@ -16,10 +24,14 @@ import {capitalize} from "lodash";
|
|||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import ProgressBar from "@/components/Low/ProgressBar";
|
import ProgressBar from "@/components/Low/ProgressBar";
|
||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import {calculateAverageLevel} from "@/utils/score";
|
import {calculateAverageLevel, calculateBandScore} from "@/utils/score";
|
||||||
import {MODULE_ARRAY} from "@/utils/moduleUtils";
|
import {MODULE_ARRAY} from "@/utils/moduleUtils";
|
||||||
import {Chart} from "react-chartjs-2";
|
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}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -121,9 +169,9 @@ export default function Stats() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col gap-3">
|
<section className="flex flex-col gap-3">
|
||||||
<span className="font-semi text-lg">Module Statistics</span>
|
<div className="flex gap-4 flex-wrap">
|
||||||
<div className="flex gap-4 justify-between">
|
{/* Exams per module */}
|
||||||
<div className="flex flex-col gap-12 border w-full max-w-xs border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
<div className="flex flex-col gap-12 border w-full h-fit max-w-xs border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
||||||
<span className="text-sm font-bold">Exams per Module</span>
|
<span className="text-sm font-bold">Exams per Module</span>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -188,6 +236,48 @@ export default function Stats() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-3xl border border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
||||||
|
<span className="text-sm font-bold">Total Score Band per Session</span>
|
||||||
|
<Chart
|
||||||
|
type="line"
|
||||||
|
data={{
|
||||||
|
labels: Object.keys(groupBySession(stats)).map((_, index) => index),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
label: "Total",
|
||||||
|
fill: false,
|
||||||
|
borderColor: "#6A5FB1",
|
||||||
|
backgroundColor: "#7872BF",
|
||||||
|
borderWidth: 2,
|
||||||
|
spanGaps: true,
|
||||||
|
data: calculateTotalScorePerSession(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-3xl border border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
||||||
|
<span className="text-sm font-bold">Module Score Band per Session</span>
|
||||||
|
<Chart
|
||||||
|
type="line"
|
||||||
|
data={{
|
||||||
|
labels: Object.keys(groupBySession(stats)).map((_, index) => 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),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {capitalize, groupBy} from "lodash";
|
|||||||
import {convertCamelCaseToReadable} from "@/utils/string";
|
import {convertCamelCaseToReadable} from "@/utils/string";
|
||||||
import {UserSolution} from "@/interfaces/exam";
|
import {UserSolution} from "@/interfaces/exam";
|
||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
|
import {MODULES} from "@/constants/ielts";
|
||||||
|
|
||||||
export const totalExams = (stats: Stat[]): number => {
|
export const totalExams = (stats: Stat[]): number => {
|
||||||
const moduleStats = formatModuleTotalStats(stats);
|
const moduleStats = formatModuleTotalStats(stats);
|
||||||
@@ -52,7 +53,7 @@ export const totalExamsByModule = (stats: Stat[], module: Module): number => {
|
|||||||
return moduleSessions[module]?.length || 0;
|
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}} = {};
|
const moduleScores: {[key: string]: {correct: number; total: number}} = {};
|
||||||
|
|
||||||
stats.forEach((stat) => {
|
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];
|
const score = moduleScores[x as keyof typeof moduleScores];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: capitalize(x),
|
module: x,
|
||||||
value: score ? parseFloat(((score.correct / score.total) * 100).toFixed(2)) : 0,
|
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}[] => {
|
export const formatExerciseTotalStats = (stats: Stat[]): {label: string; value: number}[] => {
|
||||||
const totalExercises = stats.map((stat) => ({
|
const totalExercises = stats.map((stat) => ({
|
||||||
label: convertCamelCaseToReadable(stat.type),
|
label: convertCamelCaseToReadable(stat.type),
|
||||||
|
|||||||
Reference in New Issue
Block a user