training.tsx still a bit messy, all that is left is to retrieve data from firestore /training and /walkthrough and render it

This commit is contained in:
Carlos Mesquita
2024-07-31 20:44:46 +01:00
parent 02320b9484
commit a534126c61
16 changed files with 1600 additions and 285 deletions

View File

@@ -1,35 +1,37 @@
/* eslint-disable @next/next/no-img-element */
import Head from "next/head";
import {withIronSessionSsr} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {Stat, User} from "@/interfaces/user";
import {useEffect, useState} from "react";
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { Stat, User } from "@/interfaces/user";
import { useEffect, useRef, useState } from "react";
import useStats from "@/hooks/useStats";
import {convertToUserSolutions, groupByDate} from "@/utils/stats";
import { convertToUserSolutions, groupByDate } from "@/utils/stats";
import moment from "moment";
import useUsers from "@/hooks/useUsers";
import useExamStore from "@/stores/examStore";
import {Module} from "@/interfaces";
import {ToastContainer} from "react-toastify";
import {useRouter} from "next/router";
import {uniqBy} from "lodash";
import {getExamById} from "@/utils/exams";
import {sortByModule} from "@/utils/moduleUtils";
import { Module } from "@/interfaces";
import { ToastContainer } from "react-toastify";
import { useRouter } from "next/router";
import { uniqBy } from "lodash";
import { getExamById } from "@/utils/exams";
import { sortByModule } from "@/utils/moduleUtils";
import Layout from "@/components/High/Layout";
import clsx from "clsx";
import {calculateBandScore} from "@/utils/score";
import {BsBook, BsClipboard, BsClock, BsHeadphones, BsMegaphone, BsPen, BsPersonDash, BsPersonFillX, BsXCircle} from "react-icons/bs";
import { calculateBandScore } from "@/utils/score";
import { BsBook, BsClipboard, BsClock, BsHeadphones, BsMegaphone, BsPen, BsPersonDash, BsPersonFillX, BsXCircle } from "react-icons/bs";
import Select from "@/components/Low/Select";
import useGroups from "@/hooks/useGroups";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import useAssignments from "@/hooks/useAssignments";
import {uuidv4} from "@firebase/util";
import {usePDFDownload} from "@/hooks/usePDFDownload";
import { uuidv4 } from "@firebase/util";
import { usePDFDownload } from "@/hooks/usePDFDownload";
import useRecordStore from "@/stores/recordStore";
import useTrainingContentStore from "@/stores/trainingContentStore";
import ai_usage from "@/utils/ai.detection";
import Button from "@/components/Low/Button";
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
const user = req.session.user;
if (!user || !user.isVerified) {
@@ -51,7 +53,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}
return {
props: {user: req.session.user},
props: { user: req.session.user },
};
}, sessionOptions);
@@ -60,16 +62,16 @@ const defaultSelectableCorporate = {
label: "All",
};
export default function History({user}: {user: User}) {
const [statsUserId, setStatsUserId] = useRecordStore((state) => [state.selectedUser, state.setSelectedUser]);
export default function History({ user }: { user: User }) {
const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [state.selectedUser, state.setSelectedUser, state.training, state.setTraining]);
// 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" | "assignments">();
const {assignments} = useAssignments({});
const { assignments } = useAssignments({});
const {users} = useUsers();
const {stats, isLoading: isStatsLoading} = useStats(statsUserId);
const {groups: allGroups} = useGroups();
const { users } = useUsers();
const { stats, isLoading: isStatsLoading } = useStats(statsUserId);
const { groups: allGroups } = useGroups();
const groups = allGroups.filter((x) => x.admin === user.id);
@@ -109,22 +111,21 @@ export default function History({user}: {user: User}) {
setFilter((prev) => (prev === value ? undefined : value));
};
const filterStatsByDate = (stats: {[key: string]: Stat[]}) => {
const filterStatsByDate = (stats: { [key: string]: Stat[] }) => {
if (filter && filter !== "assignments") {
const filterDate = moment()
.subtract({[filter as string]: 1})
.subtract({ [filter as string]: 1 })
.format("x");
const filteredStats: {[key: string]: Stat[]} = {};
const filteredStats: { [key: string]: Stat[] } = {};
Object.keys(stats).forEach((timestamp) => {
if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp];
});
return filteredStats;
}
if (filter && filter === "assignments") {
const filteredStats: {[key: string]: Stat[]} = {};
const filteredStats: { [key: string]: Stat[] } = {};
Object.keys(stats).forEach((timestamp) => {
if (stats[timestamp].map((s) => s.assignment === undefined).includes(false))
@@ -144,9 +145,9 @@ export default function History({user}: {user: User}) {
return date.format(formatter);
};
const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => {
const aggregateScoresByModule = (stats: Stat[]): { module: Module; total: number; missing: number; correct: number }[] => {
const scores: {
[key in Module]: {total: number; missing: number; correct: number};
[key in Module]: { total: number; missing: number; correct: number };
} = {
reading: {
total: 0,
@@ -185,9 +186,38 @@ export default function History({user}: {user: User}) {
return Object.keys(scores)
.filter((x) => scores[x as Module].total > 0)
.map((x) => ({module: x as Module, ...scores[x as Module]}));
.map((x) => ({ module: x as Module, ...scores[x as Module] }));
};
const MAX_TRAINING_EXAMS = 10;
const [selectedTrainingExams, setSelectedTrainingExams] = useState<string[]>([]);
const setTrainingStats = useTrainingContentStore((state) => state.setStats);
useEffect(() => {
const handleRouteChange = (url: string) => {
setTraining(false)
}
router.events.on('routeChangeStart', handleRouteChange)
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router.events, setTraining])
const handleTrainingContentSubmission = () => {
if (groupedStats) {
const allStats = Object.keys(filterStatsByDate(groupedStats));
const selectedStats = selectedTrainingExams.reduce<Record<string, Stat[]>>((accumulator, timestamp) => {
if (allStats.includes(timestamp)) {
accumulator[timestamp] = filterStatsByDate(groupedStats)[timestamp];
}
return accumulator;
}, {});
setTrainingStats(Object.values(selectedStats).flat())
router.push("/training");
}
}
const customContent = (timestamp: string) => {
if (!groupedStats) return <></>;
@@ -206,33 +236,45 @@ export default function History({user}: {user: User}) {
level: calculateBandScore(x.correct, x.total, x.module, user.focus),
}));
const {timeSpent, inactivity, session} = dateStats[0];
const { timeSpent, inactivity, session } = dateStats[0];
const selectExam = () => {
const examPromises = uniqBy(dateStats, "exam").map((stat) => {
console.log({stat});
return getExamById(stat.module, stat.exam);
});
if (training && !isDisabled) {
setSelectedTrainingExams(prevExams => {
const index = prevExams.indexOf(timestamp);
if (isDisabled) return;
if (index !== -1) {
const newExams = [...prevExams];
newExams.splice(index, 1);
return newExams;
} else {
return [...prevExams, timestamp];
}
});
} else {
const examPromises = uniqBy(dateStats, "exam").map((stat) => {
return getExamById(stat.module, stat.exam);
});
Promise.all(examPromises).then((exams) => {
if (exams.every((x) => !!x)) {
if (!!timeSpent) setTimeSpent(timeSpent);
if (!!inactivity) setInactivity(inactivity);
if (isDisabled) return;
setUserSolutions(convertToUserSolutions(dateStats));
setShowSolutions(true);
setExams(exams.map((x) => x!).sort(sortByModule));
setSelectedModules(
exams
.map((x) => x!)
.sort(sortByModule)
.map((x) => x!.module),
);
router.push("/exercises");
}
});
Promise.all(examPromises).then((exams) => {
if (exams.every((x) => !!x)) {
if (!!timeSpent) setTimeSpent(timeSpent);
if (!!inactivity) setInactivity(inactivity);
setUserSolutions(convertToUserSolutions(dateStats));
setShowSolutions(true);
setExams(exams.map((x) => x!).sort(sortByModule));
setSelectedModules(
exams
.map((x) => x!)
.sort(sortByModule)
.map((x) => x!.module),
);
router.push("/exercises");
}
});
}
};
const textColor = clsx(
@@ -283,7 +325,7 @@ export default function History({user}: {user: User}) {
<div className="w-full flex flex-col gap-1">
<div className="grid grid-cols-4 gap-2 place-items-start w-full -md:mt-2">
{aggregatedLevels.map(({module, level}) => (
{aggregatedLevels.map(({ module, level }) => (
<div
key={module}
className={clsx(
@@ -323,6 +365,7 @@ export default function History({user}: {user: User}) {
correct / total >= 0.7 && "hover:border-mti-purple",
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
correct / total < 0.3 && "hover:border-mti-rose",
selectedTrainingExams.includes(timestamp) && "border-2 border-slate-600",
)}
onClick={selectExam}
data-tip="This exam is still being evaluated..."
@@ -395,13 +438,13 @@ export default function History({user}: {user: User}) {
const selectedUser = getSelectedUser();
const selectedUserSelectValue = selectedUser
? {
value: selectedUser.id,
label: `${selectedUser.name} - ${selectedUser.email}`,
}
value: selectedUser.id,
label: `${selectedUser.name} - ${selectedUser.email}`,
}
: {
value: "",
label: "",
};
value: "",
label: "",
};
return (
<>
<Head>
@@ -418,7 +461,7 @@ export default function History({user}: {user: User}) {
<Layout user={user}>
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
<div className="xl:w-3/4">
{(user.type === "developer" || user.type === "admin") && (
{(user.type === "developer" || user.type === "admin") && !training && (
<>
<label className="font-normal text-base text-mti-gray-dim">Corporate</label>
@@ -427,7 +470,7 @@ export default function History({user}: {user: User}) {
value={selectableCorporates.find((x) => x.value === selectedCorporate)}
onChange={(value) => setSelectedCorporate(value?.value || "")}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
@@ -444,7 +487,7 @@ export default function History({user}: {user: User}) {
value={selectedUserSelectValue}
onChange={(value) => setStatsUserId(value?.value)}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
@@ -454,7 +497,7 @@ export default function History({user}: {user: User}) {
/>
</>
)}
{(user.type === "corporate" || user.type === "teacher") && groups.length > 0 && (
{(user.type === "corporate" || user.type === "teacher") && groups.length > 0 && !training && (
<>
<label className="font-normal text-base text-mti-gray-dim">User</label>
@@ -468,7 +511,7 @@ export default function History({user}: {user: User}) {
value={selectedUserSelectValue}
onChange={(value) => setStatsUserId(value?.value)}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
@@ -478,6 +521,18 @@ export default function History({user}: {user: User}) {
/>
</>
)}
{(training && (
<div className="flex flex-row justify-between">
<div className="font-semibold text-2xl">Select up to 10 exams {`(${selectedTrainingExams.length}/${MAX_TRAINING_EXAMS})`}</div>
<Button
color="purple"
disabled={selectedTrainingExams.length==0}
onClick={handleTrainingContentSubmission}
className="w-fit">
Submit
</Button>
</div>
))}
</div>
<div className="flex gap-4 w-full justify-center xl:justify-end">
<button