From 418221427a20c45e541e92842e4a79e8478f7b7c Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 9 Jan 2024 22:42:42 +0000 Subject: [PATCH] Added pdf download to record page Reenabled reuse of PDF --- src/pages/api/stats/[id]/export.tsx | 37 +++++++------ src/pages/record.tsx | 81 +++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 27 deletions(-) diff --git a/src/pages/api/stats/[id]/export.tsx b/src/pages/api/stats/[id]/export.tsx index 23f89bc7..a1c3bab5 100644 --- a/src/pages/api/stats/[id]/export.tsx +++ b/src/pages/api/stats/[id]/export.tsx @@ -14,12 +14,10 @@ import { withIronSessionApiRoute } from "iron-session/next"; import { sessionOptions } from "@/lib/session"; import ReactPDF from "@react-pdf/renderer"; import TestReport from "@/exams/pdf/test.report"; -import { ref, uploadBytes } from "firebase/storage"; -import { Stat } from "@/interfaces/user"; +import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; import { User } from "@/interfaces/user"; import { Module } from "@/interfaces"; import { ModuleScore } from "@/interfaces/module.scores"; -import qrcode from "qrcode"; import { SkillExamDetails } from "@/exams/pdf/details/skill.exam"; import { LevelExamDetails } from "@/exams/pdf/details/level.exam"; import { calculateBandScore } from "@/utils/score"; @@ -146,14 +144,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) { } const stats = docsSnap.docs.map((d) => d.data()); - // TODO: verify if the stats already have a pdf generated - // const hasPDF = stats.find((s) => s.pdf); + // verify if the stats already have a pdf generated + const hasPDF = stats.find((s) => s.pdf); - // if (hasPDF) { - // // if it does, return the pdf url - // res.status(200).end(hasPDF.pdf); - // return; - // } + if (hasPDF) { + // if it does, return the pdf url + const fileRef = ref(storage, hasPDF.pdf); + const url = await getDownloadURL(fileRef); + + res.status(200).end(url); + return; + } try { // generate the pdf report @@ -318,7 +319,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) { // generate the file ref for storage const fileName = `${Date.now().toString()}.pdf`; - const fileRef = ref(storage, `exam_report/${fileName}`); + const refName = `exam_report/${fileName}`; + const fileRef = ref(storage, refName); // upload the pdf to storage const pdfBuffer = await streamToBuffer(pdfStream); @@ -329,10 +331,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) { // update the stats entries with the pdf url to prevent duplication docsSnap.docs.forEach(async (doc) => { await updateDoc(doc.ref, { - pdf: snapshot.ref.fullPath, + pdf: refName, }); }); - res.status(200).end(snapshot.ref.fullPath); + const url = await getDownloadURL(fileRef); + res.status(200).end(url); return; } @@ -361,10 +364,12 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const stats = docsSnap.docs.map((d) => d.data()); - const pdfUrl = stats.find((s) => s.pdf); + const hasPDF = stats.find((s) => s.pdf); - if (pdfUrl) { - return res.end(pdfUrl); + if (hasPDF) { + const fileRef = ref(storage, hasPDF.pdf); + const url = await getDownloadURL(fileRef); + return res.redirect(url); } res.status(500).end(); diff --git a/src/pages/record.tsx b/src/pages/record.tsx index 9a934180..80323849 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -24,6 +24,9 @@ import useGroups from "@/hooks/useGroups"; import {shouldRedirectHome} from "@/utils/navigation.disabled"; import useAssignments from "@/hooks/useAssignments"; import {uuidv4} from "@firebase/util"; +import { BsFilePdf } from "react-icons/bs"; +import axios from "axios"; +import {toast} from "react-toastify"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -55,6 +58,10 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { }; }, sessionOptions); +type DownloadingPdf = { + [key: string]: boolean; +}; + export default function History({user}: {user: User}) { const [statsUserId, setStatsUserId] = useState(user.id); const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>(); @@ -69,7 +76,7 @@ export default function History({user}: {user: User}) { const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setUserSolutions = useExamStore((state) => state.setUserSolutions); const setSelectedModules = useExamStore((state) => state.setSelectedModules); - + const [downloadingPdf, setDownloadingPdf] = useState({}); const router = useRouter(); useEffect(() => { @@ -174,7 +181,7 @@ export default function History({user}: {user: User}) { level: calculateBandScore(x.correct, x.total, x.module, user.focus), })); - const timeSpent = dateStats[0].timeSpent; + const { timeSpent, session } = dateStats[0]; const selectExam = () => { const examPromises = uniqBy(dateStats, "exam").map((stat) => getExamById(stat.module, stat.exam)); @@ -195,6 +202,34 @@ export default function History({user}: {user: User}) { }); }; + const textColor = clsx( + correct / total >= 0.7 && "text-mti-purple", + correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red", + correct / total < 0.3 && "text-mti-rose", + ); + + const triggerDownload = async () => { + try { + setDownloadingPdf((prev) => ({...prev, [session]: true})); + const res = await axios.post(`/api/stats/${session}/export`); + toast.success("Report ready!"); + const link = document.createElement("a"); + link.href = res.data; + // download should have worked but there are some CORS issues + // https://firebase.google.com/docs/storage/web/download-files#cors_configuration + // link.download="report.pdf"; + link.target = '_blank'; + link.rel="noreferrer" + link.click(); + setDownloadingPdf((prev) => ({...prev, [session]: false})); + } catch(err) { + toast.error("Failed to display the report!"); + console.error(err); + setDownloadingPdf((prev) => ({...prev, [session]: false})); + + } + + } const content = ( <>
@@ -207,15 +242,39 @@ export default function History({user}: {user: User}) { )}
- = 0.7 && "text-mti-purple", - correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red", - correct / total < 0.3 && "text-mti-rose", - )}> - Level{" "} - {(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)} - +
+ + Level{" "} + {(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)} + + {/* + { + e.stopPropagation(); + triggerDownload(); + }} + /> + */} + {downloadingPdf[session] ? + : + ( + { + e.stopPropagation(); + triggerDownload(); + }} + /> + ) + } +