Final improvements for Groups PDF's
This commit is contained in:
@@ -5,6 +5,7 @@ import { styles } from "../styles";
|
|||||||
import { RadialResult } from "./radial.result";
|
import { RadialResult } from "./radial.result";
|
||||||
interface Props {
|
interface Props {
|
||||||
detail: ModuleScore;
|
detail: ModuleScore;
|
||||||
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const thresholds = [
|
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) => ({
|
const updatedThresholds = thresholds.map((t) => ({
|
||||||
...t,
|
...t,
|
||||||
match: detail.score >= t.minValue && detail.score <= t.maxValue,
|
match: detail.score >= t.minValue && detail.score <= t.maxValue,
|
||||||
@@ -86,7 +87,7 @@ export const LevelExamDetails = ({ detail }: Props) => {
|
|||||||
<Text
|
<Text
|
||||||
style={[styles.textBold, styles.textColor, { fontSize: "10px" }]}
|
style={[styles.textBold, styles.textColor, { fontSize: "10px" }]}
|
||||||
>
|
>
|
||||||
Level as per CEFR Levels
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={customStyles.tableBody}>
|
<View style={customStyles.tableBody}>
|
||||||
|
|||||||
@@ -1,39 +1,22 @@
|
|||||||
/* eslint-disable jsx-a11y/alt-text */
|
/* eslint-disable jsx-a11y/alt-text */
|
||||||
import React from "react";
|
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 { styles } from "../styles";
|
||||||
import { ModuleScore } from "@/interfaces/module.scores";
|
import { ModuleScore } from "@/interfaces/module.scores";
|
||||||
|
|
||||||
const customStyles = StyleSheet.create({
|
export const RadialResult = ({
|
||||||
container: {
|
module,
|
||||||
display: "flex",
|
score,
|
||||||
flexDirection: "column",
|
total,
|
||||||
alignItems: "center",
|
png,
|
||||||
gap: 4,
|
}: ModuleScore) => (
|
||||||
position: "relative",
|
<View style={[styles.textFont, styles.radialContainer]}>
|
||||||
},
|
<Text style={[styles.textColor, styles.textBold, { fontSize: 10 }]}>
|
||||||
resultContainer: {
|
{module}
|
||||||
display: "flex",
|
</Text>
|
||||||
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) => (
|
|
||||||
<View key="module" style={[styles.textFont, customStyles.container]}>
|
|
||||||
<Text style={[styles.textColor, styles.textBold, { fontSize: 10 }]}>
|
|
||||||
{module}
|
|
||||||
</Text>
|
|
||||||
<Image src={png} style={styles.image64}></Image>
|
<Image src={png} style={styles.image64}></Image>
|
||||||
<View style={[styles.textColor, customStyles.resultContainer]}>
|
<View style={[styles.textColor, styles.radialResultContainer]}>
|
||||||
<Text style={styles.textBold}>{score}</Text>
|
<Text style={styles.textBold}>{score}</Text>
|
||||||
<Text style={{ fontSize: 8 }}>out of {total}</Text>
|
<Text style={{ fontSize: 8 }}>out of {total}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { styles } from "./styles";
|
|||||||
import TestReportFooter from "./test.report.footer";
|
import TestReportFooter from "./test.report.footer";
|
||||||
import { ModuleScore, StudentData } from "@/interfaces/module.scores";
|
import { ModuleScore, StudentData } from "@/interfaces/module.scores";
|
||||||
import ProgressBar from "./progress.bar";
|
import ProgressBar from "./progress.bar";
|
||||||
|
|
||||||
Font.registerHyphenationCallback((word) => [word]);
|
Font.registerHyphenationCallback((word) => [word]);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -32,6 +31,9 @@ interface Props {
|
|||||||
institution: string;
|
institution: string;
|
||||||
studentsData: StudentData[];
|
studentsData: StudentData[];
|
||||||
showLevel: boolean;
|
showLevel: boolean;
|
||||||
|
summaryPNG: string;
|
||||||
|
summaryScore: string;
|
||||||
|
groupScoreSummary: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const customStyles = StyleSheet.create({
|
const customStyles = StyleSheet.create({
|
||||||
@@ -44,13 +46,11 @@ const customStyles = StyleSheet.create({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
// maxWidth: "600px",
|
// maxWidth: "600px",
|
||||||
// margin: "0 auto",
|
// margin: "0 auto",
|
||||||
border: "1px solid #ccc",
|
|
||||||
// borderCollapse: 'collapse',
|
// borderCollapse: 'collapse',
|
||||||
},
|
},
|
||||||
tableRow: {
|
tableRow: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
borderBottom: "1px solid #ccc",
|
|
||||||
},
|
},
|
||||||
tableHeader: {
|
tableHeader: {
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
@@ -80,6 +80,9 @@ const GroupTestReport = ({
|
|||||||
institution,
|
institution,
|
||||||
studentsData,
|
studentsData,
|
||||||
showLevel,
|
showLevel,
|
||||||
|
summaryPNG,
|
||||||
|
summaryScore,
|
||||||
|
groupScoreSummary,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const defaultTextStyle = [styles.textFont, { fontSize: 8 }];
|
const defaultTextStyle = [styles.textFont, { fontSize: 8 }];
|
||||||
const defaultSkillsTextStyle = [styles.textFont, { fontSize: 8 }];
|
const defaultSkillsTextStyle = [styles.textFont, { fontSize: 8 }];
|
||||||
@@ -150,8 +153,16 @@ const GroupTestReport = ({
|
|||||||
>
|
>
|
||||||
Group Overall Performance Summary
|
Group Overall Performance Summary
|
||||||
</Text>
|
</Text>
|
||||||
<View>
|
<View style={{ display: "flex", flexDirection: "row", gap: 16 }}>
|
||||||
<Text style={[styles.textFont, { fontSize: 8 }]}>{summary}</Text>
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={[styles.textFont, { fontSize: 8 }]}>{summary}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.textFont, styles.radialContainer]}>
|
||||||
|
<Image src={summaryPNG} style={styles.image64}></Image>
|
||||||
|
<View style={[styles.textColor, styles.radialResultContainer]}>
|
||||||
|
<Text style={styles.textBold}>{summaryScore}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={[{ paddingTop: 30 }, styles.separator]}></View>
|
<View style={[{ paddingTop: 30 }, styles.separator]}></View>
|
||||||
@@ -173,13 +184,44 @@ const GroupTestReport = ({
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{testDetails
|
<View
|
||||||
.filter(
|
style={[
|
||||||
({ suggestions, evaluation }) => suggestions || evaluation
|
customStyles.table,
|
||||||
)
|
styles.textFont,
|
||||||
.map(({ module, suggestions, evaluation }) => (
|
{ width: "100%", fontSize: "8px" },
|
||||||
<Text key={module}>TODO</Text>
|
]}
|
||||||
|
>
|
||||||
|
{groupScoreSummary.map(({ label, percent, description }) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
customStyles.tableRow,
|
||||||
|
{
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
key={label}
|
||||||
|
>
|
||||||
|
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<View style={customStyles.tableCell}>
|
||||||
|
<ProgressBar
|
||||||
|
width={200}
|
||||||
|
height={18}
|
||||||
|
backgroundColor="#fab7b0"
|
||||||
|
progressColor="#cc5b55"
|
||||||
|
percentage={percent}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[customStyles.tableCell, { maxWidth: "24px" }]}>
|
||||||
|
{percent}
|
||||||
|
</Text>
|
||||||
|
<Text style={customStyles.tableCell}>{description}</Text>
|
||||||
|
</View>
|
||||||
))}
|
))}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.alignRightRow}>
|
<View style={styles.alignRightRow}>
|
||||||
<Image src={qrcode} style={styles.qrcode} />
|
<Image src={qrcode} style={styles.qrcode} />
|
||||||
@@ -205,7 +247,7 @@ const GroupTestReport = ({
|
|||||||
style={[
|
style={[
|
||||||
customStyles.table,
|
customStyles.table,
|
||||||
styles.textFont,
|
styles.textFont,
|
||||||
{ width: "100%", fontSize: "8px" },
|
{ border: "1px solid #ccc", width: "100%", fontSize: "8px" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -213,6 +255,7 @@ const GroupTestReport = ({
|
|||||||
customStyles.tableRow,
|
customStyles.tableRow,
|
||||||
customStyles.tableHeader,
|
customStyles.tableHeader,
|
||||||
customStyles.tableCellHighlight,
|
customStyles.tableCellHighlight,
|
||||||
|
{ borderBottom: "1px solid #ccc" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={[customStyles.tableCell, { maxWidth: "24px" }]}>
|
<Text style={[customStyles.tableCell, { maxWidth: "24px" }]}>
|
||||||
@@ -229,11 +272,17 @@ const GroupTestReport = ({
|
|||||||
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
||||||
Result
|
Result
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={customStyles.tableCell}>Level</Text>
|
{showLevel && <Text style={customStyles.tableCell}>Level</Text>}
|
||||||
</View>
|
</View>
|
||||||
{studentsData.map(
|
{studentsData.map(
|
||||||
({ id, name, email, gender, date, result, level }, index) => (
|
({ id, name, email, gender, date, result, level }, index) => (
|
||||||
<View style={customStyles.tableRow} key={id}>
|
<View
|
||||||
|
style={[
|
||||||
|
customStyles.tableRow,
|
||||||
|
{ borderBottom: "1px solid #ccc" },
|
||||||
|
]}
|
||||||
|
key={id}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
customStyles.tableCell,
|
customStyles.tableCell,
|
||||||
@@ -254,7 +303,9 @@ const GroupTestReport = ({
|
|||||||
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
||||||
{result}
|
{result}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={customStyles.tableCell}>{level}</Text>
|
{showLevel && (
|
||||||
|
<Text style={customStyles.tableCell}>{level}</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const ProgressBar = ({
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{ width: `${percentage}px`, backgroundColor: progressColor },
|
{ width: `${percentage}%`, backgroundColor: progressColor },
|
||||||
styles.progressBarPerc,
|
styles.progressBarPerc,
|
||||||
]}
|
]}
|
||||||
></View>
|
></View>
|
||||||
|
|||||||
@@ -51,4 +51,23 @@ export const styles = StyleSheet.create({
|
|||||||
width: "80px",
|
width: "80px",
|
||||||
height: "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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ export interface ModuleScore {
|
|||||||
date: string;
|
date: string;
|
||||||
result: string;
|
result: string;
|
||||||
level?: string;
|
level?: string;
|
||||||
|
bandScore: number;
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,11 @@ import {
|
|||||||
streamToBuffer,
|
streamToBuffer,
|
||||||
} from "@/utils/pdf";
|
} from "@/utils/pdf";
|
||||||
|
|
||||||
|
interface GroupScoreSummaryHelper {
|
||||||
|
score: [number, number];
|
||||||
|
label: string;
|
||||||
|
sessions: string[];
|
||||||
|
}
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
|
|
||||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
@@ -99,13 +104,7 @@ const getScoreAndTotal = (stats: Stat[]) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLevelScoreForUserExams = (
|
const getLevelScoreForUserExams = (bandScore: number) => {
|
||||||
correct: number,
|
|
||||||
total: number,
|
|
||||||
module: Module,
|
|
||||||
focus: "academic" | "general"
|
|
||||||
) => {
|
|
||||||
const bandScore = calculateBandScore(correct, total, module, focus);
|
|
||||||
const [level] = getLevelScore(bandScore);
|
const [level] = getLevelScore(bandScore);
|
||||||
return level;
|
return level;
|
||||||
};
|
};
|
||||||
@@ -158,20 +157,45 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
[]
|
[]
|
||||||
) as Stat[];
|
) 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 = data.exams.map(({ module }) => {
|
||||||
const moduleResults = flattenResults.filter(
|
const moduleResults = flattenResultsWithGrade.filter(
|
||||||
(e) => e.module === module
|
(e) => e.module === module
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bandScore =
|
||||||
|
moduleResults.reduce((accm, curr) => accm + curr.bandScore, 0) /
|
||||||
|
moduleResults.length;
|
||||||
const { correct, total } = getScoreAndTotal(moduleResults);
|
const { correct, total } = getScoreAndTotal(moduleResults);
|
||||||
const score = calculateBandScore(correct, total, module, "academic");
|
const png = getRadialProgressPNG("azul", correct, total);
|
||||||
const png = getRadialProgressPNG("azul", score, total);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bandScore: score,
|
bandScore,
|
||||||
png,
|
png,
|
||||||
module: module[0].toUpperCase() + module.substring(1),
|
module: module[0].toUpperCase() + module.substring(1),
|
||||||
score,
|
score: bandScore,
|
||||||
total,
|
total,
|
||||||
code: module,
|
code: module,
|
||||||
};
|
};
|
||||||
@@ -181,12 +205,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
getScoreAndTotal(flattenResults);
|
getScoreAndTotal(flattenResults);
|
||||||
const overallResult = overallCorrect / overallTotal;
|
const overallResult = overallCorrect / overallTotal;
|
||||||
|
|
||||||
|
const overallPNG = getRadialProgressPNG(
|
||||||
|
"laranja",
|
||||||
|
overallCorrect,
|
||||||
|
overallTotal
|
||||||
|
);
|
||||||
// generate the overall detail report
|
// generate the overall detail report
|
||||||
const overallDetail = {
|
const overallDetail = {
|
||||||
module: "Overall",
|
module: "Overall",
|
||||||
score: overallCorrect,
|
score: overallCorrect,
|
||||||
total: overallTotal,
|
total: overallTotal,
|
||||||
png: getRadialProgressPNG("laranja", overallCorrect, overallTotal),
|
png: overallPNG,
|
||||||
} as ModuleScore;
|
} as ModuleScore;
|
||||||
|
|
||||||
const testDetails = [overallDetail, ...moduleResults];
|
const testDetails = [overallDetail, ...moduleResults];
|
||||||
@@ -207,7 +236,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (showLevel) {
|
if (showLevel) {
|
||||||
return {
|
return {
|
||||||
title: "GROUP ENGLISH LEVEL TEST RESULT REPORT ",
|
title: "GROUP ENGLISH LEVEL TEST RESULT REPORT ",
|
||||||
details: <LevelExamDetails detail={overallDetail} />,
|
details: (
|
||||||
|
<LevelExamDetails
|
||||||
|
detail={overallDetail}
|
||||||
|
title="Group Average CEFR"
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,21 +256,9 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const numberOfStudents = data.assignees.length;
|
const numberOfStudents = data.assignees.length;
|
||||||
|
|
||||||
const getStudentsData = async (): Promise<StudentData[]> => {
|
const getStudentsData = async (): Promise<StudentData[]> => {
|
||||||
// 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) => {
|
return data.assignees.map((id) => {
|
||||||
const user = users.find((u) => u.id === 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 =
|
const date =
|
||||||
exams.length === 0
|
exams.length === 0
|
||||||
? "N/A"
|
? "N/A"
|
||||||
@@ -246,6 +268,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bandScore =
|
||||||
|
exams.length === 0
|
||||||
|
? 0
|
||||||
|
: exams.reduce((accm, curr) => accm + curr.bandScore, 0) /
|
||||||
|
exams.length;
|
||||||
const { correct, total } = getScoreAndTotal(exams);
|
const { correct, total } = getScoreAndTotal(exams);
|
||||||
|
|
||||||
const result = exams.length === 0 ? "N/A" : `${correct}/${total}`;
|
const result = exams.length === 0 ? "N/A" : `${correct}/${total}`;
|
||||||
@@ -258,19 +285,60 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
date,
|
date,
|
||||||
result,
|
result,
|
||||||
level: showLevel
|
level: showLevel
|
||||||
? getLevelScoreForUserExams(
|
? getLevelScoreForUserExams(bandScore)
|
||||||
correct,
|
: undefined,
|
||||||
total,
|
bandScore,
|
||||||
baseStat.module,
|
|
||||||
user?.focus || "academic"
|
|
||||||
)
|
|
||||||
: "",
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const studentsData = await getStudentsData();
|
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(
|
const pdfStream = await ReactPDF.renderToStream(
|
||||||
<GroupTestReport
|
<GroupTestReport
|
||||||
title={title}
|
title={title}
|
||||||
@@ -288,6 +356,9 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
institution="TODO: PLACEHOLDER"
|
institution="TODO: PLACEHOLDER"
|
||||||
studentsData={studentsData}
|
studentsData={studentsData}
|
||||||
showLevel={showLevel}
|
showLevel={showLevel}
|
||||||
|
summaryPNG={overallPNG}
|
||||||
|
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
||||||
|
groupScoreSummary={groupScoreSummary}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -284,7 +284,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (stat.module === "level") {
|
if (stat.module === "level") {
|
||||||
return {
|
return {
|
||||||
title: "ENGLISH LEVEL TEST RESULT REPORT ",
|
title: "ENGLISH LEVEL TEST RESULT REPORT ",
|
||||||
details: <LevelExamDetails detail={overallDetail} />,
|
details: (
|
||||||
|
<LevelExamDetails
|
||||||
|
detail={overallDetail}
|
||||||
|
title="Level as per CEFR Levels"
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user