From ca7e83749d50babd57c9a4589a7fd083021548b9 Mon Sep 17 00:00:00 2001 From: Vikrant Kumar Date: Mon, 5 Oct 2026 17:24:57 +0530 Subject: [PATCH] Add an academic-year grade sheet generator for acad admin Lets acad admin generate the official multi-semester grade sheet PDF for a batch of students, matching the layout of the existing semester-wise Grade Sheet Software but grouped by academic year. Pages pack semesters continuously (not fixed to one academic year per page) and the shared grading legend only prints once, on the final page. --- .../Examination/components/gradeSheetList.jsx | 134 ++++++++ .../Examination/components/gradeSheetPdf.js | 238 ++++++++++++++ .../components/gradeSheetTemplate.js | 297 ++++++++++++++++++ src/Modules/Examination/examination.jsx | 1 + src/Modules/Examination/gradeSheet.jsx | 168 ++++++++++ src/Modules/Examination/pages.js | 8 + .../Examination/routes/examinationRoutes.jsx | 2 + vite.config.js | 2 +- 8 files changed, 849 insertions(+), 1 deletion(-) create mode 100644 src/Modules/Examination/components/gradeSheetList.jsx create mode 100644 src/Modules/Examination/components/gradeSheetPdf.js create mode 100644 src/Modules/Examination/components/gradeSheetTemplate.js create mode 100644 src/Modules/Examination/gradeSheet.jsx diff --git a/src/Modules/Examination/components/gradeSheetList.jsx b/src/Modules/Examination/components/gradeSheetList.jsx new file mode 100644 index 000000000..b3acdb901 --- /dev/null +++ b/src/Modules/Examination/components/gradeSheetList.jsx @@ -0,0 +1,134 @@ +import { useState } from "react"; +import { Table, Button, Group, Modal, Alert } from "@mantine/core"; +import { IconEye, IconDownload, IconAlertTriangle } from "@tabler/icons-react"; +import axios from "axios"; +import { generate_full_gradesheet_data } from "../routes/examinationRoutes"; +import { buildFullGradeSheetHTML } from "./gradeSheetTemplate"; +import { saveGradeSheetPDF } from "./gradeSheetPdf"; +import styles from "../styles/transcript.module.css"; + +export default function GradeSheetList({ students }) { + const [busy, setBusy] = useState({}); + const [error, setError] = useState(null); + const [preview, setPreview] = useState({ open: false, html: "", title: "" }); + + const setRowBusy = (roll, v) => setBusy((p) => ({ ...p, [roll]: v })); + + const fetchFullGradeSheetHtml = async (rollNo) => { + const token = localStorage.getItem("authToken"); + const { data } = await axios.post( + generate_full_gradesheet_data, + { student: rollNo }, + { headers: { Authorization: `Token ${token}` } }, + ); + if (!data.semesters || data.semesters.length === 0) { + throw new Error("No graded semesters found for this student."); + } + return buildFullGradeSheetHTML(data.student_info, data.semesters); + }; + + const handlePreview = async (student) => { + setRowBusy(student.id_id, "preview"); + setError(null); + try { + const html = await fetchFullGradeSheetHtml(student.id_id); + setPreview({ open: true, html, title: `Grade Sheet — ${student.id_id}` }); + } catch (err) { + setError(err.message || "Failed to load grade sheet."); + } finally { + setRowBusy(student.id_id, null); + } + }; + + const handleDownload = async (student) => { + setRowBusy(student.id_id, "download"); + setError(null); + try { + const html = await fetchFullGradeSheetHtml(student.id_id); + await saveGradeSheetPDF(html, `GradeSheet_${student.id_id}.pdf`); + } catch (err) { + setError(err.message || "Failed to download grade sheet."); + } finally { + setRowBusy(student.id_id, null); + } + }; + + return ( +
+ {error && ( + } + mb="md" + withCloseButton + onClose={() => setError(null)} + > + {error} + + )} + {students.length > 0 ? ( + + + + + + + + + + {students.map((student) => ( + + + + + + ))} + +
Roll NumberProgrammeActions
{student.id_id}{student.programme} + + + + +
+ ) : ( +
No students found for this batch.
+ )} + + setPreview((p) => ({ ...p, open: false }))} + title={preview.title} + size="90%" + > +