/* 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, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; import Select from "react-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"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { res.setHeader("location", "/login"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } if (shouldRedirectHome(user)) { res.setHeader("location", "/"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } return { props: {user: req.session.user}, }; }, sessionOptions); export default function History({user}: {user: User}) { 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} = useGroups(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 router = useRouter(); const renderPdfIcon = usePDFDownload("stats"); useEffect(() => { if (stats && !isStatsLoading) { setGroupedStats(groupByDate(stats)); } }, [stats, isStatsLoading]); 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 aggregatedLevels = aggregatedScores.map((x) => ({ module: x.module, level: calculateBandScore(x.correct, x.total, x.module, user.focus), })); const {timeSpent, session} = dateStats[0]; const selectExam = () => { const examPromises = uniqBy(dateStats, "exam").map((stat) => getExamById(stat.module, stat.exam)); Promise.all(examPromises).then((exams) => { if (exams.every((x) => !!x)) { 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 )}
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={selectExam} 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}
); }; 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}`}))} defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} onChange={(value) => setStatsUserId(value?.value)} styles={{ 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... )}
)} ); }