From e60130069de0b96cec6cd79fbcfbcb1ee7d697b7 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 20 Apr 2023 10:38:55 +0100 Subject: [PATCH] Created more types of stats to be showcased: - Total Exams per Module; - Average Score per Module; - Total Exercises per Type; - Average Score per Exercise Type; --- src/components/UserResultChart.tsx | 22 +++---- src/interfaces/user.ts | 1 + src/pages/index.tsx | 52 +---------------- src/utils/stats.ts | 92 ++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 src/utils/stats.ts diff --git a/src/components/UserResultChart.tsx b/src/components/UserResultChart.tsx index b654ef17..50195cca 100644 --- a/src/components/UserResultChart.tsx +++ b/src/components/UserResultChart.tsx @@ -1,28 +1,24 @@ -import {Module} from "@/interfaces"; -import {UserResults} from "@/interfaces/results"; import {SEMI_TRANSPARENT} from "@/resources/colors"; -import {moduleLabels} from "@/utils/moduleUtils"; import {Chart as ChartJS, RadialLinearScale, ArcElement, Tooltip, Legend} from "chart.js"; import clsx from "clsx"; import {PolarArea} from "react-chartjs-2"; interface Props { - results: UserResults; - resultKey: "score" | "total"; - label: string; + data: {label: string; value: number}[]; + title: string; className?: string; } ChartJS.register(RadialLinearScale, ArcElement, Tooltip, Legend); -export default function UserResultChart({results, resultKey, label, className = ""}: Props) { - const labels = Object.keys(results).map((key) => moduleLabels[key as Module]); - const data = { +export default function UserResultChart({data, title, className = ""}: Props) { + const labels = data.map((x) => x.label); + const chartData = { labels, datasets: [ { - label, - data: Object.keys(results).map((module) => results[module as Module][resultKey]), + title, + data: data.map((x) => x.value), backgroundColor: Object.values(SEMI_TRANSPARENT), }, ], @@ -30,8 +26,8 @@ export default function UserResultChart({results, resultKey, label, className = return (
- -

{label}

+ +

{title}

); } diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index 7b4d7a6a..215850f9 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -16,6 +16,7 @@ export interface Stat { exercise: string; module: Module; solutions: any[]; + type: string; score: { correct: number; total: number; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4a312741..a4efaa81 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,23 +2,13 @@ import Head from "next/head"; import UserResultChart from "@/components/UserResultChart"; import Navbar from "@/components/Navbar"; -import Icon from "@mdi/react"; -import {mdiPlus} from "@mdi/js"; -import Link from "next/link"; -import clsx from "clsx"; -import {infoButtonStyle} from "@/constants/buttonStyles"; import ProfileCard from "@/components/ProfileCard"; - -// TODO: Remove this import -import JSON_RESULTS from "@/demo/user_results.json"; - import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; -import {Stat, User} from "@/interfaces/user"; +import {User} from "@/interfaces/user"; import {useEffect, useState} from "react"; import useStats from "@/hooks/useStats"; -import {Module} from "@/interfaces"; -import {UserResults} from "@/interfaces/results"; +import {formatModuleTotalStats} from "@/utils/stats"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -46,42 +36,6 @@ export default function Home({user}: {user: User}) { useEffect(() => setShowEndExam(window.innerWidth <= 960), []); useEffect(() => console.log(stats), [stats]); - const formatStatsToChart = (data: Stat[]): UserResults => { - const result: UserResults = { - reading: { - exams: [], - score: 0, - total: 0, - }, - listening: { - exams: [], - score: 0, - total: 0, - }, - writing: { - exams: [], - score: 0, - total: 0, - }, - speaking: { - exams: [], - score: 0, - total: 0, - }, - }; - - data.forEach((stat) => { - if (result[stat.module].exams) - result[stat.module] = { - exams: [...result[stat.module].exams.filter((x) => x !== stat.exam), stat.exam], - total: result[stat.module].score + (result[stat.module].exams.includes(stat.exam) ? 0 : 1), - score: result[stat.module].total + 1, - }; - }); - - return result; - }; - return ( <> @@ -102,7 +56,7 @@ export default function Home({user}: {user: User}) { {!isLoading && stats && (
- +
)} diff --git a/src/utils/stats.ts b/src/utils/stats.ts new file mode 100644 index 00000000..78ee03de --- /dev/null +++ b/src/utils/stats.ts @@ -0,0 +1,92 @@ +import {Module} from "@/interfaces"; +import {Stat} from "@/interfaces/user"; +import {capitalize} from "lodash"; +import {convertCamelCaseToReadable} from "@/utils/string"; + +export const formatModuleTotalStats = (stats: Stat[]): {label: string; value: number}[] => { + const result: {[key in Module]: {exams: string[]; total: number}} = { + reading: { + exams: [], + total: 0, + }, + listening: { + exams: [], + total: 0, + }, + writing: { + exams: [], + total: 0, + }, + speaking: { + exams: [], + total: 0, + }, + }; + + stats.forEach((stat) => { + if (result[stat.module].exams) + result[stat.module] = { + exams: [...result[stat.module].exams.filter((x) => x !== stat.exam), stat.exam], + total: result[stat.module].total + 1, + }; + }); + + return Object.keys(result).map((key) => ({label: capitalize(key), value: result[key as Module].total})); +}; + +export const formatModuleAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => { + const moduleScores: {[key: string]: {correct: number; total: number}} = {}; + + stats.forEach((stat) => { + if (stat.module in moduleScores) { + moduleScores[stat.module] = { + correct: moduleScores[stat.module].correct + stat.score.correct, + total: moduleScores[stat.module].total + stat.score.total, + }; + } else { + moduleScores[stat.module] = stat.score; + } + }); + + return Object.keys(moduleScores).map((x) => { + const {correct, total} = moduleScores[x as keyof typeof moduleScores]; + + return { + label: capitalize(x), + value: correct / total, + }; + }); +}; + +export const formatExerciseTotalStats = (stats: Stat[]): {label: string; value: number}[] => { + const totalExercises = stats.map((stat) => ({ + label: convertCamelCaseToReadable(stat.type), + value: stats.filter((x) => x.type === stat.type).length, + })); + + return totalExercises.filter((ex, index) => totalExercises.findIndex((x) => x.label === ex.label) === index); +}; + +export const formatExerciseAverageScoreStats = (stats: Stat[]): {label: string; value: number}[] => { + const typeScores: {[key: string]: {correct: number; total: number}} = {}; + + stats.forEach((stat) => { + if (stat.type in typeScores) { + typeScores[stat.type] = { + correct: typeScores[stat.type].correct + stat.score.correct, + total: typeScores[stat.type].total + stat.score.total, + }; + } else { + typeScores[stat.type] = stat.score; + } + }); + + return Object.keys(typeScores).map((x) => { + const {correct, total} = typeScores[x as keyof typeof typeScores]; + + return { + label: convertCamelCaseToReadable(x), + value: correct / total, + }; + }); +};