Updated the stats and record page

This commit is contained in:
Tiago Ribeiro
2023-10-02 10:23:33 +01:00
parent b58957e38e
commit 0f0b7748d7
2 changed files with 56 additions and 45 deletions

View File

@@ -19,6 +19,8 @@ import Layout from "@/components/High/Layout";
import clsx from "clsx"; import clsx from "clsx";
import {calculateBandScore} from "@/utils/score"; import {calculateBandScore} from "@/utils/score";
import {BsBook, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; import {BsBook, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
import Select from "react-select";
import useGroups from "@/hooks/useGroups";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -40,12 +42,13 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}, sessionOptions); }, sessionOptions);
export default function History({user}: {user: User}) { export default function History({user}: {user: User}) {
const [selectedUser, setSelectedUser] = useState<User>(user); const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id);
const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>(); const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>();
const [filter, setFilter] = useState<"months" | "weeks" | "days">(); const [filter, setFilter] = useState<"months" | "weeks" | "days">();
const {users, isLoading: isUsersLoading} = useUsers(); const {users} = useUsers();
const {stats, isLoading: isStatsLoading} = useStats(selectedUser?.id); const {stats, isLoading: isStatsLoading} = useStats(statsUserId);
const {groups} = useGroups(user.id);
const setExams = useExamStore((state) => state.setExams); const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setShowSolutions = useExamStore((state) => state.setShowSolutions);
@@ -218,22 +221,25 @@ export default function History({user}: {user: User}) {
{user && ( {user && (
<Layout user={user}> <Layout user={user}>
<div className="w-full flex justify-between items-center"> <div className="w-full flex justify-between items-center">
<div className="w-fit"> <div className="w-3/4">
{!isUsersLoading && user.type !== "student" && ( {(user.type === "developer" || user.type === "owner") && (
<> <Select
<select options={users.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))}
className="select w-full max-w-xs bg-white border border-mti-gray-platinum outline-none font-normal text-base" defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
onChange={(e) => setSelectedUser(users.find((x) => x.id === e.target.value)!)}> onChange={(value) => setStatsUserId(value?.value)}
{users.map((x) => ( />
<option key={x.id} selected={selectedUser.id === x.id} value={x.id}> )}
{x.name} {(user.type === "admin" || user.type === "teacher") && groups.length > 0 && (
</option> <Select
))} options={users
</select> .filter((x) => 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)}
/>
)} )}
</div> </div>
<div className="flex gap-4"> <div className="flex gap-4 w-full justify-end">
<button <button
className={clsx( className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light", "bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",

View File

@@ -1,24 +1,13 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import Navbar from "@/components/Navbar"; import {BsFileEarmarkText, BsPencil, BsStar} from "react-icons/bs";
import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone} from "react-icons/bs"; import {LinearScale, Chart as ChartJS, CategoryScale, PointElement, LineElement, Legend, Tooltip, LineController} 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 { import {averageScore, totalExamsByModule, groupBySession, groupByModule} from "@/utils/stats";
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 Diagnostic from "@/components/Diagnostic";
import {ToastContainer} from "react-toastify"; import {ToastContainer} from "react-toastify";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
import {Module} from "@/interfaces"; import {Module} from "@/interfaces";
@@ -27,8 +16,11 @@ import Layout from "@/components/High/Layout";
import {calculateAverageLevel, calculateBandScore} 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";
import useUsers from "@/hooks/useUsers";
import Select from "react-select";
import useGroups from "@/hooks/useGroups";
ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, Legend, Tooltip); ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip);
const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8"]; const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8"];
@@ -52,19 +44,16 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}, sessionOptions); }, sessionOptions);
export default function Stats() { export default function Stats() {
const {user} = useUser({redirectTo: "/login"}); const [statsUserId, setStatsUserId] = useState<string>();
const {stats} = useStats(user?.id);
const totalExamsData = { const {user} = useUser({redirectTo: "/login"});
labels: MODULE_ARRAY.map((x) => capitalize(x)), const {users} = useUsers();
datasets: [ const {groups} = useGroups(user?.id);
{ const {stats} = useStats(statsUserId);
label: "Total exams",
data: MODULE_ARRAY.map((x) => totalExamsByModule(stats, x)), useEffect(() => {
backgroundColor: ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8"], if (user) setStatsUserId(user.id);
}, }, [user]);
],
};
const calculateTotalScorePerSession = () => { const calculateTotalScorePerSession = () => {
const groupedBySession = groupBySession(stats); const groupedBySession = groupBySession(stats);
@@ -115,7 +104,7 @@ export default function Stats() {
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<Layout user={user}> <Layout user={user} className="gap-8">
<section className="w-full flex gap-8"> <section className="w-full flex gap-8">
<img src={user.profilePicture} alt={user.name} className="aspect-square h-64 rounded-3xl drop-shadow-xl object-cover" /> <img src={user.profilePicture} alt={user.name} className="aspect-square h-64 rounded-3xl drop-shadow-xl object-cover" />
<div className="flex flex-col gap-4 py-4 w-full"> <div className="flex flex-col gap-4 py-4 w-full">
@@ -170,6 +159,22 @@ export default function Stats() {
</section> </section>
{stats.length > 0 && ( {stats.length > 0 && (
<section className="flex flex-col gap-3"> <section className="flex flex-col gap-3">
{(user.type === "developer" || user.type === "owner") && (
<Select
options={users.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.type === "admin" || user.type === "teacher") && groups.length > 0 && (
<Select
options={users
.filter((x) => 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)}
/>
)}
<div className="flex gap-4 flex-wrap"> <div className="flex gap-4 flex-wrap">
{/* Exams per module */} {/* Exams per module */}
<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"> <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">