Solved a bug on the assignments because of the multiple exams

This commit is contained in:
Tiago Ribeiro
2024-01-29 15:31:41 +00:00
parent a862e59574
commit 8bff64dd13
2 changed files with 425 additions and 315 deletions

View File

@@ -1,79 +1,115 @@
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Module} from "@/interfaces"; import { Module } from "@/interfaces";
import {Assignment} from "@/interfaces/results"; import { Assignment } from "@/interfaces/results";
import {calculateBandScore} from "@/utils/score"; import { calculateBandScore } from "@/utils/score";
import clsx from "clsx"; import clsx from "clsx";
import moment from "moment"; import moment from "moment";
import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; import {
BsBook,
BsClipboard,
BsHeadphones,
BsMegaphone,
BsPen,
} from "react-icons/bs";
import { usePDFDownload } from "@/hooks/usePDFDownload"; import { usePDFDownload } from "@/hooks/usePDFDownload";
import { uniqBy } from "lodash";
interface Props { interface Props {
onClick?: () => void; onClick?: () => void;
allowDownload?: boolean; allowDownload?: boolean;
} }
export default function AssignmentCard({id, name, assigner, startDate, endDate, assignees, results, exams, onClick, allowDownload}: Assignment & Props) { export default function AssignmentCard({
const {users} = useUsers(); id,
const renderPdfIcon = usePDFDownload("assignments"); name,
assigner,
startDate,
endDate,
assignees,
results,
exams,
onClick,
allowDownload,
}: Assignment & Props) {
const { users } = useUsers();
const renderPdfIcon = usePDFDownload("assignments");
const calculateAverageModuleScore = (module: Module) => { const calculateAverageModuleScore = (module: Module) => {
const resultModuleBandScores = results.map((r) => { const resultModuleBandScores = results.map((r) => {
const moduleStats = r.stats.filter((s) => s.module === module); const moduleStats = r.stats.filter((s) => s.module === module);
const correct = moduleStats.reduce((acc, curr) => acc + curr.score.correct, 0); const correct = moduleStats.reduce(
const total = moduleStats.reduce((acc, curr) => acc + curr.score.total, 0); (acc, curr) => acc + curr.score.correct,
return calculateBandScore(correct, total, module, r.type); 0,
}); );
const total = moduleStats.reduce(
(acc, curr) => acc + curr.score.total,
0,
);
return calculateBandScore(correct, total, module, r.type);
});
return resultModuleBandScores.length === 0 ? -1 : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) / results.length; return resultModuleBandScores.length === 0
}; ? -1
: resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) /
results.length;
};
return ( return (
<div <div
onClick={onClick} onClick={onClick}
className="w-[350px] h-fit flex flex-col gap-6 bg-white border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300"> className="border-mti-gray-platinum flex h-fit w-[350px] cursor-pointer flex-col gap-6 rounded-xl border bg-white p-4 transition duration-300 ease-in-out hover:drop-shadow"
<div className="flex flex-col gap-3"> >
<div className="flex flex-row justify-between"> <div className="flex flex-col gap-3">
<h3 className="font-semibold text-xl">{name}</h3> <div className="flex flex-row justify-between">
{allowDownload && renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")} <h3 className="text-xl font-semibold">{name}</h3>
</div> {allowDownload &&
<ProgressBar renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
color={results.length / assignees.length < 0.5 ? "red" : "purple"} </div>
percentage={(results.length / assignees.length) * 100} <ProgressBar
label={`${results.length}/${assignees.length}`} color={results.length / assignees.length < 0.5 ? "red" : "purple"}
className="h-5" percentage={(results.length / assignees.length) * 100}
textClassName={results.length / assignees.length < 0.5 ? "!text-mti-gray-dim font-light" : "text-white"} label={`${results.length}/${assignees.length}`}
/> className="h-5"
</div> textClassName={
<span className="flex gap-1 justify-between"> results.length / assignees.length < 0.5
<span>{moment(startDate).format("DD/MM/YY, HH:mm")}</span> ? "!text-mti-gray-dim font-light"
<span>-</span> : "text-white"
<span>{moment(endDate).format("DD/MM/YY, HH:mm")}</span> }
</span> />
<div className="grid grid-cols-4 gap-2 place-items-start w-full -md:mt-2"> </div>
{exams.map(({module}) => ( <span className="flex justify-between gap-1">
<div <span>{moment(startDate).format("DD/MM/YY, HH:mm")}</span>
key={module} <span>-</span>
className={clsx( <span>{moment(endDate).format("DD/MM/YY, HH:mm")}</span>
"flex gap-2 items-center w-fit text-white -md:px-4 xl:px-4 md:px-2 py-2 rounded-xl", </span>
module === "reading" && "bg-ielts-reading", <div className="-md:mt-2 grid w-full grid-cols-4 place-items-start gap-2">
module === "listening" && "bg-ielts-listening", {uniqBy(exams, (x) => x.module).map(({ module }) => (
module === "writing" && "bg-ielts-writing", <div
module === "speaking" && "bg-ielts-speaking", key={module}
module === "level" && "bg-ielts-level", className={clsx(
)}> "-md:px-4 flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4",
{module === "reading" && <BsBook className="w-4 h-4" />} module === "reading" && "bg-ielts-reading",
{module === "listening" && <BsHeadphones className="w-4 h-4" />} module === "listening" && "bg-ielts-listening",
{module === "writing" && <BsPen className="w-4 h-4" />} module === "writing" && "bg-ielts-writing",
{module === "speaking" && <BsMegaphone className="w-4 h-4" />} module === "speaking" && "bg-ielts-speaking",
{module === "level" && <BsClipboard className="w-4 h-4" />} module === "level" && "bg-ielts-level",
{calculateAverageModuleScore(module) > -1 && ( )}
<span className="text-sm">{calculateAverageModuleScore(module).toFixed(1)}</span> >
)} {module === "reading" && <BsBook className="h-4 w-4" />}
</div> {module === "listening" && <BsHeadphones className="h-4 w-4" />}
))} {module === "writing" && <BsPen className="h-4 w-4" />}
</div> {module === "speaking" && <BsMegaphone className="h-4 w-4" />}
</div> {module === "level" && <BsClipboard className="h-4 w-4" />}
); {calculateAverageModuleScore(module) > -1 && (
<span className="text-sm">
{calculateAverageModuleScore(module).toFixed(1)}
</span>
)}
</div>
))}
</div>
</div>
);
} }

View File

@@ -1,280 +1,354 @@
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Module} from "@/interfaces"; import { Module } from "@/interfaces";
import {Assignment} from "@/interfaces/results"; import { Assignment } from "@/interfaces/results";
import {Stat, User} from "@/interfaces/user"; import { Stat, User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {getExamById} from "@/utils/exams"; import { getExamById } from "@/utils/exams";
import {sortByModule} from "@/utils/moduleUtils"; import { sortByModule } from "@/utils/moduleUtils";
import {calculateBandScore} from "@/utils/score"; import { calculateBandScore } from "@/utils/score";
import {convertToUserSolutions} from "@/utils/stats"; import { convertToUserSolutions } from "@/utils/stats";
import clsx from "clsx"; import clsx from "clsx";
import {capitalize, uniqBy} from "lodash"; import { capitalize, uniqBy } from "lodash";
import moment from "moment"; import moment from "moment";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; import {
BsBook,
BsClipboard,
BsHeadphones,
BsMegaphone,
BsPen,
} from "react-icons/bs";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
assignment?: Assignment; assignment?: Assignment;
onClose: () => void; onClose: () => void;
} }
export default function AssignmentView({isOpen, assignment, onClose}: Props) { export default function AssignmentView({ isOpen, assignment, onClose }: Props) {
const {users} = useUsers(); const { users } = useUsers();
const router = useRouter(); const router = useRouter();
const setExams = useExamStore((state) => state.setExams); const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setShowSolutions = useExamStore((state) => state.setShowSolutions);
const setUserSolutions = useExamStore((state) => state.setUserSolutions); const setUserSolutions = useExamStore((state) => state.setUserSolutions);
const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const formatTimestamp = (timestamp: string) => { const formatTimestamp = (timestamp: string) => {
const date = moment(parseInt(timestamp)); const date = moment(parseInt(timestamp));
const formatter = "YYYY/MM/DD - HH:mm"; const formatter = "YYYY/MM/DD - HH:mm";
return date.format(formatter); return date.format(formatter);
}; };
const calculateAverageModuleScore = (module: Module) => { const calculateAverageModuleScore = (module: Module) => {
if (!assignment) return -1; if (!assignment) return -1;
const resultModuleBandScores = assignment.results.map((r) => { const resultModuleBandScores = assignment.results.map((r) => {
const moduleStats = r.stats.filter((s) => s.module === module); const moduleStats = r.stats.filter((s) => s.module === module);
const correct = moduleStats.reduce((acc, curr) => acc + curr.score.correct, 0); const correct = moduleStats.reduce(
const total = moduleStats.reduce((acc, curr) => acc + curr.score.total, 0); (acc, curr) => acc + curr.score.correct,
return calculateBandScore(correct, total, module, r.type); 0,
}); );
const total = moduleStats.reduce(
(acc, curr) => acc + curr.score.total,
0,
);
return calculateBandScore(correct, total, module, r.type);
});
return resultModuleBandScores.length === 0 ? -1 : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) / assignment.results.length; return resultModuleBandScores.length === 0
}; ? -1
: resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) /
assignment.results.length;
};
const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => { const aggregateScoresByModule = (
const scores: {[key in Module]: {total: number; missing: number; correct: number}} = { stats: Stat[],
reading: { ): { module: Module; total: number; missing: number; correct: number }[] => {
total: 0, const scores: {
correct: 0, [key in Module]: { total: number; missing: number; correct: number };
missing: 0, } = {
}, reading: {
listening: { total: 0,
total: 0, correct: 0,
correct: 0, missing: 0,
missing: 0, },
}, listening: {
writing: { total: 0,
total: 0, correct: 0,
correct: 0, missing: 0,
missing: 0, },
}, writing: {
speaking: { total: 0,
total: 0, correct: 0,
correct: 0, missing: 0,
missing: 0, },
}, speaking: {
level: { total: 0,
total: 0, correct: 0,
correct: 0, missing: 0,
missing: 0, },
}, level: {
}; total: 0,
correct: 0,
missing: 0,
},
};
stats.forEach((x) => { stats.forEach((x) => {
scores[x.module!] = { scores[x.module!] = {
total: scores[x.module!].total + x.score.total, total: scores[x.module!].total + x.score.total,
correct: scores[x.module!].correct + x.score.correct, correct: scores[x.module!].correct + x.score.correct,
missing: scores[x.module!].missing + x.score.missing, missing: scores[x.module!].missing + x.score.missing,
}; };
}); });
return Object.keys(scores) return Object.keys(scores)
.filter((x) => scores[x as Module].total > 0) .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 customContent = (stats: Stat[], user: string, focus: "academic" | "general") => { const customContent = (
const correct = stats.reduce((accumulator, current) => accumulator + current.score.correct, 0); stats: Stat[],
const total = stats.reduce((accumulator, current) => accumulator + current.score.total, 0); user: string,
const aggregatedScores = aggregateScoresByModule(stats).filter((x) => x.total > 0); focus: "academic" | "general",
) => {
const correct = stats.reduce(
(accumulator, current) => accumulator + current.score.correct,
0,
);
const total = stats.reduce(
(accumulator, current) => accumulator + current.score.total,
0,
);
const aggregatedScores = aggregateScoresByModule(stats).filter(
(x) => x.total > 0,
);
const aggregatedLevels = aggregatedScores.map((x) => ({ const aggregatedLevels = aggregatedScores.map((x) => ({
module: x.module, module: x.module,
level: calculateBandScore(x.correct, x.total, x.module, focus), level: calculateBandScore(x.correct, x.total, x.module, focus),
})); }));
const timeSpent = stats[0].timeSpent; const timeSpent = stats[0].timeSpent;
const selectExam = () => { const selectExam = () => {
const examPromises = uniqBy(stats, "exam").map((stat) => getExamById(stat.module, stat.exam)); const examPromises = uniqBy(stats, "exam").map((stat) =>
getExamById(stat.module, stat.exam),
);
Promise.all(examPromises).then((exams) => { Promise.all(examPromises).then((exams) => {
if (exams.every((x) => !!x)) { if (exams.every((x) => !!x)) {
setUserSolutions(convertToUserSolutions(stats)); setUserSolutions(convertToUserSolutions(stats));
setShowSolutions(true); setShowSolutions(true);
setExams(exams.map((x) => x!).sort(sortByModule)); setExams(exams.map((x) => x!).sort(sortByModule));
setSelectedModules( setSelectedModules(
exams exams
.map((x) => x!) .map((x) => x!)
.sort(sortByModule) .sort(sortByModule)
.map((x) => x!.module), .map((x) => x!.module),
); );
router.push("/exercises"); router.push("/exercises");
} }
}); });
}; };
const content = ( const content = (
<> <>
<div className="w-full flex justify-between -md:items-center 2xl:items-center"> <div className="-md:items-center flex w-full justify-between 2xl:items-center">
<div className="flex md:flex-col 2xl:flex-row md:gap-1 -md:gap-2 2xl:gap-2 -md:items-center 2xl:items-center"> <div className="-md:gap-2 -md:items-center flex md:flex-col md:gap-1 2xl:flex-row 2xl:items-center 2xl:gap-2">
<span className="font-medium">{formatTimestamp(stats[0].date.toString())}</span> <span className="font-medium">
{timeSpent && ( {formatTimestamp(stats[0].date.toString())}
<> </span>
<span className="md:hidden 2xl:flex"> </span> {timeSpent && (
<span className="text-sm">{Math.floor(timeSpent / 60)} minutes</span> <>
</> <span className="md:hidden 2xl:flex"> </span>
)} <span className="text-sm">
</div> {Math.floor(timeSpent / 60)} minutes
<span </span>
className={clsx( </>
correct / total >= 0.7 && "text-mti-purple", )}
correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red", </div>
correct / total < 0.3 && "text-mti-rose", <span
)}> className={clsx(
Level{" "} correct / total >= 0.7 && "text-mti-purple",
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)} correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
</span> correct / total < 0.3 && "text-mti-rose",
</div> )}
>
Level{" "}
{(
aggregatedLevels.reduce(
(accumulator, current) => accumulator + current.level,
0,
) / aggregatedLevels.length
).toFixed(1)}
</span>
</div>
<div className="w-full flex flex-col gap-1"> <div className="flex w-full flex-col gap-1">
<div className="grid grid-cols-4 gap-2 place-items-start w-full -md:mt-2"> <div className="-md:mt-2 grid w-full grid-cols-4 place-items-start gap-2">
{aggregatedLevels.map(({module, level}) => ( {aggregatedLevels.map(({ module, level }) => (
<div <div
key={module} key={module}
className={clsx( className={clsx(
"flex gap-2 items-center w-fit text-white -md:px-4 xl:px-4 md:px-2 py-2 rounded-xl", "-md:px-4 flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4",
module === "reading" && "bg-ielts-reading", module === "reading" && "bg-ielts-reading",
module === "listening" && "bg-ielts-listening", module === "listening" && "bg-ielts-listening",
module === "writing" && "bg-ielts-writing", module === "writing" && "bg-ielts-writing",
module === "speaking" && "bg-ielts-speaking", module === "speaking" && "bg-ielts-speaking",
module === "level" && "bg-ielts-level", module === "level" && "bg-ielts-level",
)}> )}
{module === "reading" && <BsBook className="w-4 h-4" />} >
{module === "listening" && <BsHeadphones className="w-4 h-4" />} {module === "reading" && <BsBook className="h-4 w-4" />}
{module === "writing" && <BsPen className="w-4 h-4" />} {module === "listening" && <BsHeadphones className="h-4 w-4" />}
{module === "speaking" && <BsMegaphone className="w-4 h-4" />} {module === "writing" && <BsPen className="h-4 w-4" />}
{module === "level" && <BsClipboard className="w-4 h-4" />} {module === "speaking" && <BsMegaphone className="h-4 w-4" />}
<span className="text-sm">{level.toFixed(1)}</span> {module === "level" && <BsClipboard className="h-4 w-4" />}
</div> <span className="text-sm">{level.toFixed(1)}</span>
))} </div>
</div> ))}
</div> </div>
</> </div>
); </>
);
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span> <span>
{(() => { {(() => {
const student = users.find((u) => u.id === user); const student = users.find((u) => u.id === user);
return `${student?.name} (${student?.email})`; return `${student?.name} (${student?.email})`;
})()} })()}
</span> </span>
<div <div
key={user} key={user}
className={clsx( className={clsx(
"flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:hidden", "border-mti-gray-platinum -md:hidden flex cursor-pointer flex-col gap-4 rounded-xl border p-4 transition duration-300 ease-in-out",
correct / total >= 0.7 && "hover:border-mti-purple", correct / total >= 0.7 && "hover:border-mti-purple",
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red", correct / total >= 0.3 &&
correct / total < 0.3 && "hover:border-mti-rose", correct / total < 0.7 &&
)} "hover:border-mti-red",
onClick={selectExam} correct / total < 0.3 && "hover:border-mti-rose",
role="button"> )}
{content} onClick={selectExam}
</div> role="button"
<div >
key={user} {content}
className={clsx( </div>
"flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:tooltip md:hidden", <div
correct / total >= 0.7 && "hover:border-mti-purple", key={user}
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red", className={clsx(
correct / total < 0.3 && "hover:border-mti-rose", "border-mti-gray-platinum -md:tooltip flex cursor-pointer flex-col gap-4 rounded-xl border p-4 transition duration-300 ease-in-out md:hidden",
)} correct / total >= 0.7 && "hover:border-mti-purple",
data-tip="Your screen size is too small to view previous exams." correct / total >= 0.3 &&
role="button"> correct / total < 0.7 &&
{content} "hover:border-mti-red",
</div> correct / total < 0.3 && "hover:border-mti-rose",
</div> )}
); data-tip="Your screen size is too small to view previous exams."
}; role="button"
>
{content}
</div>
</div>
);
};
return ( return (
<Modal isOpen={isOpen} onClose={onClose} title={assignment?.name}> <Modal isOpen={isOpen} onClose={onClose} title={assignment?.name}>
<div className="mt-4 flex flex-col w-full gap-4"> <div className="mt-4 flex w-full flex-col gap-4">
<ProgressBar <ProgressBar
color="purple" color="purple"
label={`${assignment?.results.length}/${assignment?.assignees.length} assignees completed`} label={`${assignment?.results.length}/${assignment?.assignees.length} assignees completed`}
className="h-6" className="h-6"
textClassName={ textClassName={
(assignment?.results.length || 0) / (assignment?.assignees.length || 1) < 0.5 ? "!text-mti-gray-dim font-light" : "text-white" (assignment?.results.length || 0) /
} (assignment?.assignees.length || 1) <
percentage={((assignment?.results.length || 0) / (assignment?.assignees.length || 1)) * 100} 0.5
/> ? "!text-mti-gray-dim font-light"
<div className="flex gap-8 items-start"> : "text-white"
<div className="flex flex-col gap-2"> }
<span>Start Date: {moment(assignment?.startDate).format("DD/MM/YY, HH:mm")}</span> percentage={
<span>End Date: {moment(assignment?.endDate).format("DD/MM/YY, HH:mm")}</span> ((assignment?.results.length || 0) /
</div> (assignment?.assignees.length || 1)) *
<span> 100
Assignees:{" "} }
{users />
.filter((u) => assignment?.assignees.includes(u.id)) <div className="flex items-start gap-8">
.map((u) => `${u.name} (${u.email})`) <div className="flex flex-col gap-2">
.join(", ")} <span>
</span> Start Date:{" "}
</div> {moment(assignment?.startDate).format("DD/MM/YY, HH:mm")}
<div className="flex flex-col gap-2"> </span>
<span className="text-xl font-bold">Average Scores</span> <span>
<div className="grid grid-cols-4 gap-2 place-items-start w-full -md:mt-2"> End Date: {moment(assignment?.endDate).format("DD/MM/YY, HH:mm")}
{assignment?.exams.map(({module}) => ( </span>
<div </div>
data-tip={capitalize(module)} <span>
key={module} Assignees:{" "}
className={clsx( {users
"flex gap-2 items-center w-fit text-white -md:px-4 xl:px-4 md:px-2 py-2 rounded-xl tooltip", .filter((u) => assignment?.assignees.includes(u.id))
module === "reading" && "bg-ielts-reading", .map((u) => `${u.name} (${u.email})`)
module === "listening" && "bg-ielts-listening", .join(", ")}
module === "writing" && "bg-ielts-writing", </span>
module === "speaking" && "bg-ielts-speaking", </div>
module === "level" && "bg-ielts-level", <div className="flex flex-col gap-2">
)}> <span className="text-xl font-bold">Average Scores</span>
{module === "reading" && <BsBook className="w-4 h-4" />} <div className="-md:mt-2 flex w-full items-center gap-4">
{module === "listening" && <BsHeadphones className="w-4 h-4" />} {assignment &&
{module === "writing" && <BsPen className="w-4 h-4" />} uniqBy(assignment.exams, (x) => x.module).map(({ module }) => (
{module === "speaking" && <BsMegaphone className="w-4 h-4" />} <div
{module === "level" && <BsClipboard className="w-4 h-4" />} data-tip={capitalize(module)}
{calculateAverageModuleScore(module) > -1 && ( key={module}
<span className="text-sm">{calculateAverageModuleScore(module).toFixed(1)}</span> className={clsx(
)} "-md:px-4 tooltip flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4",
</div> module === "reading" && "bg-ielts-reading",
))} module === "listening" && "bg-ielts-listening",
</div> module === "writing" && "bg-ielts-writing",
</div> module === "speaking" && "bg-ielts-speaking",
<div className="flex flex-col gap-2"> module === "level" && "bg-ielts-level",
<span className="text-xl font-bold"> )}
Results ({assignment?.results.length}/{assignment?.assignees.length}) >
</span> {module === "reading" && <BsBook className="h-4 w-4" />}
<div> {module === "listening" && (
{assignment && assignment?.results.length > 0 && ( <BsHeadphones className="h-4 w-4" />
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 w-full gap-4 xl:gap-6"> )}
{assignment.results.map((r) => customContent(r.stats, r.user, r.type))} {module === "writing" && <BsPen className="h-4 w-4" />}
</div> {module === "speaking" && <BsMegaphone className="h-4 w-4" />}
)} {module === "level" && <BsClipboard className="h-4 w-4" />}
{assignment && assignment?.results.length === 0 && <span className="font-semibold ml-1">No results yet...</span>} {calculateAverageModuleScore(module) > -1 && (
</div> <span className="text-sm">
</div> {calculateAverageModuleScore(module).toFixed(1)}
</div> </span>
</Modal> )}
); </div>
))}
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-xl font-bold">
Results ({assignment?.results.length}/{assignment?.assignees.length}
)
</span>
<div>
{assignment && assignment?.results.length > 0 && (
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 xl:gap-6">
{assignment.results.map((r) =>
customContent(r.stats, r.user, r.type),
)}
</div>
)}
{assignment && assignment?.results.length === 0 && (
<span className="ml-1 font-semibold">No results yet...</span>
)}
</div>
</div>
</div>
</Modal>
);
} }