From 0499621796aa6e61f7ce78d04cbeed21b9916508 Mon Sep 17 00:00:00 2001 From: GAURAV KOTANGALE Date: Mon, 14 Sep 2026 17:01:54 +0530 Subject: [PATCH] Add thesis change request frontend support Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Academic/courseRegistrationReceipt.js | 5 +- .../StudentProfile/bankDetailsComponent.jsx | 273 ++++++++---------- src/Modules/Examination/submitGradesProf.jsx | 46 +-- .../DeanCommitteeChangeModal.jsx | 155 ++++++++++ src/Modules/ThesisResearch/DeanDashboard.jsx | 76 ++++- .../DeanThesisChangeRequestModal.jsx | 153 ++++++++++ .../HODCommitteeChangeModal.jsx | 156 ++++++++++ src/Modules/ThesisResearch/HODDashboard.jsx | 76 ++++- .../HODThesisChangeRequestModal.jsx | 156 ++++++++++ .../ProposeCommitteeChangeModal.jsx | 226 +++++++++++++++ .../RequestThesisChangeModal.jsx | 160 ++++++++++ .../ThesisResearch/StudentThesisPage.jsx | 174 ++++++++--- .../ThesisResearch/SupervisorDashboard.jsx | 3 + .../ThesisResearch/SupervisorReviewModal.jsx | 23 ++ .../ThesisChangeConsentModal.jsx | 178 ++++++++++++ .../ThesisChangeRequestsConsentSection.jsx | 105 +++++++ src/routes/academicRoutes/index.jsx | 26 ++ src/routes/dashboardRoutes/index.jsx | 1 + src/test/setup.js | 6 +- 19 files changed, 1777 insertions(+), 221 deletions(-) create mode 100644 src/Modules/ThesisResearch/DeanCommitteeChangeModal.jsx create mode 100644 src/Modules/ThesisResearch/DeanThesisChangeRequestModal.jsx create mode 100644 src/Modules/ThesisResearch/HODCommitteeChangeModal.jsx create mode 100644 src/Modules/ThesisResearch/HODThesisChangeRequestModal.jsx create mode 100644 src/Modules/ThesisResearch/ProposeCommitteeChangeModal.jsx create mode 100644 src/Modules/ThesisResearch/RequestThesisChangeModal.jsx create mode 100644 src/Modules/ThesisResearch/ThesisChangeConsentModal.jsx create mode 100644 src/Modules/ThesisResearch/ThesisChangeRequestsConsentSection.jsx diff --git a/src/Modules/Academic/courseRegistrationReceipt.js b/src/Modules/Academic/courseRegistrationReceipt.js index 324e5fc39..f1d36b8a2 100644 --- a/src/Modules/Academic/courseRegistrationReceipt.js +++ b/src/Modules/Academic/courseRegistrationReceipt.js @@ -24,7 +24,8 @@ export default function downloadCourseRegistrationReceipt({ : "Prev. Sem. CPI: -"; try { - const doc = new jsPDF({ + const JsPDF = jsPDF; + const doc = new JsPDF({ orientation: "portrait", unit: "mm", format: "legal", @@ -60,7 +61,6 @@ export default function downloadCourseRegistrationReceipt({ `Semester: ${studentInfo.semester || ""}`, ]; - const tableWidth = pageWidth - 40; const tableStartX = 20; const tableEndX = pageWidth - 20; const availableWidth = tableEndX - tableStartX; @@ -215,7 +215,6 @@ export default function downloadCourseRegistrationReceipt({ `Semester: ${studentInfo.semester || ""}`, ]; - const academicTableWidth = pageWidth - 40; const academicTableStartX = 20; const academicTableEndX = pageWidth - 20; const academicAvailableWidth = academicTableEndX - academicTableStartX; diff --git a/src/Modules/Dashboard/StudentProfile/bankDetailsComponent.jsx b/src/Modules/Dashboard/StudentProfile/bankDetailsComponent.jsx index 848060b47..37237c165 100644 --- a/src/Modules/Dashboard/StudentProfile/bankDetailsComponent.jsx +++ b/src/Modules/Dashboard/StudentProfile/bankDetailsComponent.jsx @@ -1,178 +1,159 @@ -import { useState } from "react"; -import PropTypes from "prop-types"; +import { useState, useEffect } from "react"; import { - Alert, - Button, - Grid, - Group, - Stack, + Table, Text, + Button, + Flex, + Divider, TextInput, + Center, + Loader, } from "@mantine/core"; import { notifications } from "@mantine/notifications"; -import { Bank, Check, Info, PencilSimple, X } from "@phosphor-icons/react"; import axios from "axios"; -import { studentProfileUpdateRoute } from "../../../routes/globalRoutes"; -import { DASH, Field, HALF, ReadOnlyValue, SectionCard } from "./profileUi"; +import { studentBankDetailsRoute } from "../../../routes/dashboardRoutes"; const FIELDS = [ - { - key: "bank_name", - label: "Bank Name", - placeholder: "e.g. State Bank of India", - }, - { - key: "bank_account_no", - label: "Bank Account No.", - placeholder: "9 to 18 digits", - clean: (value) => String(value || "").replace(/\D/g, ""), - }, - { - key: "ifsc_code", - label: "IFSC Code", - placeholder: "e.g. SBIN0001234", - clean: (value) => - String(value || "") - .toUpperCase() - .replace(/[^A-Z0-9]/g, "") - .slice(0, 11), - }, + { key: "account_holder_name", label: "Account Holder Name" }, + { key: "bank_name", label: "Bank Name" }, + { key: "branch_name", label: "Branch Name" }, + { key: "account_number", label: "Account Number" }, + { key: "ifsc_code", label: "IFSC Code" }, ]; -export default function BankDetailsComponent({ record, onSaved }) { - const [editing, setEditing] = useState(false); - const [form, setForm] = useState({}); - const [errors, setErrors] = useState({}); - const [saving, setSaving] = useState(false); +function BankDetailsComponent() { + const [loading, setLoading] = useState(true); + const [isEditing, setIsEditing] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [bankDetails, setBankDetails] = useState({ + account_holder_name: "", + bank_name: "", + branch_name: "", + account_number: "", + ifsc_code: "", + }); - const value = (key) => (key in form ? form[key] : (record?.[key] ?? "")); + useEffect(() => { + const token = localStorage.getItem("authToken"); + if (!token) { + setLoading(false); + return; + } + axios + .get(studentBankDetailsRoute, { + headers: { Authorization: `Token ${token}` }, + }) + .then((res) => { + if (res.data && Object.keys(res.data).length > 0) { + setBankDetails((prev) => ({ ...prev, ...res.data })); + } + }) + .catch(() => { + notifications.show({ + message: "Error fetching bank details.", + color: "red", + }); + }) + .finally(() => setLoading(false)); + }, []); - const cancel = () => { - setForm({}); - setErrors({}); - setEditing(false); + const handleChange = (field, value) => { + setBankDetails((prev) => ({ ...prev, [field]: value })); }; - const save = async () => { - const changed = Object.fromEntries( - Object.entries(form).filter( - ([key]) => form[key] !== (record?.[key] ?? ""), - ), - ); - if (!Object.keys(changed).length) { - cancel(); + const handleEditClick = async () => { + const token = localStorage.getItem("authToken"); + if (!token) { + notifications.show({ + message: "Authentication required. Please log in again.", + color: "red", + }); + return; + } + if (!isEditing) { + setIsEditing(true); return; } - setSaving(true); + if (submitting) return; + setSubmitting(true); try { - const token = localStorage.getItem("authToken"); - const { data } = await axios.post(studentProfileUpdateRoute, changed, { - headers: token ? { Authorization: `Token ${token}` } : {}, + const res = await axios.post(studentBankDetailsRoute, bankDetails, { + headers: { Authorization: `Token ${token}` }, }); - onSaved(data.data); - notifications.show({ message: "Bank details saved.", color: "green" }); - cancel(); - } catch (err) { - const body = err.response?.data; - setErrors(body?.errors || {}); + setBankDetails((prev) => ({ ...prev, ...res.data })); notifications.show({ - title: "Could not save", - message: body?.message || "Please try again.", + message: "Bank details updated successfully!", + color: "green", + }); + setIsEditing(false); + } catch { + notifications.show({ + message: "Error updating bank details. Please try again.", color: "red", }); } finally { - setSaving(false); + setSubmitting(false); } }; - const actions = editing ? ( - - - - - ) : ( - - ); + if (loading) { + return ( +
+ +
+ ); + } return ( - - } - title="Bank Details" - description="Used for stipend and reimbursement payments" - action={actions} + + - - {FIELDS.map((field) => ( - - {editing ? ( - { - const next = field.clean - ? field.clean(event.currentTarget.value) - : event.currentTarget.value; - setForm((prev) => ({ ...prev, [field.key]: next })); - setErrors((prev) => ({ ...prev, [field.key]: undefined })); - }} - /> - ) : ( - - {String(record?.[field.key] || "").trim() || DASH} - - )} - - ))} - - - } - mt="md" - > - - Keep this the account in your own name. Payments fail if the name on - the account does not match your records. + + + Bank Account Details - - - + + + + + + {FIELDS.map(({ key, label }) => ( + + {label} + + {isEditing ? ( + handleChange(key, e.target.value)} + /> + ) : ( + bankDetails[key] || "—" + )} + + + ))} + +
+ + ); } -BankDetailsComponent.propTypes = { - record: PropTypes.shape({ - bank_name: PropTypes.string, - bank_account_no: PropTypes.string, - ifsc_code: PropTypes.string, - }), - onSaved: PropTypes.func.isRequired, -}; - -BankDetailsComponent.defaultProps = { record: null }; +export default BankDetailsComponent; diff --git a/src/Modules/Examination/submitGradesProf.jsx b/src/Modules/Examination/submitGradesProf.jsx index ce66a6d08..5d5a6e84d 100644 --- a/src/Modules/Examination/submitGradesProf.jsx +++ b/src/Modules/Examination/submitGradesProf.jsx @@ -638,25 +638,33 @@ function SubmitCourseGradesTab() { - {previewData.map((r, i) => ( - - {i + 1} - {r.roll_no} - {r.name} - {r.branch || "-"} - {r.grades} - {r.remarks} - {r.semester} - - {r.is_registered ? "Registered" : "Missing Registration"} - - - ))} + {previewData.map((r, i) => { + const hasIssue = !r.is_registered || r.grade_valid === false; + const statusText = !r.is_registered + ? "Missing Registration" + : r.grade_valid === false + ? r.grade_error + : "Registered"; + return ( + + {i + 1} + {r.roll_no} + {r.name} + {r.branch || "-"} + {r.grades} + {r.remarks} + {r.semester} + + {statusText} + + + ); + })} diff --git a/src/Modules/ThesisResearch/DeanCommitteeChangeModal.jsx b/src/Modules/ThesisResearch/DeanCommitteeChangeModal.jsx new file mode 100644 index 000000000..51246a6a8 --- /dev/null +++ b/src/Modules/ThesisResearch/DeanCommitteeChangeModal.jsx @@ -0,0 +1,155 @@ +import React, { useState } from "react"; +import { + Modal, + Text, + Table, + Textarea, + Button, + Stack, + Group, +} from "@mantine/core"; +import { showNotification } from "@mantine/notifications"; +import axios from "axios"; +import PropTypes from "prop-types"; +import { deanCommitteeChangeReviewRoute } from "../../routes/academicRoutes"; + +export default function DeanCommitteeChangeModal({ + request, + onClose, + refresh, +}) { + const [remarks, setRemarks] = useState(""); + const [loading, setLoading] = useState(false); + + const handle = async (approve) => { + setLoading(true); + try { + await axios.post( + deanCommitteeChangeReviewRoute(request.id), + { approve, remarks }, + { + headers: { + Authorization: `Token ${localStorage.getItem("authToken")}`, + }, + }, + ); + showNotification({ + title: approve ? "Approved" : "Rejected", + message: approve + ? "Committee membership updated." + : "Request rejected; the supervisor has been notified.", + color: approve ? "green" : "yellow", + }); + refresh(); + } catch (e) { + showNotification({ + title: "Error", + message: e.response?.data?.error || "Action failed", + color: "red", + }); + } finally { + setLoading(false); + } + }; + + return ( + + + + + + Student + + {request.student_name} ({request.student_roll}) + + + + Supervisor + {request.supervisor.name} + + +
+ + Current Committee + + + + Name + Discipline + + + + {request.current_committee.map((m) => ( + + {m.name} + {m.discipline} + + ))} + +
+ + Proposed Committee (HOD approved) + + + + Name + Discipline + + + + {request.proposed_committee.map((m) => ( + + {m.name} + {m.discipline} + + ))} + +
+ +