ENCOA-269

This commit is contained in:
Tiago Ribeiro
2024-12-11 17:42:57 +00:00
parent 71ed1013b7
commit 76cbf8dc41
2 changed files with 44 additions and 43 deletions

View File

@@ -3,7 +3,7 @@ import { RolePermission } from "@/resources/entityPermissions";
export interface Entity { export interface Entity {
id: string; id: string;
label: string; label: string;
licenses: number licenses: number;
} }
export interface Role { export interface Role {

View File

@@ -1,31 +1,31 @@
/* 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 {BsArrowClockwise, BsChevronLeft, BsChevronRight, BsFileEarmarkText, BsPencil, BsStar} from "react-icons/bs"; 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 { LinearScale, Chart as ChartJS, CategoryScale, PointElement, LineElement, Legend, Tooltip, LineController } 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, useMemo, useState } from "react";
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import {averageScore, totalExamsByModule, groupBySession, groupByModule, timestampToMoment, groupByDate} from "@/utils/stats"; import { averageScore, totalExamsByModule, groupBySession, groupByModule, timestampToMoment, groupByDate } from "@/utils/stats";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {ToastContainer} from "react-toastify"; import { ToastContainer } from "react-toastify";
import {capitalize, Dictionary} from "lodash"; import { capitalize, Dictionary } 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, calculateBandScore} from "@/utils/score"; import { calculateAverageLevel, calculateBandScore } from "@/utils/score";
import {countExamModules, countFullExams, MODULE_ARRAY, sortByModule} from "@/utils/moduleUtils"; import { countExamModules, countFullExams, MODULE_ARRAY, sortByModule } from "@/utils/moduleUtils";
import {Chart} from "react-chartjs-2"; import { Chart } from "react-chartjs-2";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
import ProfileSummary from "@/components/ProfileSummary"; import ProfileSummary from "@/components/ProfileSummary";
import moment from "moment"; import moment from "moment";
import {Group, Stat, User} from "@/interfaces/user"; import { Group, Stat, User } from "@/interfaces/user";
import {Divider} from "primereact/divider"; import { Divider } from "primereact/divider";
import Badge from "@/components/Low/Badge"; import Badge from "@/components/Low/Badge";
import { mapBy, redirect, serialize } from "@/utils"; import { filterBy, mapBy, redirect, serialize } from "@/utils";
import { getEntitiesWithRoles } from "@/utils/entities.be"; import { getEntitiesWithRoles } from "@/utils/entities.be";
import { checkAccess } from "@/utils/permissions"; import { checkAccess } from "@/utils/permissions";
import { getEntitiesUsers, getUsers } from "@/utils/users.be"; import { getEntitiesUsers, getUsers } from "@/utils/users.be";
@@ -38,7 +38,7 @@ ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineCont
const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8", "#414288"]; const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8", "#414288"];
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return redirect("/login") if (!user) return redirect("/login")
@@ -50,7 +50,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const groups = await (checkAccess(user, ["admin", "developer"]) ? getGroups() : getGroupsByEntities(mapBy(entities, 'id'))) const groups = await (checkAccess(user, ["admin", "developer"]) ? getGroups() : getGroupsByEntities(mapBy(entities, 'id')))
return { return {
props: serialize({user, entities, users, groups}), props: serialize({ user, entities, users, groups }),
}; };
}, sessionOptions); }, sessionOptions);
@@ -66,6 +66,7 @@ export default function Stats({ user, entities, users, groups }: Props) {
const [startDate, setStartDate] = useState<Date | null>(moment(new Date()).subtract(1, "weeks").toDate()); const [startDate, setStartDate] = useState<Date | null>(moment(new Date()).subtract(1, "weeks").toDate());
const [endDate, setEndDate] = useState<Date | null>(new Date()); const [endDate, setEndDate] = useState<Date | null>(new Date());
const [initialStatDate, setInitialStatDate] = useState<Date>(); const [initialStatDate, setInitialStatDate] = useState<Date>();
const [selectedEntity, setSelectedEntity] = useState<string>()
const [monthlyOverallScoreDate, setMonthlyOverallScoreDate] = useState<Date | null>(new Date()); const [monthlyOverallScoreDate, setMonthlyOverallScoreDate] = useState<Date | null>(new Date());
const [monthlyModuleScoreDate, setMonthlyModuleScoreDate] = useState<Date | null>(new Date()); const [monthlyModuleScoreDate, setMonthlyModuleScoreDate] = useState<Date | null>(new Date());
@@ -73,7 +74,12 @@ export default function Stats({ user, entities, users, groups }: Props) {
const [dailyScoreDate, setDailyScoreDate] = useState<Date | null>(new Date()); const [dailyScoreDate, setDailyScoreDate] = useState<Date | null>(new Date());
const [intervalDates, setIntervalDates] = useState<Date[]>([]); const [intervalDates, setIntervalDates] = useState<Date[]>([]);
const {data: stats} = useFilterRecordsByUser<Stat[]>(statsUserId, !statsUserId); const { data: stats } = useFilterRecordsByUser<Stat[]>(statsUserId, !statsUserId);
const students = useMemo(() =>
filterBy(users, 'type', 'student').filter(x => !selectedEntity ? true : mapBy(x.entities, 'id').includes((selectedEntity))),
[users, selectedEntity]
)
useEffect(() => { useEffect(() => {
setInitialStatDate( setInitialStatDate(
@@ -181,26 +187,24 @@ export default function Stats({ user, entities, users, groups }: Props) {
<section className="flex flex-col gap-3"> <section className="flex flex-col gap-3">
<div className="w-full flex justify-between gap-4 items-center"> <div className="w-full flex justify-between gap-4 items-center">
{["corporate", "teacher", "mastercorporate", "developer", "admin"].includes(user.type) && (
<> <>
{(user.type === "developer" || user.type === "admin") && (
<Select <Select
className="w-full" className="w-full"
options={users.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} options={entities.map(e => ({ value: e.id, label: e.label }))}
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} onChange={(value) => setSelectedEntity(value?.value || undefined)}
onChange={(value) => setStatsUserId(value?.value || user.id)} placeholder="Select an entity..."
isClearable
/> />
)}
{["corporate", "teacher", "mastercorporate"].includes(user.type) && groups.length > 0 && (
<Select <Select
className="w-full" className="w-full"
options={users options={students
.filter((x) => groups.flatMap((y) => y.participants).includes(x.id)) .map((x) => ({ value: x.id, label: `${x.name} - ${x.email}` }))}
.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }}
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
onChange={(value) => setStatsUserId(value?.value || user.id)} onChange={(value) => setStatsUserId(value?.value || user.id)}
/> />
)}
</> </>
)}
</div> </div>
{stats.length > 0 && ( {stats.length > 0 && (
@@ -244,8 +248,7 @@ export default function Stats({ user, entities, users, groups }: Props) {
<div className="w-full grid grid-cols-3 gap-4 items-center"> <div className="w-full grid grid-cols-3 gap-4 items-center">
{[...Array(31).keys()].map((day) => { {[...Array(31).keys()].map((day) => {
const date = moment( const date = moment(
`${(day + 1).toString().padStart(2, "0")}/${ `${(day + 1).toString().padStart(2, "0")}/${moment(monthlyOverallScoreDate).get("month") + 1
moment(monthlyOverallScoreDate).get("month") + 1
}/${moment(monthlyOverallScoreDate).get("year")}`, }/${moment(monthlyOverallScoreDate).get("year")}`,
"DD/MM/yyyy", "DD/MM/yyyy",
); );
@@ -319,8 +322,7 @@ export default function Stats({ user, entities, users, groups }: Props) {
labels: [...Array(31).keys()] labels: [...Array(31).keys()]
.map((day) => { .map((day) => {
const date = moment( const date = moment(
`${(day + 1).toString().padStart(2, "0")}/${ `${(day + 1).toString().padStart(2, "0")}/${moment(monthlyOverallScoreDate).get("month") + 1
moment(monthlyOverallScoreDate).get("month") + 1
}/${moment(monthlyOverallScoreDate).get("year")}`, }/${moment(monthlyOverallScoreDate).get("year")}`,
"DD/MM/yyyy", "DD/MM/yyyy",
); );
@@ -339,8 +341,7 @@ export default function Stats({ user, entities, users, groups }: Props) {
data: [...Array(31).keys()] data: [...Array(31).keys()]
.map((day) => { .map((day) => {
const date = moment( const date = moment(
`${(day + 1).toString().padStart(2, "0")}/${ `${(day + 1).toString().padStart(2, "0")}/${moment(monthlyOverallScoreDate).get("month") + 1
moment(monthlyOverallScoreDate).get("month") + 1
}/${moment(monthlyOverallScoreDate).get("year")}`, }/${moment(monthlyOverallScoreDate).get("year")}`,
"DD/MM/yyyy", "DD/MM/yyyy",
); );
@@ -396,7 +397,7 @@ export default function Stats({ user, entities, users, groups }: Props) {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{calculateModuleScore(stats.filter((s) => timestampToMoment(s).isBefore(moment(monthlyModuleScoreDate)))) {calculateModuleScore(stats.filter((s) => timestampToMoment(s).isBefore(moment(monthlyModuleScoreDate))))
.sort(sortByModule) .sort(sortByModule)
.map(({module, score}) => ( .map(({ module, score }) => (
<div className="flex flex-col gap-2" key={module}> <div className="flex flex-col gap-2" key={module}>
<div className="flex justify-between items-end"> <div className="flex justify-between items-end">
<span className="text-xs"> <span className="text-xs">