Added initial Excel changes
This commit is contained in:
@@ -18,6 +18,7 @@ interface Props {
|
||||
reload?: Function;
|
||||
allowArchive?: boolean;
|
||||
allowUnarchive?: boolean;
|
||||
allowExcelDownload?: boolean;
|
||||
}
|
||||
|
||||
export default function AssignmentCard({
|
||||
@@ -35,10 +36,12 @@ export default function AssignmentCard({
|
||||
reload,
|
||||
allowArchive,
|
||||
allowUnarchive,
|
||||
allowExcelDownload,
|
||||
}: Assignment & Props) {
|
||||
const {users} = useUsers();
|
||||
|
||||
const renderPdfIcon = usePDFDownload("assignments");
|
||||
const renderExcelIcon = usePDFDownload("assignments", "excel");
|
||||
const renderArchiveIcon = useAssignmentArchive(id, reload);
|
||||
const renderUnarchiveIcon = useAssignmentUnarchive(id, reload);
|
||||
|
||||
@@ -63,6 +66,7 @@ export default function AssignmentCard({
|
||||
<h3 className="text-xl font-semibold">{name}</h3>
|
||||
<div className="flex gap-2">
|
||||
{allowDownload && renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
|
||||
{allowExcelDownload && renderExcelIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
|
||||
{allowArchive && !archived && renderArchiveIcon("text-mti-gray-dim", "text-mti-gray-dim")}
|
||||
{allowUnarchive && archived && renderUnarchiveIcon("text-mti-gray-dim", "text-mti-gray-dim")}
|
||||
</div>
|
||||
|
||||
@@ -264,6 +264,7 @@ export default function CorporateDashboard({user}: Props) {
|
||||
allowDownload
|
||||
reload={reloadAssignments}
|
||||
allowArchive
|
||||
allowExcelDownload
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -279,6 +280,7 @@ export default function CorporateDashboard({user}: Props) {
|
||||
allowDownload
|
||||
reload={reloadAssignments}
|
||||
allowUnarchive
|
||||
allowExcelDownload
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import { toast } from "react-toastify";
|
||||
import { BsFilePdf } from "react-icons/bs";
|
||||
import { BsFilePdf, BsFileExcel} from "react-icons/bs";
|
||||
|
||||
type DownloadingPdf = {
|
||||
[key: string]: boolean;
|
||||
};
|
||||
|
||||
type PdfEndpoint = "stats" | "assignments";
|
||||
type FileType = "pdf" | "excel";
|
||||
|
||||
export const usePDFDownload = (endpoint: PdfEndpoint) => {
|
||||
export const usePDFDownload = (endpoint: PdfEndpoint, file: FileType = 'pdf') => {
|
||||
const [downloadingPdf, setDownloadingPdf] = React.useState<DownloadingPdf>(
|
||||
{}
|
||||
);
|
||||
@@ -17,7 +18,7 @@ export const usePDFDownload = (endpoint: PdfEndpoint) => {
|
||||
const triggerDownload = async (id: string) => {
|
||||
try {
|
||||
setDownloadingPdf((prev) => ({ ...prev, [id]: true }));
|
||||
const res = await axios.post(`/api/${endpoint}/${id}/export`);
|
||||
const res = await axios.post(`/api/${endpoint}/${id}/export/${file}`);
|
||||
toast.success("Report ready!");
|
||||
const link = document.createElement("a");
|
||||
link.href = res.data;
|
||||
@@ -45,8 +46,11 @@ export const usePDFDownload = (endpoint: PdfEndpoint) => {
|
||||
<span className={`${loadingClasses} loading loading-infinity w-6`} />
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = file === "excel" ? BsFileExcel : BsFilePdf;
|
||||
|
||||
return (
|
||||
<BsFilePdf
|
||||
<Icon
|
||||
className={`${downloadClasses} text-2xl cursor-pointer`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
154
src/pages/api/assignments/[id]/[export]/excel.ts
Normal file
154
src/pages/api/assignments/[id]/[export]/excel.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { app, storage } from "@/firebase";
|
||||
import {
|
||||
getFirestore,
|
||||
doc,
|
||||
getDoc,
|
||||
updateDoc,
|
||||
getDocs,
|
||||
query,
|
||||
collection,
|
||||
where,
|
||||
documentId,
|
||||
} from "firebase/firestore";
|
||||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import ReactPDF from "@react-pdf/renderer";
|
||||
import GroupTestReport from "@/exams/pdf/group.test.report";
|
||||
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
|
||||
import { Stat, CorporateUser } from "@/interfaces/user";
|
||||
import { User, DemographicInformation } from "@/interfaces/user";
|
||||
import { Module } from "@/interfaces";
|
||||
import { ModuleScore, StudentData } from "@/interfaces/module.scores";
|
||||
import { SkillExamDetails } from "@/exams/pdf/details/skill.exam";
|
||||
import { LevelExamDetails } from "@/exams/pdf/details/level.exam";
|
||||
import { calculateBandScore, getLevelScore } from "@/utils/score";
|
||||
import {
|
||||
generateQRCode,
|
||||
getRadialProgressPNG,
|
||||
streamToBuffer,
|
||||
} from "@/utils/pdf";
|
||||
import { Group } from "@/interfaces/user";
|
||||
import moment from "moment-timezone";
|
||||
import ExcelJS from "exceljs";
|
||||
|
||||
interface GroupScoreSummaryHelper {
|
||||
score: [number, number];
|
||||
label: string;
|
||||
sessions: string[];
|
||||
}
|
||||
const db = getFirestore(app);
|
||||
|
||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// if (req.method === "GET") return get(req, res);
|
||||
if (req.method === "POST") return await post(req, res);
|
||||
}
|
||||
|
||||
async function post(req: NextApiRequest, res: NextApiResponse) {
|
||||
// verify if it's a logged user that is trying to export
|
||||
if (req.session.user) {
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
const docSnap = await getDoc(doc(db, "assignments", id));
|
||||
const data = docSnap.data() as {
|
||||
assigner: string;
|
||||
assignees: string[];
|
||||
results: any;
|
||||
exams: { module: Module }[];
|
||||
startDate: string;
|
||||
excel: {
|
||||
path: string;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
if (!data) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.excel &&
|
||||
data.excel.path &&
|
||||
data.excel.version === process.env.EXCEL_VERSION
|
||||
) {
|
||||
// if it does, return the excel url
|
||||
const fileRef = ref(storage, data.excel.path);
|
||||
const url = await getDownloadURL(fileRef);
|
||||
res.status(200).end(url);
|
||||
return;
|
||||
}
|
||||
|
||||
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 docUser = await getDoc(doc(db, "users", req.session.user.id));
|
||||
if (docUser.exists()) {
|
||||
// we'll need the user in order to get the user data (name, email, focus, etc);
|
||||
const user = docUser.data() as CorporateUser;
|
||||
|
||||
const results = data.results.map((r: any) => r.score.correct);
|
||||
const highestScore = Math.max(...results);
|
||||
const lowestScore = Math.min(...results);
|
||||
|
||||
const dates = data.results
|
||||
.map((r: any) => r.date)
|
||||
.map((d: number) => moment(d));
|
||||
const firstDate = moment.min(dates);
|
||||
const lastDate = moment.max(dates);
|
||||
|
||||
const firstSectionData = [
|
||||
{
|
||||
label: "Corporate Name :",
|
||||
value: user.corporateInformation.companyInformation.name,
|
||||
},
|
||||
{
|
||||
label: "Report Download date :",
|
||||
value: moment().format("DD/MM/YYYY"),
|
||||
},
|
||||
{ label: "Test Information :", value: "TODO" },
|
||||
{ label: "Date of Test :", value: moment(data.startDate).format("DD/MM/YYYY") },
|
||||
{ label: "Number of Candidates :", value: data.assignees.length },
|
||||
{ label: "Highest score :", value: highestScore },
|
||||
{ label: "Lowest score :", value: lowestScore },
|
||||
{ label: "", value: "" },
|
||||
{ label: "Date and time of First submission :", value: firstDate.format("DD/MM/YYYY") },
|
||||
{ label: "Date and time of Last submission :", value: lastDate.format("DD/MM/YYYY") },
|
||||
];
|
||||
|
||||
// Create a new workbook and add a worksheet
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet("Report Data");
|
||||
|
||||
// Populate the worksheet with the data
|
||||
firstSectionData.forEach(({ label, value }, index) => {
|
||||
worksheet.getCell(`A${index + 1}`).value = label; // First column (labels)
|
||||
worksheet.getCell(`B${index + 1}`).value = value; // Second column (values)
|
||||
});
|
||||
|
||||
// Set the response headers for downloading an Excel file
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
);
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=ReportData.xlsx"
|
||||
);
|
||||
|
||||
// Write the workbook to the response
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
Reference in New Issue
Block a user