import React, {useEffect, useMemo, useState} from "react"; import {CorporateUser, StudentUser, User} from "@/interfaces/user"; import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs"; import IconCard from "../IconCard"; import useAssignmentsCorporates from "@/hooks/useAssignmentCorporates"; import ReactDatePicker from "react-datepicker"; import moment from "moment"; import {AssignmentWithCorporateId} from "@/interfaces/results"; import {flexRender, createColumnHelper, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import Checkbox from "@/components/Low/Checkbox"; import {useListSearch} from "@/hooks/useListSearch"; import axios from "axios"; import {toast} from "react-toastify"; import Button from "@/components/Low/Button"; import {getUserName} from "@/utils/users"; interface GroupedCorporateUsers { // list of user Ids [key: string]: string[]; } interface Props { corporateUsers: GroupedCorporateUsers; users: User[]; displaySelection?: boolean; } interface TableData { user: User | undefined; email: string; correct: number; corporate: string; submitted: boolean; date: moment.Moment; assignment: string; corporateId: string; } interface UserCount { userCount: number; maxUserCount: number; } const searchFilters = [["email"], ["user"], ["userId"], ["exams"], ["assignment"]]; const SIZE = 16; const MasterStatistical = (props: Props) => { const {users, corporateUsers, displaySelection = true} = props; const [page, setPage] = useState(0); // const corporateRelevantUsers = React.useMemo( // () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[], // [corporateUsers] // ); const corporates = React.useMemo(() => Object.values(corporateUsers).flat(), [corporateUsers]); const [selectedCorporates, setSelectedCorporates] = React.useState(corporates); const [startDate, setStartDate] = React.useState(moment("01/01/2023").toDate()); const [endDate, setEndDate] = React.useState(moment().endOf("year").toDate()); const {assignments, isLoading} = useAssignmentsCorporates({ corporates: selectedCorporates, startDate, endDate, }); const [downloading, setDownloading] = React.useState(false); const tableResults = React.useMemo( () => assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { const userResults = a.assignees.map((assignee) => { const userData = users.find((u) => u.id === assignee); const userStats = a.results.find((r) => r.user === assignee)?.stats || []; const corporate = getUserName(users.find((u) => u.id === a.assigner)); const commonData = { user: userData, email: userData?.email || "N/A", userId: assignee, corporateId: a.corporateId, exams: a.exams.map((x) => x.id).join(", "), corporate, assignment: a.name, }; if (userStats.length === 0) { return { ...commonData, correct: 0, submitted: false, date: null, }; } return { ...commonData, correct: userStats.reduce((n, e) => n + e.score.correct, 0), submitted: true, date: moment.max(userStats.map((e) => moment(e.date))), }; }) as TableData[]; return [...accmA, ...userResults]; }, []), [assignments, users], ); const getCorporateScores = (corporateId: string): UserCount => { const corporateAssignmentsUsers = assignments.filter((a) => a.corporateId === corporateId).reduce((acc, a) => acc + a.assignees.length, 0); const corporateResults = tableResults.filter((r) => r.corporateId === corporateId).length; return { maxUserCount: corporateAssignmentsUsers, userCount: corporateResults, }; }; const getCorporatesScoresHash = (data: string[]) => data.reduce( (accm, id) => ({ ...accm, [id]: getCorporateScores(id), }), {}, ) as Record; const getConsolidateScore = (data: Record) => Object.values(data).reduce( (acc: UserCount, {userCount, maxUserCount}: UserCount) => ({ userCount: acc.userCount + userCount, maxUserCount: acc.maxUserCount + maxUserCount, }), {userCount: 0, maxUserCount: 0}, ); const corporateScores = getCorporatesScoresHash(corporates); const consolidateScore = getConsolidateScore(corporateScores); const getConsolidateScoreStr = (data: UserCount) => `${data.userCount}/${data.maxUserCount}`; const columnHelper = createColumnHelper(); const defaultColumns = [ columnHelper.accessor("user", { header: "User", id: "user", cell: (info) => { return {info.getValue()?.name || "N/A"}; }, }), columnHelper.accessor("email", { header: "Email", id: "email", cell: (info) => { return {info.getValue()}; }, }), columnHelper.accessor("user", { header: "Student ID", id: "studentID", cell: (info) => { return {(info.getValue() as StudentUser)?.studentID || "N/A"}; }, }), ...(displaySelection ? [ columnHelper.accessor("corporate", { header: "Corporate", id: "corporate", cell: (info) => { return {info.getValue()}; }, }), ] : []), columnHelper.accessor("assignment", { header: "Assignment", id: "assignment", cell: (info) => { return {info.getValue()}; }, }), columnHelper.accessor("submitted", { header: "Submitted", id: "submitted", cell: (info) => { return ( {}}> ); }, }), columnHelper.accessor("correct", { header: "Score", id: "correct", cell: (info) => { return {info.getValue()}; }, }), columnHelper.accessor("date", { header: "Date", id: "date", cell: (info) => { const date = info.getValue(); if (date) { return {!!date ? date.format("DD/MM/YYYY") : "N/A"}; } return {""}; }, }), ]; const {rows: filteredRows, renderSearch, text: searchText} = useListSearch(searchFilters, tableResults); useEffect(() => setPage(0), [searchText]); const rows = useMemo( () => filteredRows.slice(page * SIZE, (page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE), [filteredRows, page], ); const table = useReactTable({ data: rows, columns: defaultColumns, getCoreRowModel: getCoreRowModel(), }); const areAllSelected = selectedCorporates.length === corporates.length; const getStudentsConsolidateScore = () => { if (tableResults.length === 0) { return {highest: null, lowest: null}; } // Find the student with the highest and lowest score return tableResults.reduce( (acc, curr) => { if (curr.correct > acc.highest.correct) { acc.highest = curr; } if (curr.correct < acc.lowest.correct) { acc.lowest = curr; } return acc; }, {highest: tableResults[0], lowest: tableResults[0]}, ); }; const triggerDownload = async () => { try { setDownloading(true); const res = await axios.post("/api/assignments/statistical/excel", { ids: selectedCorporates, ...(startDate ? {startDate: startDate.toISOString()} : {}), ...(endDate ? {endDate: endDate.toISOString()} : {}), searchText, }); toast.success("Report ready!"); const link = document.createElement("a"); link.href = res.data; // download should have worked but there are some CORS issues // https://firebase.google.com/docs/storage/web/download-files#cors_configuration // link.download="report.pdf"; link.target = "_blank"; link.rel = "noreferrer"; link.click(); setDownloading(false); } catch (err) { toast.error("Failed to display the report!"); console.error(err); setDownloading(false); } }; const consolidateResults = getStudentsConsolidateScore(); return ( <> {displaySelection && (
{ if (areAllSelected) { setSelectedCorporates([]); return; } setSelectedCorporates(corporates); }} isSelected={areAllSelected} /> {Object.keys(corporateUsers).map((corporateName) => { const group = corporateUsers[corporateName]; const isSelected = group.every((id) => selectedCorporates.includes(id)); const valueHash = getCorporatesScoresHash(group); const value = getConsolidateScoreStr(getConsolidateScore(valueHash)); return ( { if (isSelected) { setSelectedCorporates((prev) => prev.filter((x) => !group.includes(x))); return; } setSelectedCorporates((prev) => [...new Set([...prev, ...group])]); }} isSelected={isSelected} /> ); })}
)}
{ setStartDate(initialDate ?? moment("01/01/2023").toDate()); if (finalDate) { // basicly selecting a final day works as if I'm selecting the first // minute of that day. this way it covers the whole day setEndDate(moment(finalDate).endOf("day").toDate()); return; } setEndDate(null); }} />
{renderSearch()}
{page * SIZE + 1} - {(page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE} /{" "} {filteredRows.length}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
{consolidateResults.highest && ( {}} Icon={BsPersonFill} label={`Highest result: ${consolidateResults.highest.user}`} color="purple" /> )} {consolidateResults.lowest && ( {}} Icon={BsPersonFill} label={`Lowest result: ${consolidateResults.lowest.user}`} color="purple" /> )}
); }; export default MasterStatistical;