diff --git a/src/exams/pdf/details/level.exam.tsx b/src/exams/pdf/details/level.exam.tsx new file mode 100644 index 00000000..57ccac20 --- /dev/null +++ b/src/exams/pdf/details/level.exam.tsx @@ -0,0 +1,165 @@ +import React from "react"; +import { View, Text } from "@react-pdf/renderer"; +import { ModuleScore } from "@/interfaces/module.scores"; +import { styles } from "../styles"; +import { RadialResult } from "./radial.result"; +interface Props { + detail: ModuleScore; +} + +const thresholds = [ + { + level: "Low A1", + label: "Begginner", + minValue: 0, + maxValue: 3, + }, + { + level: "High A1/Low A2", + label: "Elementary", + minValue: 4, + maxValue: 7, + }, + { + level: "High A2/Low B1", + label: "Pre-Intermediate", + minValue: 8, + maxValue: 12, + }, + { + level: "High B2/Low C1", + label: "Upper-Intermediate", + minValue: 16, + maxValue: 21, + }, + { + level: "C1", + label: "Advanced", + minValue: 22, + maxValue: 25, + }, +]; + +export const LevelExamDetails = ({ detail }: Props) => { + const updatedThresholds = thresholds.map((t) => ({ + ...t, + match: detail.score >= t.minValue && detail.score <= t.maxValue, + })); + + const getBackgroundColor = (match: boolean, base: boolean) => { + if (match) return "#c2bfdd"; + return base ? "#553b25" : "#ea7c7b"; + }; + + const getTextColor = (match: boolean, base: boolean) => { + if (match) return "#9e7936"; + return base ? "white" : "#553b25"; + }; + return ( + + + + + + Level as per CEFR Levels + + + + {updatedThresholds.map( + ({ level, label, minValue, maxValue, match }, index, arr) => ( + + + + {level} + + + + + {label} + + + + + {minValue}-{maxValue} + + + + ) + )} + + + + ); +}; diff --git a/src/exams/pdf/details/radial.result.tsx b/src/exams/pdf/details/radial.result.tsx new file mode 100644 index 00000000..df999000 --- /dev/null +++ b/src/exams/pdf/details/radial.result.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { + Document, + Page, + View, + Text, + StyleSheet, + Image, +} from "@react-pdf/renderer"; +import { styles } from "../styles"; +import { ModuleScore } from "@/interfaces/module.scores"; + +export const RadialResult = ({ module, score, total }: ModuleScore) => ( + + + {module} + + {score} + Out of {total} + +); diff --git a/src/exams/pdf/details/skill.exam.tsx b/src/exams/pdf/details/skill.exam.tsx new file mode 100644 index 00000000..a23e7c69 --- /dev/null +++ b/src/exams/pdf/details/skill.exam.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { + Document, + Page, + View, + Text, + StyleSheet, + Image, +} from "@react-pdf/renderer"; +import { ModuleScore } from "@/interfaces/module.scores"; +import { styles } from "../styles"; +import { RadialResult } from "./radial.result"; +interface Props { + testDetails: ModuleScore[]; +} + +export const SkillExamDetails = ({ testDetails }: Props) => ( + + {testDetails.map((detail) => { + const { module } = detail; + + return ; + })} + +); diff --git a/src/exams/pdf/index.tsx b/src/exams/pdf/index.tsx index c0ca27b4..dd9d5b98 100644 --- a/src/exams/pdf/index.tsx +++ b/src/exams/pdf/index.tsx @@ -1,64 +1,13 @@ /* eslint-disable jsx-a11y/alt-text */ import React from "react"; -import { - Document, - Page, - View, - Text, - StyleSheet, - Image, -} from "@react-pdf/renderer"; +import { Document, Page, View, Text, Image } from "@react-pdf/renderer"; import ProgressBar from "./progress.bar"; // import RadialProgress from "./radial.progress"; // import RadialProgressSvg from "./radial.progress.svg"; import { Module } from "@/interfaces"; import { ModuleScore } from "@/interfaces/module.scores"; // import logo from './logo_title.png'; - -const styles = StyleSheet.create({ - body: { - paddingTop: 10, - paddingBottom: 20, - paddingHorizontal: 35, - }, - titleView: { - display: "flex", - // flex: 1, - alignItems: "center", - }, - title: { - textTransform: "uppercase", - }, - textPadding: { - margin: "8px", - }, - separator: { - width: "100%", - borderBottom: "1px solid #89b0c2", - }, - textFont: { - fontFamily: "Helvetica", - }, - textBold: { - fontFamily: "Helvetica-Bold", - fontWeight: "bold", - }, - textColor: { - color: "#4e4969", - }, - textUnderline: { - textDecoration: "underline", - }, - spacedRow: { - display: "flex", - flexDirection: "row", - justifyContent: "space-between", - }, - alignRightRow: { - display: "flex", - flexDirection: "row-reverse", - } -}); +import { styles } from "./styles"; interface Props { date: string; @@ -70,6 +19,7 @@ interface Props { summary: string; logo: string; qrcode: string; + renderDetails: React.ReactNode; } const PDFReport = ({ @@ -82,6 +32,7 @@ const PDFReport = ({ summary, logo, qrcode, + renderDetails, }: Props) => { const defaultTextStyle = [styles.textFont, { fontSize: 8 }]; const defaultSkillsTextStyle = [styles.textFont, { fontSize: 8 }]; @@ -94,14 +45,8 @@ const PDFReport = ({ return ( - - + + Email: {email} Gender: {gender} - + Test Details: - - {testDetails.map(({ module, score, total }) => ( - - - {module} - - {score} - Out of {total} - - ))} - + {renderDetails} - + diff --git a/src/exams/pdf/styles.ts b/src/exams/pdf/styles.ts new file mode 100644 index 00000000..1e31de4e --- /dev/null +++ b/src/exams/pdf/styles.ts @@ -0,0 +1,46 @@ +import { StyleSheet } from "@react-pdf/renderer"; + +export const styles = StyleSheet.create({ + body: { + paddingTop: 10, + paddingBottom: 20, + paddingHorizontal: 35, + }, + titleView: { + display: "flex", + // flex: 1, + alignItems: "center", + }, + title: { + textTransform: "uppercase", + }, + textPadding: { + margin: "8px", + }, + separator: { + width: "100%", + borderBottom: "1px solid #89b0c2", + }, + textFont: { + fontFamily: "Helvetica", + }, + textBold: { + fontFamily: "Helvetica-Bold", + fontWeight: "bold", + }, + textColor: { + color: "#4e4969", + }, + textUnderline: { + textDecoration: "underline", + }, + spacedRow: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + }, + alignRightRow: { + display: "flex", + flexDirection: "row-reverse", + }, +}); diff --git a/src/pages/api/stats/[id]/export.tsx b/src/pages/api/stats/[id]/export.tsx index 91f3ab69..4a8829b9 100644 --- a/src/pages/api/stats/[id]/export.tsx +++ b/src/pages/api/stats/[id]/export.tsx @@ -14,16 +14,14 @@ import { withIronSessionApiRoute } from "iron-session/next"; import { sessionOptions } from "@/lib/session"; import ReactPDF from "@react-pdf/renderer"; import PDFReport from "@/exams/pdf"; -import { - ref, - uploadBytes, -} from "firebase/storage"; +import { ref, uploadBytes } from "firebase/storage"; import { Stat } from "@/interfaces/user"; import { User } from "@/interfaces/user"; import { Module } from "@/interfaces"; import { ModuleScore } from "@/interfaces/module.scores"; -import qrcode from 'qrcode'; - +import qrcode from "qrcode"; +import { SkillExamDetails } from "@/exams/pdf/details/skill.exam"; +import { LevelExamDetails } from "@/exams/pdf/details/level.exam"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); @@ -122,7 +120,7 @@ const generateQRCode = async (link: string) => { const qrCodeDataURL = await qrcode.toDataURL(link); return qrCodeDataURL; } catch (error) { - console.error('Error generating QR code:', error); + console.error("Error generating QR code:", error); return null; } }; @@ -157,7 +155,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const stats = docsSnap.docs.map((d) => d.data()); const results = stats.reduce((accm: ModuleScore[], { module, score }) => { - const fixedModuleStr = module[0].toUpperCase() + module.substring(1) + const fixedModuleStr = module[0].toUpperCase() + module.substring(1); if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) { return accm.map((e: ModuleScore) => { if (e.module === fixedModuleStr) { @@ -192,9 +190,22 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const overallResult = overallScore / overallTotal; const performanceSummary = getPerformanceSummary("level", overallResult); - const qrcode = await generateQRCode((req.headers.origin || '') + req.url); + const qrcode = await generateQRCode((req.headers.origin || "") + req.url); - if(qrcode) { + const overallDetail = { + module: "Overall", + score: overallScore, + total: overallTotal, + } as ModuleScore; + const testDetails = [overallDetail, ...results]; + const renderDetails = () => { + if (stats[0].module === "level") { + return ; + } + + return ; + }; + if (qrcode) { const pdfStream = await ReactPDF.renderToStream(