/* 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 useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; 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 {countExamModules, countFullExams, MODULE_ARRAY, sortByModule} from "@/utils/moduleUtils"; import {Chart} from "react-chartjs-2"; import useUsers from "@/hooks/useUsers"; 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 {Group, Stat, User} from "@/interfaces/user"; import {Divider} from "primereact/divider"; import Badge from "@/components/Low/Badge"; import { mapBy, serialize } from "@/utils"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { checkAccess } from "@/utils/permissions"; import { getEntitiesUsers, getUsers } from "@/utils/users.be"; import { EntityWithRoles } from "@/interfaces/entity"; import { getGroups, getGroupsByEntities } from "@/utils/groups.be"; import Select from "@/components/Low/Select"; ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip); const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8", "#414288"]; export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const user = req.session.user as User; if (!user) { return { redirect: { destination: "/login", permanent: false, }, }; } if (shouldRedirectHome(user)) { return { redirect: { destination: "/", permanent: false, }, }; } const entityIDs = mapBy(user.entities, 'id') const entities = await getEntitiesWithRoles(checkAccess(user, ["admin", "developer"]) ? undefined : entityIDs) const users = await (checkAccess(user, ["admin", "developer"]) ? getUsers() : getEntitiesUsers(mapBy(entities, 'id'))) const groups = await (checkAccess(user, ["admin", "developer"]) ? getGroups() : getGroupsByEntities(mapBy(entities, 'id'))) return { props: serialize({user, entities, users, groups}), }; }, sessionOptions); interface Props { user: User users: User[] entities: EntityWithRoles[] groups: Group[] } export default function Stats({ user, entities, users, groups }: Props) { const [statsUserId, setStatsUserId] = useState(user.id); 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 {data: stats} = useFilterRecordsByUser(statsUserId, !statsUserId); 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 && ( x.id === statsUserId) || user} items={[ { icon: , value: countFullExams(stats), label: "Exams", tooltip: "Number of all conducted completed exams", }, { icon: , value: countExamModules(stats), label: "Modules", tooltip: "Number of all exam modules performed including Level Test", }, { icon: , value: `${stats.length > 0 ? averageScore(stats) : 0}%`, label: "Average Score", tooltip: "Average success rate for questions responded", }, ]} />
<> {(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 || user.id)} /> )}
{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...
)}
)} ); }