Added pdf download to record page

Reenabled reuse of PDF
This commit is contained in:
Joao Ramos
2024-01-09 22:42:42 +00:00
parent 6c741f944d
commit 418221427a
2 changed files with 91 additions and 27 deletions

View File

@@ -14,12 +14,10 @@ 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 TestReport from "@/exams/pdf/test.report"; import TestReport from "@/exams/pdf/test.report";
import { ref, uploadBytes } from "firebase/storage"; import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
import { Stat } from "@/interfaces/user";
import { User } from "@/interfaces/user"; import { 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 qrcode from "qrcode";
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 } from "@/utils/score"; import { calculateBandScore } from "@/utils/score";
@@ -146,14 +144,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
} }
const stats = docsSnap.docs.map((d) => d.data()); const stats = docsSnap.docs.map((d) => d.data());
// TODO: verify if the stats already have a pdf generated // verify if the stats already have a pdf generated
// const hasPDF = stats.find((s) => s.pdf); const hasPDF = stats.find((s) => s.pdf);
// if (hasPDF) { if (hasPDF) {
// // if it does, return the pdf url // if it does, return the pdf url
// res.status(200).end(hasPDF.pdf); const fileRef = ref(storage, hasPDF.pdf);
// return; const url = await getDownloadURL(fileRef);
// }
res.status(200).end(url);
return;
}
try { try {
// generate the pdf report // generate the pdf report
@@ -318,7 +319,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
// generate the file ref for storage // generate the file ref for storage
const fileName = `${Date.now().toString()}.pdf`; 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 // upload the pdf to storage
const pdfBuffer = await streamToBuffer(pdfStream); 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 // update the stats entries with the pdf url to prevent duplication
docsSnap.docs.forEach(async (doc) => { docsSnap.docs.forEach(async (doc) => {
await updateDoc(doc.ref, { 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; return;
} }
@@ -361,10 +364,12 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const stats = docsSnap.docs.map((d) => d.data()); const stats = docsSnap.docs.map((d) => d.data());
const pdfUrl = stats.find((s) => s.pdf); const hasPDF = stats.find((s) => s.pdf);
if (pdfUrl) { if (hasPDF) {
return res.end(pdfUrl); const fileRef = ref(storage, hasPDF.pdf);
const url = await getDownloadURL(fileRef);
return res.redirect(url);
} }
res.status(500).end(); res.status(500).end();

View File

@@ -24,6 +24,9 @@ import useGroups from "@/hooks/useGroups";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import useAssignments from "@/hooks/useAssignments"; import useAssignments from "@/hooks/useAssignments";
import {uuidv4} from "@firebase/util"; 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}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -55,6 +58,10 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
}, sessionOptions); }, sessionOptions);
type DownloadingPdf = {
[key: string]: boolean;
};
export default function History({user}: {user: User}) { export default function History({user}: {user: User}) {
const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id); const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id);
const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>(); 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 setShowSolutions = useExamStore((state) => state.setShowSolutions);
const setUserSolutions = useExamStore((state) => state.setUserSolutions); const setUserSolutions = useExamStore((state) => state.setUserSolutions);
const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const [downloadingPdf, setDownloadingPdf] = useState<DownloadingPdf>({});
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
@@ -174,7 +181,7 @@ export default function History({user}: {user: User}) {
level: calculateBandScore(x.correct, x.total, x.module, user.focus), level: calculateBandScore(x.correct, x.total, x.module, user.focus),
})); }));
const timeSpent = dateStats[0].timeSpent; const { timeSpent, session } = dateStats[0];
const selectExam = () => { const selectExam = () => {
const examPromises = uniqBy(dateStats, "exam").map((stat) => getExamById(stat.module, stat.exam)); 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 = ( const content = (
<> <>
<div className="w-full flex justify-between -md:items-center 2xl:items-center"> <div className="w-full flex justify-between -md:items-center 2xl:items-center">
@@ -207,15 +242,39 @@ export default function History({user}: {user: User}) {
</> </>
)} )}
</div> </div>
<div className="flex flex-row gap-2">
<span <span
className={clsx( className={textColor}>
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",
)}>
Level{" "} Level{" "}
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)} {(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
</span> </span>
{/*<a
href="https://firebasestorage.googleapis.com/v0/b/mti-ielts.appspot.com/o/exam_report%2F1704838712225.pdf?alt=media&token=0df9a50a-05a9-40a8-ba7c-4ff10ac5f3c8"
//download="report.pdf"
target="_blank"
rel="noreferrer"
>
<BsFilePdf
className={`${textColor} text-2xl cursor-pointer`}
onClick={(e) => {
e.stopPropagation();
triggerDownload();
}}
/>
</a>*/}
{downloadingPdf[session] ?
<span className={`${textColor} loading loading-infinity w-6`} /> :
(
<BsFilePdf
className={`${textColor} text-2xl cursor-pointer`}
onClick={(e) => {
e.stopPropagation();
triggerDownload();
}}
/>
)
}
</div>
</div> </div>
<div className="w-full flex flex-col gap-1"> <div className="w-full flex flex-col gap-1">