/* eslint-disable @next/next/no-img-element */ import Head from "next/head"; import {BsArrowClockwise, BsChevronLeft, BsChevronRight, BsFileEarmarkText, BsPencil, BsStar} from "react-icons/bs"; import {LinearScale, Chart as ChartJS, CategoryScale, PointElement, LineElement, Legend, Tooltip, LineController} 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, totalExamsByModule, groupBySession, groupByModule, timestampToMoment, groupByDate} from "@/utils/stats"; import useUser from "@/hooks/useUser"; import {ToastContainer} from "react-toastify"; import {capitalize, Dictionary} from "lodash"; import {Module} from "@/interfaces"; import ProgressBar from "@/components/Low/ProgressBar"; import Layout from "@/components/High/Layout"; import {calculateAverageLevel, calculateBandScore} from "@/utils/score"; import {MODULE_ARRAY, sortByModule} from "@/utils/moduleUtils"; import {Chart} from "react-chartjs-2"; import useUsers from "@/hooks/useUsers"; import Select from "react-select"; import useGroups from "@/hooks/useGroups"; import DatePicker from "react-datepicker"; import {shouldRedirectHome} from "@/utils/navigation.disabled"; import ProfileSummary from "@/components/ProfileSummary"; import moment from "moment"; import {Stat} from "@/interfaces/user"; import {Divider} from "primereact/divider"; import Badge from "@/components/Low/Badge"; ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip); const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8", "#414288"]; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { res.setHeader("location", "/login"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } if (shouldRedirectHome(user)) { res.setHeader("location", "/"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } return { props: {user: req.session.user}, }; }, sessionOptions); export default function Stats() { const [statsUserId, setStatsUserId] = useState(); const [startDate, setStartDate] = useState(moment(new Date()).subtract(1, "weeks").toDate()); const [endDate, setEndDate] = useState(new Date()); const [initialStatDate, setInitialStatDate] = useState(); const [monthlyOverallScoreDate, setMonthlyOverallScoreDate] = useState(new Date()); const [monthlyModuleScoreDate, setMonthlyModuleScoreDate] = useState(new Date()); const [dailyScoreDate, setDailyScoreDate] = useState(new Date()); const [intervalDates, setIntervalDates] = useState([]); const {user} = useUser({redirectTo: "/login"}); const {users} = useUsers(); const {groups} = useGroups(user?.id); const {stats} = useStats(statsUserId); const {stats: userStats} = useStats(user?.id); useEffect(() => { if (user) setStatsUserId(user.id); }, [user]); useEffect(() => { setInitialStatDate( stats .filter((s) => s.date) .sort((a, b) => timestampToMoment(a).diff(timestampToMoment(b))) .map(timestampToMoment) .shift() ?.toDate(), ); }, [stats]); const calculateModuleScore = (stats: Stat[]) => { const moduleStats = groupByModule(stats); return 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 as Module, score: calculateBandScore(correct, total, y as Module, user?.focus || "academic"), }; }); }; const calculateModularScorePerSession = (stats: Stat[], 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; }; const getListOfDateInInterval = (start: Date, end: Date) => { let currentDate = moment(start); const dates = [currentDate.toDate()]; while (moment(end).diff(currentDate, "days") > 0) { currentDate = currentDate.add(1, "days"); dates.push(currentDate.toDate()); } return dates; }; useEffect(() => { if (startDate && endDate) { setIntervalDates(getListOfDateInInterval(startDate, endDate)); } }, [startDate, endDate]); const calculateTotalScore = (stats: Stat[], divisionFactor: number) => { const moduleScores = calculateModuleScore(stats); return moduleScores.reduce((acc, curr) => acc + curr.score, 0) / divisionFactor; }; const calculateScorePerModule = (stats: Stat[], module: Module) => { const moduleScores = calculateModuleScore(stats); return moduleScores.find((x) => x.module === module)?.score || -1; }; return ( <> Stats | EnCoach {user && ( , value: Object.keys(groupBySession(userStats)).length, label: "Exams", }, { icon: , value: userStats.length, label: "Exercises", }, { icon: , value: `${userStats.length > 0 ? averageScore(userStats) : 0}%`, label: "Average Score", }, ]} />
<> {(user.type === "developer" || user.type === "admin") && ( groups.flatMap((y) => y.participants).includes(x.id)) .map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} onChange={(value) => setStatsUserId(value?.value)} menuPortalTarget={document?.body} styles={{ menuPortal: (base) => ({...base, zIndex: 9999}), option: (styles, state) => ({ ...styles, backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", color: state.isFocused ? "black" : styles.color, }), }} /> )}
{stats.length > 0 && ( <>
{/* Overall Level per Month */}
Overall Level per Month
{monthlyOverallScoreDate && ( )} {monthlyOverallScoreDate && ( )}
{[...Array(31).keys()].map((day) => { const date = moment( `${(day + 1).toString().padStart(2, "0")}/${ moment(monthlyOverallScoreDate).get("month") + 1 }/${moment(monthlyOverallScoreDate).get("year")}`, "DD/MM/yyyy", ); return date.isValid() && date.isSameOrBefore(moment()) ? (
Day {(day + 1).toString().padStart(2, "0")} Level{" "} {calculateTotalScore( stats.filter((s) => timestampToMoment(s).isBefore(date)), 5, ).toFixed(1)}
) : null; })}
{/* Overall Level per Month Graph */}
Overall Level per Month
{monthlyOverallScoreDate && ( )} {monthlyOverallScoreDate && ( )}
{ const date = moment( `${(day + 1).toString().padStart(2, "0")}/${ moment(monthlyOverallScoreDate).get("month") + 1 }/${moment(monthlyOverallScoreDate).get("year")}`, "DD/MM/yyyy", ); return date.isValid() ? (day + 1).toString().padStart(2, "0") : undefined; }) .filter((x) => !!x), datasets: [ { type: "line", label: "Total", fill: false, borderColor: "#6A5FB1", backgroundColor: "#7872BF", borderWidth: 2, spanGaps: true, data: [...Array(31).keys()] .map((day) => { const date = moment( `${(day + 1).toString().padStart(2, "0")}/${ moment(monthlyOverallScoreDate).get("month") + 1 }/${moment(monthlyOverallScoreDate).get("year")}`, "DD/MM/yyyy", ); return date.isValid() ? calculateTotalScore( stats.filter((s) => timestampToMoment(s).isBefore(date)), 5, ).toFixed(1) : undefined; }) .filter((x) => !!x), }, ], }} />
{/* Module Level per Day */}
Module Level per Day
{monthlyModuleScoreDate && ( )} {monthlyModuleScoreDate && ( )}
{calculateModuleScore(stats.filter((s) => timestampToMoment(s).isBefore(moment(monthlyModuleScoreDate)))) .sort(sortByModule) .map(({module, score}) => (
{score} of 9 {capitalize(module)}
))}
{/* Module Level per Exam */}
Module Level per Exam
{dailyScoreDate && ( )} {dailyScoreDate && ( )}
{Object.keys( groupBySession( stats.filter( (s) => Math.abs(timestampToMoment(s).diff(moment(dailyScoreDate), "days")) === 0 && timestampToMoment(s).day() === moment(dailyScoreDate).day(), ), ), ).length === 0 && No exams performed this day...} {Object.keys( groupBySession( stats.filter( (s) => Math.abs(timestampToMoment(s).diff(moment(dailyScoreDate), "days")) === 0 && timestampToMoment(s).day() === moment(dailyScoreDate).day(), ), ), ).map((session, index) => (
Exam {(index + 1).toString().padStart(2, "0")}
{MODULE_ARRAY.map((module) => { const score = calculateScorePerModule( groupBySession( stats.filter( (s) => Math.abs(timestampToMoment(s).diff(moment(dailyScoreDate), "days")) === 0, ), )[session], module, ); return score === -1 ? null : {score.toFixed(1)}; }).filter((m) => !!m)}
))}
Module Level per Exam
{dailyScoreDate && ( )} {dailyScoreDate && ( )}
Math.abs(timestampToMoment(s).diff(moment(dailyScoreDate), "days")) === 0 && timestampToMoment(s).day() === moment(dailyScoreDate).day(), ), ), ).map((_, index) => `Exam ${(index + 1).toString().padStart(2, "0")}`), datasets: [ ...MODULE_ARRAY.map((module, index) => ({ type: "line" as const, label: capitalize(module), borderColor: COLORS[index], backgroundColor: COLORS[index], borderWidth: 2, data: calculateModularScorePerSession( stats.filter( (s) => Math.abs(timestampToMoment(s).diff(moment(dailyScoreDate), "days")) === 0 && timestampToMoment(s).day() === moment(dailyScoreDate).day(), ), module, ), })), ], }} />
moment(date).isSameOrBefore(moment(new Date()))} onChange={([initialDate, finalDate]) => { setStartDate(initialDate); setEndDate(finalDate); }} />
{/* Module Score Band in Interval */} {MODULE_ARRAY.map((module, index) => (
{capitalize(module)} Score Band in Interval moment(date).format("DD/MM/YYYY")), datasets: [ { type: "line", label: capitalize(module), fill: false, borderColor: COLORS[index], backgroundColor: COLORS[index], borderWidth: 2, spanGaps: true, data: intervalDates.map((date) => { return calculateTotalScore( stats.filter( (s) => timestampToMoment(s).isBefore(date) && s.module === module, ), 1, ).toFixed(1); }), }, ], }} />
))}
)}
{stats.length === 0 && (
No stats to display...
)}
)} ); }