/* 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 useStats from "@/hooks/useStats"; 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 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 Select from "@/components/Low/Select"; import useGroups from "@/hooks/useGroups"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import useAssignments from "@/hooks/useAssignments"; import { uuidv4 } from "@firebase/util"; import { usePDFDownload } from "@/hooks/usePDFDownload"; import useRecordStore from "@/stores/recordStore"; export const getServerSideProps = withIronSessionSsr(({ req, res }) => { const user = req.session.user; if (!user || !user.isVerified) { return { redirect: { destination: "/login", permanent: false, }, }; } if (shouldRedirectHome(user)) { return { redirect: { destination: "/", permanent: false, }, }; } return { props: { user: req.session.user }, }; }, sessionOptions); const defaultSelectableCorporate = { value: "", label: "All", }; export default function History({ user }: { user: User }) { const [statsUserId, setStatsUserId] = useRecordStore((state) => [ state.selectedUser, state.setSelectedUser, ]); // const [statsUserId, setStatsUserId] = useState(user.id); const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>(); const [filter, setFilter] = useState< "months" | "weeks" | "days" | "assignments" >(); const { assignments } = useAssignments({}); const { users } = useUsers(); const { stats, isLoading: isStatsLoading } = useStats(statsUserId); const { groups: allGroups } = useGroups(); const groups = allGroups.filter((x) => x.admin === user.id); const setExams = useExamStore((state) => state.setExams); const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setUserSolutions = useExamStore((state) => state.setUserSolutions); const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setInactivity = useExamStore((state) => state.setInactivity); const setTimeSpent = useExamStore((state) => state.setTimeSpent); const router = useRouter(); const renderPdfIcon = usePDFDownload("stats"); useEffect(() => { if (stats && !isStatsLoading) { setGroupedStats( groupByDate( stats.filter((x) => { if ( (x.module === "writing" || x.module === "speaking") && !x.isDisabled && !x.solutions.every((y) => Object.keys(y).includes("evaluation")) ) return false; return true; }) ) ); } }, [stats, isStatsLoading]); // useEffect(() => { // // just set this initially // if (!statsUserId) setStatsUserId(user.id); // }, []); const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => { setFilter((prev) => (prev === value ? undefined : value)); }; const filterStatsByDate = (stats: { [key: string]: Stat[] }) => { if (filter && filter !== "assignments") { const filterDate = moment() .subtract({ [filter as string]: 1 }) .format("x"); 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[] } = {}; Object.keys(stats).forEach((timestamp) => { if ( stats[timestamp] .map((s) => s.assignment === undefined) .includes(false) ) filteredStats[timestamp] = [ ...stats[timestamp].filter((s) => !!s.assignment), ]; }); return filteredStats; } return stats; }; const formatTimestamp = (timestamp: string) => { const date = moment(parseInt(timestamp)); const formatter = "YYYY/MM/DD - HH:mm"; return date.format(formatter); }; const aggregateScoresByModule = ( stats: Stat[] ): { module: Module; total: number; missing: number; correct: number }[] => { const scores: { [key in Module]: { total: number; missing: number; correct: number }; } = { reading: { total: 0, correct: 0, missing: 0, }, listening: { total: 0, correct: 0, missing: 0, }, writing: { total: 0, correct: 0, missing: 0, }, speaking: { total: 0, correct: 0, missing: 0, }, level: { total: 0, correct: 0, missing: 0, }, }; stats.forEach((x) => { scores[x.module!] = { total: scores[x.module!].total + x.score.total, correct: scores[x.module!].correct + x.score.correct, missing: scores[x.module!].missing + x.score.missing, }; }); return Object.keys(scores) .filter((x) => scores[x as Module].total > 0) .map((x) => ({ module: x as Module, ...scores[x as Module] })); }; const customContent = (timestamp: string) => { if (!groupedStats) return <>; const dateStats = groupedStats[timestamp]; const correct = dateStats.reduce( (accumulator, current) => accumulator + current.score.correct, 0 ); const total = dateStats.reduce( (accumulator, current) => accumulator + current.score.total, 0 ); const aggregatedScores = aggregateScoresByModule(dateStats).filter( (x) => x.total > 0 ); const assignmentID = dateStats.reduce( (_, current) => current.assignment as any, "" ); const assignment = assignments.find((a) => a.id === assignmentID); const isDisabled = dateStats.some((x) => x.isDisabled); const aggregatedLevels = aggregatedScores.map((x) => ({ module: x.module, level: calculateBandScore(x.correct, x.total, x.module, user.focus), })); const { timeSpent, inactivity, session } = dateStats[0]; const selectExam = () => { const examPromises = uniqBy(dateStats, "exam").map((stat) => { console.log({ 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); 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( correct / total >= 0.7 && "text-mti-purple", correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red", correct / total < 0.3 && "text-mti-rose" ); const content = ( <>
{formatTimestamp(timestamp)}
{!!timeSpent && ( {Math.floor(timeSpent / 60)} minutes )} {!!inactivity && ( {Math.floor(inactivity / 60)} minutes )}
Level{" "} {( aggregatedLevels.reduce( (accumulator, current) => accumulator + current.level, 0 ) / aggregatedLevels.length ).toFixed(1)} {renderPdfIcon(session, textColor, textColor)}
{aggregatedLevels.map(({ module, level }) => (
{module === "reading" && } {module === "listening" && } {module === "writing" && } {module === "speaking" && } {module === "level" && } {level.toFixed(1)}
))}
{assignment && ( Assignment: {assignment.name}, Teacher:{" "} {users.find((u) => u.id === assignment.assigner)?.name} )}
); return ( <>
= 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" )} onClick={isDisabled ? () => null : selectExam} data-tip="This exam is still being evaluated..." role="button" > {content}
= 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" )} data-tip="Your screen size is too small to view previous exams." role="button" > {content}
); }; const selectableCorporates = [ defaultSelectableCorporate, ...users .filter((x) => x.type === "corporate") .map((x) => ({ value: x.id, label: `${x.name} - ${x.email}`, })), ]; const [selectedCorporate, setSelectedCorporate] = useState( defaultSelectableCorporate.value ); const getUsersList = (): User[] => { if (selectedCorporate) { // get groups for that corporate const selectedCorporateGroups = allGroups.filter( (x) => x.admin === selectedCorporate ); // get the teacher ids for that group const selectedCorporateGroupsParticipants = selectedCorporateGroups.flatMap((x) => x.participants); // // search for groups for these teachers // const teacherGroups = allGroups.filter((x) => { // return selectedCorporateGroupsParticipants.includes(x.admin); // }); // const usersList = [ // ...selectedCorporateGroupsParticipants, // ...teacherGroups.flatMap((x) => x.participants), // ]; const userListWithUsers = selectedCorporateGroupsParticipants.map((x) => users.find((y) => y.id === x) ) as User[]; return userListWithUsers.filter((x) => x); } return users || []; }; const corporateFilteredUserList = getUsersList(); const getSelectedUser = () => { if (selectedCorporate) { const userInCorporate = corporateFilteredUserList.find( (x) => x.id === statsUserId ); return userInCorporate || corporateFilteredUserList[0]; } return users.find((x) => x.id === statsUserId) || user; }; const selectedUser = getSelectedUser(); const selectedUserSelectValue = selectedUser ? { value: selectedUser.id, label: `${selectedUser.name} - ${selectedUser.email}`, } : { value: "", label: "", }; return ( <> Record | EnCoach {user && (
{(user.type === "developer" || user.type === "admin") && ( <> groups.flatMap((y) => y.participants).includes(x.id) ) .map((x) => ({ value: x.id, label: `${x.name} - ${x.email}`, }))} value={selectedUserSelectValue} onChange={(value) => setStatsUserId(value?.value)} styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }), option: (styles, state) => ({ ...styles, backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", color: state.isFocused ? "black" : styles.color, }), }} /> )}
{groupedStats && Object.keys(groupedStats).length > 0 && !isStatsLoading && (
{Object.keys(filterStatsByDate(groupedStats)) .sort((a, b) => parseInt(b) - parseInt(a)) .map(customContent)}
)} {groupedStats && Object.keys(groupedStats).length === 0 && !isStatsLoading && ( No record to display... )}
)} ); }