diff --git a/src/exams/pdf/details/level.exam.tsx b/src/exams/pdf/details/level.exam.tsx index 9230a7c0..9ab87a9b 100644 --- a/src/exams/pdf/details/level.exam.tsx +++ b/src/exams/pdf/details/level.exam.tsx @@ -5,6 +5,7 @@ import { styles } from "../styles"; import { RadialResult } from "./radial.result"; interface Props { detail: ModuleScore; + title: string; } const thresholds = [ @@ -63,7 +64,7 @@ const customStyles = StyleSheet.create({ }, }); -export const LevelExamDetails = ({ detail }: Props) => { +export const LevelExamDetails = ({ detail, title }: Props) => { const updatedThresholds = thresholds.map((t) => ({ ...t, match: detail.score >= t.minValue && detail.score <= t.maxValue, @@ -86,7 +87,7 @@ export const LevelExamDetails = ({ detail }: Props) => { - Level as per CEFR Levels + {title} diff --git a/src/exams/pdf/details/radial.result.tsx b/src/exams/pdf/details/radial.result.tsx index 4bec5181..125983a2 100644 --- a/src/exams/pdf/details/radial.result.tsx +++ b/src/exams/pdf/details/radial.result.tsx @@ -1,39 +1,22 @@ /* eslint-disable jsx-a11y/alt-text */ import React from "react"; -import { View, Text, Image, StyleSheet } from "@react-pdf/renderer"; +import { View, Text, Image } from "@react-pdf/renderer"; import { styles } from "../styles"; import { ModuleScore } from "@/interfaces/module.scores"; -const customStyles = StyleSheet.create({ - container: { - display: "flex", - flexDirection: "column", - alignItems: "center", - gap: 4, - position: "relative", - }, - resultContainer: { - display: "flex", - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - alignItems: "center", - justifyContent: "center", - fontSize: 10, - gap: 8, - }, -}); - -export const RadialResult = ({ module, score, total, png }: ModuleScore) => ( - - - {module} - +export const RadialResult = ({ + module, + score, + total, + png, +}: ModuleScore) => ( + + + {module} + - + {score} out of {total} diff --git a/src/exams/pdf/group.test.report.tsx b/src/exams/pdf/group.test.report.tsx index 10535510..c7fa48b9 100644 --- a/src/exams/pdf/group.test.report.tsx +++ b/src/exams/pdf/group.test.report.tsx @@ -13,7 +13,6 @@ import { styles } from "./styles"; import TestReportFooter from "./test.report.footer"; import { ModuleScore, StudentData } from "@/interfaces/module.scores"; import ProgressBar from "./progress.bar"; - Font.registerHyphenationCallback((word) => [word]); interface Props { @@ -32,6 +31,9 @@ interface Props { institution: string; studentsData: StudentData[]; showLevel: boolean; + summaryPNG: string; + summaryScore: string; + groupScoreSummary: any[]; } const customStyles = StyleSheet.create({ @@ -44,13 +46,11 @@ const customStyles = StyleSheet.create({ flexDirection: "column", // maxWidth: "600px", // margin: "0 auto", - border: "1px solid #ccc", // borderCollapse: 'collapse', }, tableRow: { display: "flex", flexDirection: "row", - borderBottom: "1px solid #ccc", }, tableHeader: { fontWeight: "bold", @@ -80,6 +80,9 @@ const GroupTestReport = ({ institution, studentsData, showLevel, + summaryPNG, + summaryScore, + groupScoreSummary, }: Props) => { const defaultTextStyle = [styles.textFont, { fontSize: 8 }]; const defaultSkillsTextStyle = [styles.textFont, { fontSize: 8 }]; @@ -150,8 +153,16 @@ const GroupTestReport = ({ > Group Overall Performance Summary - - {summary} + + + {summary} + + + + + {summaryScore} + + @@ -173,13 +184,44 @@ const GroupTestReport = ({ gap: 8, }} > - {testDetails - .filter( - ({ suggestions, evaluation }) => suggestions || evaluation - ) - .map(({ module, suggestions, evaluation }) => ( - TODO + + {groupScoreSummary.map(({ label, percent, description }) => ( + + + {label} + + + + + + {percent} + + {description} + ))} + @@ -205,7 +247,7 @@ const GroupTestReport = ({ style={[ customStyles.table, styles.textFont, - { width: "100%", fontSize: "8px" }, + { border: "1px solid #ccc", width: "100%", fontSize: "8px" }, ]} > @@ -229,11 +272,17 @@ const GroupTestReport = ({ Result - Level + {showLevel && Level} {studentsData.map( ({ id, name, email, gender, date, result, level }, index) => ( - + {result} - {level} + {showLevel && ( + {level} + )} ) )} diff --git a/src/exams/pdf/progress.bar.tsx b/src/exams/pdf/progress.bar.tsx index ef701132..7ee7060c 100644 --- a/src/exams/pdf/progress.bar.tsx +++ b/src/exams/pdf/progress.bar.tsx @@ -40,7 +40,7 @@ const ProgressBar = ({ > diff --git a/src/exams/pdf/styles.ts b/src/exams/pdf/styles.ts index 00bbc484..7cde6859 100644 --- a/src/exams/pdf/styles.ts +++ b/src/exams/pdf/styles.ts @@ -51,4 +51,23 @@ export const styles = StyleSheet.create({ width: "80px", height: "80px", }, + radialContainer: { + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 4, + position: "relative", + }, + radialResultContainer: { + display: "flex", + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + alignItems: "center", + justifyContent: "center", + fontSize: 10, + gap: 8, + }, }); diff --git a/src/interfaces/module.scores.ts b/src/interfaces/module.scores.ts index 07d6c65e..2a4d4149 100644 --- a/src/interfaces/module.scores.ts +++ b/src/interfaces/module.scores.ts @@ -18,4 +18,5 @@ export interface ModuleScore { date: string; result: string; level?: string; + bandScore: number; } \ No newline at end of file diff --git a/src/pages/api/assignments/[id]/export.tsx b/src/pages/api/assignments/[id]/export.tsx index 7e4e0867..d78fbaf3 100644 --- a/src/pages/api/assignments/[id]/export.tsx +++ b/src/pages/api/assignments/[id]/export.tsx @@ -32,6 +32,11 @@ import { streamToBuffer, } from "@/utils/pdf"; +interface GroupScoreSummaryHelper { + score: [number, number]; + label: string; + sessions: string[]; +} const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); @@ -99,13 +104,7 @@ const getScoreAndTotal = (stats: Stat[]) => { ); }; -const getLevelScoreForUserExams = ( - correct: number, - total: number, - module: Module, - focus: "academic" | "general" -) => { - const bandScore = calculateBandScore(correct, total, module, focus); +const getLevelScoreForUserExams = (bandScore: number) => { const [level] = getLevelScore(bandScore); return level; }; @@ -158,20 +157,45 @@ async function post(req: NextApiRequest, res: NextApiResponse) { [] ) as Stat[]; + const docsSnap = await getDocs( + query( + collection(db, "users"), + where(documentId(), "in", data.assignees) + ) + ); + const users = docsSnap.docs.map((d) => ({ + ...d.data(), + id: d.id, + })) as User[]; + + const flattenResultsWithGrade = flattenResults.map((e) => { + const focus = users.find((u) => u.id === e.user)?.focus || "academic"; + const bandScore = calculateBandScore( + e.score.correct, + e.score.total, + e.module, + focus + ); + + return { ...e, bandScore }; + }); + const moduleResults = data.exams.map(({ module }) => { - const moduleResults = flattenResults.filter( + const moduleResults = flattenResultsWithGrade.filter( (e) => e.module === module ); + const bandScore = + moduleResults.reduce((accm, curr) => accm + curr.bandScore, 0) / + moduleResults.length; const { correct, total } = getScoreAndTotal(moduleResults); - const score = calculateBandScore(correct, total, module, "academic"); - const png = getRadialProgressPNG("azul", score, total); + const png = getRadialProgressPNG("azul", correct, total); return { - bandScore: score, + bandScore, png, module: module[0].toUpperCase() + module.substring(1), - score, + score: bandScore, total, code: module, }; @@ -181,12 +205,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) { getScoreAndTotal(flattenResults); const overallResult = overallCorrect / overallTotal; + const overallPNG = getRadialProgressPNG( + "laranja", + overallCorrect, + overallTotal + ); // generate the overall detail report const overallDetail = { module: "Overall", score: overallCorrect, total: overallTotal, - png: getRadialProgressPNG("laranja", overallCorrect, overallTotal), + png: overallPNG, } as ModuleScore; const testDetails = [overallDetail, ...moduleResults]; @@ -207,7 +236,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) { if (showLevel) { return { title: "GROUP ENGLISH LEVEL TEST RESULT REPORT ", - details: , + details: ( + + ), }; } @@ -222,21 +256,9 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const numberOfStudents = data.assignees.length; const getStudentsData = async (): Promise => { - // const usersCol = collection(db, "users"); - const docsSnap = await getDocs( - query( - collection(db, "users"), - where(documentId(), "in", data.assignees) - ) - ); - const users = docsSnap.docs.map((d) => ({ - ...d.data(), - id: d.id, - })) as User[]; - return data.assignees.map((id) => { const user = users.find((u) => u.id === id); - const exams = flattenResults.filter((e) => e.user === id); + const exams = flattenResultsWithGrade.filter((e) => e.user === id); const date = exams.length === 0 ? "N/A" @@ -246,6 +268,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) { day: "numeric", }); + const bandScore = + exams.length === 0 + ? 0 + : exams.reduce((accm, curr) => accm + curr.bandScore, 0) / + exams.length; const { correct, total } = getScoreAndTotal(exams); const result = exams.length === 0 ? "N/A" : `${correct}/${total}`; @@ -258,19 +285,60 @@ async function post(req: NextApiRequest, res: NextApiResponse) { date, result, level: showLevel - ? getLevelScoreForUserExams( - correct, - total, - baseStat.module, - user?.focus || "academic" - ) - : "", + ? getLevelScoreForUserExams(bandScore) + : undefined, + bandScore, }; }); }; const studentsData = await getStudentsData(); + const getGroupScoreSummary = () => { + const resultHelper = studentsData.reduce( + (accm: GroupScoreSummaryHelper[], curr) => { + const { bandScore, id } = curr; + + const flooredScore = Math.floor(bandScore); + + const hasMatch = accm.find((a) => a.score.includes(flooredScore)); + if (hasMatch) { + return accm.map((a) => { + if (a.score.includes(flooredScore)) { + return { + ...a, + sessions: [...a.sessions, id], + }; + } + + return a; + }); + } + + return [ + ...accm, + { + score: [flooredScore, flooredScore + 0.5], + label: `${flooredScore} - ${flooredScore + 0.5}`, + sessions: [id], + }, + ]; + }, + [] + ) as GroupScoreSummaryHelper[]; + + const result = resultHelper.map(({ label, sessions }) => { + return { + label, + percent: Math.floor((sessions.length / numberOfStudents) * 100), + description: `No. Candidates ${sessions.length} of ${numberOfStudents}`, + }; + }); + return result; + }; + + const groupScoreSummary = getGroupScoreSummary(); + const pdfStream = await ReactPDF.renderToStream( ); diff --git a/src/pages/api/stats/[id]/export.tsx b/src/pages/api/stats/[id]/export.tsx index bb465278..23f89bc7 100644 --- a/src/pages/api/stats/[id]/export.tsx +++ b/src/pages/api/stats/[id]/export.tsx @@ -284,7 +284,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) { if (stat.module === "level") { return { title: "ENGLISH LEVEL TEST RESULT REPORT ", - details: , + details: ( + + ), }; }