Merged in bug-fixing-16-jan-24 (pull request #26)
Report PDF improvements / bugs Approved-by: Tiago Ribeiro
This commit is contained in:
@@ -33,6 +33,7 @@ interface Props {
|
|||||||
summaryPNG: string;
|
summaryPNG: string;
|
||||||
summaryScore: string;
|
summaryScore: string;
|
||||||
groupScoreSummary: any[];
|
groupScoreSummary: any[];
|
||||||
|
passportId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const customStyles = StyleSheet.create({
|
const customStyles = StyleSheet.create({
|
||||||
@@ -81,6 +82,7 @@ const GroupTestReport = ({
|
|||||||
summaryPNG,
|
summaryPNG,
|
||||||
summaryScore,
|
summaryScore,
|
||||||
groupScoreSummary,
|
groupScoreSummary,
|
||||||
|
passportId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const defaultTextStyle = [styles.textFont, { fontSize: 8 }];
|
const defaultTextStyle = [styles.textFont, { fontSize: 8 }];
|
||||||
return (
|
return (
|
||||||
@@ -114,6 +116,7 @@ const GroupTestReport = ({
|
|||||||
<Text style={defaultTextStyle}>ID: {id}</Text>
|
<Text style={defaultTextStyle}>ID: {id}</Text>
|
||||||
<Text style={defaultTextStyle}>Email: {email}</Text>
|
<Text style={defaultTextStyle}>Email: {email}</Text>
|
||||||
<Text style={defaultTextStyle}>Gender: {gender}</Text>
|
<Text style={defaultTextStyle}>Gender: {gender}</Text>
|
||||||
|
<Text style={defaultTextStyle}>Passport ID: {passportId}</Text>
|
||||||
<Text style={defaultTextStyle}>
|
<Text style={defaultTextStyle}>
|
||||||
Total Number of Students: {numberOfStudents}
|
Total Number of Students: {numberOfStudents}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -203,7 +206,7 @@ const GroupTestReport = ({
|
|||||||
percentage={percent}
|
percentage={percent}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[customStyles.tableCell, { maxWidth: "24px" }]}>
|
<Text style={[customStyles.tableCell, { maxWidth: "48px" }]}>
|
||||||
{percent}%
|
{percent}%
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={customStyles.tableCell}>{description}</Text>
|
<Text style={customStyles.tableCell}>{description}</Text>
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export const styles = StyleSheet.create({
|
|||||||
fontFamily: "Helvetica-Bold",
|
fontFamily: "Helvetica-Bold",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
|
textNormal: {
|
||||||
|
fontWeight: "normal",
|
||||||
|
},
|
||||||
textColor: {
|
textColor: {
|
||||||
color: "#4e4969",
|
color: "#4e4969",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ const TestReportFooter = () => (
|
|||||||
>
|
>
|
||||||
<View style={[styles.spacedRow, styles.textMargin]}>
|
<View style={[styles.spacedRow, styles.textMargin]}>
|
||||||
<View>
|
<View>
|
||||||
<Text>Validity</Text>
|
<Text style={styles.textBold}>Validity</Text>
|
||||||
<Text>
|
<Text>
|
||||||
This report remains valid for a duration of three months from the test
|
This report remains valid for a duration of three months from the test
|
||||||
date.
|
date.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<Text>Confidential – circulated for concern people</Text>
|
<Text style={styles.textBold}>Confidential – <Text style={[styles.textFont, styles.textNormal]}>circulated for concern people</Text></Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ paddingTop: 10 }}>
|
<View style={{ paddingTop: 10 }}>
|
||||||
<Text>Declaration</Text>
|
<Text style={styles.textBold}>Declaration</Text>
|
||||||
<Text style={{ paddingTop: 5 }}>
|
<Text style={{ paddingTop: 5 }}>
|
||||||
We hereby declare that exam results on our platform, assessed by AI, are
|
We hereby declare that exam results on our platform, assessed by AI, are
|
||||||
not the sole determinants of candidates' English proficiency
|
not the sole determinants of candidates' English proficiency
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface Props {
|
|||||||
title: string;
|
title: string;
|
||||||
summaryPNG: string;
|
summaryPNG: string;
|
||||||
summaryScore: string;
|
summaryScore: string;
|
||||||
|
passportId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TestReport = ({
|
const TestReport = ({
|
||||||
@@ -43,6 +44,7 @@ const TestReport = ({
|
|||||||
renderDetails,
|
renderDetails,
|
||||||
summaryPNG,
|
summaryPNG,
|
||||||
summaryScore,
|
summaryScore,
|
||||||
|
passportId,
|
||||||
}: 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 }];
|
||||||
@@ -83,6 +85,7 @@ const TestReport = ({
|
|||||||
<Text style={defaultTextStyle}>ID: {id}</Text>
|
<Text style={defaultTextStyle}>ID: {id}</Text>
|
||||||
<Text style={defaultTextStyle}>Email: {email}</Text>
|
<Text style={defaultTextStyle}>Email: {email}</Text>
|
||||||
<Text style={defaultTextStyle}>Gender: {gender}</Text>
|
<Text style={defaultTextStyle}>Gender: {gender}</Text>
|
||||||
|
<Text style={defaultTextStyle}>Passport ID: {passportId}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ height: "120px" }}>
|
<View style={{ height: "120px" }}>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -1,33 +1,20 @@
|
|||||||
import type {NextApiRequest, NextApiResponse} from "next";
|
import type {NextApiRequest, NextApiResponse} from "next";
|
||||||
import {app, storage} from "@/firebase";
|
import {app, storage} from "@/firebase";
|
||||||
import {
|
import {getFirestore, doc, getDoc, updateDoc, getDocs, query, collection, where, documentId} from "firebase/firestore";
|
||||||
getFirestore,
|
|
||||||
doc,
|
|
||||||
getDoc,
|
|
||||||
updateDoc,
|
|
||||||
getDocs,
|
|
||||||
query,
|
|
||||||
collection,
|
|
||||||
where,
|
|
||||||
documentId,
|
|
||||||
} from "firebase/firestore";
|
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import ReactPDF from "@react-pdf/renderer";
|
import ReactPDF from "@react-pdf/renderer";
|
||||||
import GroupTestReport from "@/exams/pdf/group.test.report";
|
import GroupTestReport from "@/exams/pdf/group.test.report";
|
||||||
import {ref, uploadBytes, getDownloadURL} from "firebase/storage";
|
import {ref, uploadBytes, getDownloadURL} from "firebase/storage";
|
||||||
import { Stat } from "@/interfaces/user";
|
import {Stat, CorporateUser} from "@/interfaces/user";
|
||||||
import { User } from "@/interfaces/user";
|
import {User, DemographicInformation} from "@/interfaces/user";
|
||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import {ModuleScore, StudentData} from "@/interfaces/module.scores";
|
import {ModuleScore, StudentData} from "@/interfaces/module.scores";
|
||||||
import {SkillExamDetails} from "@/exams/pdf/details/skill.exam";
|
import {SkillExamDetails} from "@/exams/pdf/details/skill.exam";
|
||||||
import {LevelExamDetails} from "@/exams/pdf/details/level.exam";
|
import {LevelExamDetails} from "@/exams/pdf/details/level.exam";
|
||||||
import {calculateBandScore, getLevelScore} from "@/utils/score";
|
import {calculateBandScore, getLevelScore} from "@/utils/score";
|
||||||
import {
|
import {generateQRCode, getRadialProgressPNG, streamToBuffer} from "@/utils/pdf";
|
||||||
generateQRCode,
|
import {Group} from "@/interfaces/user";
|
||||||
getRadialProgressPNG,
|
|
||||||
streamToBuffer,
|
|
||||||
} from "@/utils/pdf";
|
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
interface GroupScoreSummaryHelper {
|
interface GroupScoreSummaryHelper {
|
||||||
@@ -98,7 +85,7 @@ const getScoreAndTotal = (stats: Stat[]) => {
|
|||||||
total: acc.total + score.total,
|
total: acc.total + score.total,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ correct: 0, total: 0 }
|
{correct: 0, total: 0},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,29 +133,19 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const user = docUser.data() as User;
|
const user = docUser.data() as User;
|
||||||
|
|
||||||
// generate the QR code for the report
|
// generate the QR code for the report
|
||||||
const qrcode = await generateQRCode(
|
const qrcode = await generateQRCode((req.headers.origin || "") + req.url);
|
||||||
(req.headers.origin || "") + req.url
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!qrcode) {
|
if (!qrcode) {
|
||||||
res.status(500).json({ok: false});
|
res.status(500).json({ok: false});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const flattenResults = data.results.reduce(
|
const flattenResults = data.results.reduce((accm: Stat[], entry: any) => {
|
||||||
(accm: Stat[], entry: any) => {
|
|
||||||
const stats = entry.stats as Stat[];
|
const stats = entry.stats as Stat[];
|
||||||
return [...accm, ...stats];
|
return [...accm, ...stats];
|
||||||
},
|
}, []) as Stat[];
|
||||||
[]
|
|
||||||
) as Stat[];
|
|
||||||
|
|
||||||
const docsSnap = await getDocs(
|
const docsSnap = await getDocs(query(collection(db, "users"), where(documentId(), "in", data.assignees)));
|
||||||
query(
|
|
||||||
collection(db, "users"),
|
|
||||||
where(documentId(), "in", data.assignees)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const users = docsSnap.docs.map((d) => ({
|
const users = docsSnap.docs.map((d) => ({
|
||||||
...d.data(),
|
...d.data(),
|
||||||
id: d.id,
|
id: d.id,
|
||||||
@@ -176,24 +153,15 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
const flattenResultsWithGrade = flattenResults.map((e) => {
|
const flattenResultsWithGrade = flattenResults.map((e) => {
|
||||||
const focus = users.find((u) => u.id === e.user)?.focus || "academic";
|
const focus = users.find((u) => u.id === e.user)?.focus || "academic";
|
||||||
const bandScore = calculateBandScore(
|
const bandScore = calculateBandScore(e.score.correct, e.score.total, e.module, focus);
|
||||||
e.score.correct,
|
|
||||||
e.score.total,
|
|
||||||
e.module,
|
|
||||||
focus
|
|
||||||
);
|
|
||||||
|
|
||||||
return {...e, bandScore};
|
return {...e, bandScore};
|
||||||
});
|
});
|
||||||
|
|
||||||
const moduleResults = data.exams.map(({module}) => {
|
const moduleResults = data.exams.map(({module}) => {
|
||||||
const moduleResults = flattenResultsWithGrade.filter(
|
const moduleResults = flattenResultsWithGrade.filter((e) => e.module === module);
|
||||||
(e) => e.module === module
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseBandScore =
|
const baseBandScore = moduleResults.reduce((accm, curr) => accm + curr.bandScore, 0) / moduleResults.length;
|
||||||
moduleResults.reduce((accm, curr) => accm + curr.bandScore, 0) /
|
|
||||||
moduleResults.length;
|
|
||||||
const bandScore = isNaN(baseBandScore) ? 0 : baseBandScore;
|
const bandScore = isNaN(baseBandScore) ? 0 : baseBandScore;
|
||||||
const {correct, total} = getScoreAndTotal(moduleResults);
|
const {correct, total} = getScoreAndTotal(moduleResults);
|
||||||
const png = getRadialProgressPNG("azul", correct, total);
|
const png = getRadialProgressPNG("azul", correct, total);
|
||||||
@@ -208,16 +176,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
};
|
};
|
||||||
}) as ModuleScore[];
|
}) as ModuleScore[];
|
||||||
|
|
||||||
const { correct: overallCorrect, total: overallTotal } =
|
const {correct: overallCorrect, total: overallTotal} = getScoreAndTotal(flattenResults);
|
||||||
getScoreAndTotal(flattenResults);
|
|
||||||
const baseOverallResult = overallCorrect / overallTotal;
|
const baseOverallResult = overallCorrect / overallTotal;
|
||||||
const overallResult = isNaN(baseOverallResult) ? 0 : baseOverallResult;
|
const overallResult = isNaN(baseOverallResult) ? 0 : baseOverallResult;
|
||||||
|
|
||||||
const overallPNG = getRadialProgressPNG(
|
const overallPNG = getRadialProgressPNG("laranja", overallCorrect, overallTotal);
|
||||||
"laranja",
|
|
||||||
overallCorrect,
|
|
||||||
overallTotal
|
|
||||||
);
|
|
||||||
// generate the overall detail report
|
// generate the overall detail report
|
||||||
const overallDetail = {
|
const overallDetail = {
|
||||||
module: "Overall",
|
module: "Overall",
|
||||||
@@ -234,7 +197,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
// or X modules, either way
|
// or X modules, either way
|
||||||
// as long as I verify the first entry I should be fine
|
// as long as I verify the first entry I should be fine
|
||||||
baseStat.module,
|
baseStat.module,
|
||||||
overallResult
|
overallResult,
|
||||||
);
|
);
|
||||||
|
|
||||||
const showLevel = baseStat.module === "level";
|
const showLevel = baseStat.module === "level";
|
||||||
@@ -244,12 +207,7 @@ 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: (
|
details: <LevelExamDetails detail={overallDetail} title="Group Average CEFR" />,
|
||||||
<LevelExamDetails
|
|
||||||
detail={overallDetail}
|
|
||||||
title="Group Average CEFR"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,11 +234,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
});
|
||||||
|
|
||||||
const bandScore =
|
const bandScore = exams.length === 0 ? 0 : exams.reduce((accm, curr) => accm + curr.bandScore, 0) / exams.length;
|
||||||
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}`;
|
||||||
@@ -292,9 +246,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
gender: user?.demographicInformation?.gender || "N/A",
|
gender: user?.demographicInformation?.gender || "N/A",
|
||||||
date,
|
date,
|
||||||
result,
|
result,
|
||||||
level: showLevel
|
level: showLevel ? getLevelScoreForUserExams(bandScore) : undefined,
|
||||||
? getLevelScoreForUserExams(bandScore)
|
|
||||||
: undefined,
|
|
||||||
bandScore,
|
bandScore,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -303,8 +255,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const studentsData = await getStudentsData();
|
const studentsData = await getStudentsData();
|
||||||
|
|
||||||
const getGroupScoreSummary = () => {
|
const getGroupScoreSummary = () => {
|
||||||
const resultHelper = studentsData.reduce(
|
const resultHelper = studentsData.reduce((accm: GroupScoreSummaryHelper[], curr) => {
|
||||||
(accm: GroupScoreSummaryHelper[], curr) => {
|
|
||||||
const {bandScore, id} = curr;
|
const {bandScore, id} = curr;
|
||||||
|
|
||||||
const flooredScore = Math.floor(bandScore);
|
const flooredScore = Math.floor(bandScore);
|
||||||
@@ -331,9 +282,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
sessions: [id],
|
sessions: [id],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
}, []) as GroupScoreSummaryHelper[];
|
||||||
[]
|
|
||||||
) as GroupScoreSummaryHelper[];
|
|
||||||
|
|
||||||
const result = resultHelper.map(({score, label, sessions}) => {
|
const result = resultHelper.map(({score, label, sessions}) => {
|
||||||
const finalLabel = showLevel ? getLevelScore(score[0])[1] : label;
|
const finalLabel = showLevel ? getLevelScore(score[0])[1] : label;
|
||||||
@@ -346,28 +295,83 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupScoreSummary = getGroupScoreSummary();
|
const getInstitution = async () => {
|
||||||
|
try {
|
||||||
|
// due to database inconsistencies, I'll be overprotective here
|
||||||
|
const assignerUserSnap = await getDoc(doc(db, "users", data.assigner));
|
||||||
|
if (assignerUserSnap.exists()) {
|
||||||
|
// we'll need the user in order to get the user data (name, email, focus, etc);
|
||||||
|
const assignerUser = assignerUserSnap.data() as User;
|
||||||
|
|
||||||
|
if (assignerUser.type === "teacher") {
|
||||||
|
// also search for groups where this user belongs
|
||||||
|
const queryGroups = query(collection(db, "groups"), where("participants", "array-contains", assignerUser.id));
|
||||||
|
const groupSnapshot = await getDocs(queryGroups);
|
||||||
|
|
||||||
|
const groups = groupSnapshot.docs.map((doc) => ({
|
||||||
|
id: doc.id,
|
||||||
|
...doc.data(),
|
||||||
|
})) as Group[];
|
||||||
|
|
||||||
|
if (groups.length > 0) {
|
||||||
|
const adminQuery = query(
|
||||||
|
collection(db, "users"),
|
||||||
|
where(
|
||||||
|
documentId(),
|
||||||
|
"in",
|
||||||
|
groups.map((g) => g.admin),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const adminUsersSnap = await getDocs(adminQuery);
|
||||||
|
|
||||||
|
const admins = adminUsersSnap.docs.map((doc) => ({
|
||||||
|
id: doc.id,
|
||||||
|
...doc.data(),
|
||||||
|
})) as CorporateUser[];
|
||||||
|
|
||||||
|
const adminData = admins.find((a) => a.corporateInformation?.companyInformation?.name);
|
||||||
|
if (adminData) {
|
||||||
|
return adminData.corporateInformation.companyInformation.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignerUser.type === "corporate" && assignerUser.corporateInformation?.companyInformation?.name) {
|
||||||
|
return assignerUser.corporateInformation.companyInformation.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const institution = await getInstitution();
|
||||||
|
const groupScoreSummary = getGroupScoreSummary();
|
||||||
|
const demographicInformation = user.demographicInformation as DemographicInformation;
|
||||||
const pdfStream = await ReactPDF.renderToStream(
|
const pdfStream = await ReactPDF.renderToStream(
|
||||||
<GroupTestReport
|
<GroupTestReport
|
||||||
title={title}
|
title={title}
|
||||||
date={moment(data.startDate).tz(user.demographicInformation?.timezone || 'UTC').format('ll HH:mm:ss')}
|
date={moment(data.startDate)
|
||||||
|
.tz(user.demographicInformation?.timezone || "UTC")
|
||||||
|
.format("ll HH:mm:ss")}
|
||||||
name={user.name}
|
name={user.name}
|
||||||
email={user.email}
|
email={user.email}
|
||||||
id={user.id}
|
id={user.id}
|
||||||
gender={user.demographicInformation?.gender}
|
gender={demographicInformation?.gender}
|
||||||
summary={performanceSummary}
|
summary={performanceSummary}
|
||||||
renderDetails={details}
|
renderDetails={details}
|
||||||
logo={"public/logo_title.png"}
|
logo={"public/logo_title.png"}
|
||||||
qrcode={qrcode}
|
qrcode={qrcode}
|
||||||
numberOfStudents={numberOfStudents}
|
numberOfStudents={numberOfStudents}
|
||||||
institution="TODO: PLACEHOLDER"
|
institution={institution}
|
||||||
studentsData={studentsData}
|
studentsData={studentsData}
|
||||||
showLevel={showLevel}
|
showLevel={showLevel}
|
||||||
summaryPNG={overallPNG}
|
summaryPNG={overallPNG}
|
||||||
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
||||||
groupScoreSummary={groupScoreSummary}
|
groupScoreSummary={groupScoreSummary}
|
||||||
/>
|
passportId={demographicInformation?.passport_id || ""}
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate the file ref for storage
|
// generate the file ref for storage
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { sessionOptions } from "@/lib/session";
|
|||||||
import ReactPDF from "@react-pdf/renderer";
|
import ReactPDF from "@react-pdf/renderer";
|
||||||
import TestReport from "@/exams/pdf/test.report";
|
import TestReport from "@/exams/pdf/test.report";
|
||||||
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
|
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
|
||||||
import { User } from "@/interfaces/user";
|
import { DemographicInformation, User } from "@/interfaces/user";
|
||||||
import { Module } from "@/interfaces";
|
import { Module } from "@/interfaces";
|
||||||
import { ModuleScore } from "@/interfaces/module.scores";
|
import { ModuleScore } from "@/interfaces/module.scores";
|
||||||
import { SkillExamDetails } from "@/exams/pdf/details/skill.exam";
|
import { SkillExamDetails } from "@/exams/pdf/details/skill.exam";
|
||||||
@@ -305,6 +305,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
const { title, details } = getCustomData();
|
const { title, details } = getCustomData();
|
||||||
|
|
||||||
|
const demographicInformation = user.demographicInformation as DemographicInformation;
|
||||||
const pdfStream = await ReactPDF.renderToStream(
|
const pdfStream = await ReactPDF.renderToStream(
|
||||||
<TestReport
|
<TestReport
|
||||||
title={title}
|
title={title}
|
||||||
@@ -312,7 +313,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
name={user.name}
|
name={user.name}
|
||||||
email={user.email}
|
email={user.email}
|
||||||
id={user.id}
|
id={user.id}
|
||||||
gender={user.demographicInformation?.gender}
|
gender={demographicInformation?.gender}
|
||||||
summary={performanceSummary}
|
summary={performanceSummary}
|
||||||
testDetails={testDetails}
|
testDetails={testDetails}
|
||||||
renderDetails={details}
|
renderDetails={details}
|
||||||
@@ -320,6 +321,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
qrcode={qrcode}
|
qrcode={qrcode}
|
||||||
summaryPNG={overallPNG}
|
summaryPNG={overallPNG}
|
||||||
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
||||||
|
passportId={demographicInformation?.passport_id || ""}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user