From 85d262c65e70128cae4d5dd50f28b13d9b6a432d Mon Sep 17 00:00:00 2001 From: Subhash Date: Sun, 27 Apr 2025 23:57:45 -0500 Subject: [PATCH 1/3] Sprint 4: Added Coordinator Reminder System --- .../internshipRequestController.js | 2 +- server/controllers/reportController.js | 2 +- server/index.js | 6 ++ server/utils/reminderCoordinatorUtils.js | 73 +++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 server/utils/reminderCoordinatorUtils.js diff --git a/server/controllers/internshipRequestController.js b/server/controllers/internshipRequestController.js index e03c0c07..f6929ea9 100644 --- a/server/controllers/internshipRequestController.js +++ b/server/controllers/internshipRequestController.js @@ -1,4 +1,4 @@ -const InternshipRequest = require("../models/internshiprequest"); +const InternshipRequest = require("../models/InternshipRequest"); const WeeklyReport = require("../models/WeeklyReport"); exports.getA1ByEmail = async (req, res) => { diff --git a/server/controllers/reportController.js b/server/controllers/reportController.js index c5179546..86985cba 100644 --- a/server/controllers/reportController.js +++ b/server/controllers/reportController.js @@ -1,7 +1,7 @@ const WeeklyReport = require("../models/WeeklyReport"); const SupervisorReview = require("../models/SupervisorReview"); const CoordinatorReview = require("../models/CoordinatorReview"); -const InternshipRequest = require("../models/internshiprequest"); +const InternshipRequest = require("../models/InternshipRequest"); const { sendStudentProgressEmail } = require("../jobs/reminderEmail"); const STATIC_USER_ID = "vikash123"; diff --git a/server/index.js b/server/index.js index 80b7fe97..ea97d7ee 100644 --- a/server/index.js +++ b/server/index.js @@ -18,6 +18,8 @@ const outcomeRoutes = require("./routes/outcomeRoutes"); const cronJobManager = require("./utils/cronUtils").cronJobManager; const { registerAllJobs } = require("./jobs/registerCronJobs"); const Evaluation = require("./models/Evaluation"); + + const fourWeekReportRoutes = require("./routes/fourWeekReportRoutes"); const path = require("path"); @@ -45,6 +47,10 @@ mongoose try { await registerAllJobs(); console.log("Cron jobs initialized successfully"); + + // ✅ Register your coordinator reminder job here + const { registerCoordinatorReminderJob } = require("./utils/reminderCoordinatorUtils"); + registerCoordinatorReminderJob(); } catch (error) { console.error("Failed to initialize cron jobs:", error); } diff --git a/server/utils/reminderCoordinatorUtils.js b/server/utils/reminderCoordinatorUtils.js new file mode 100644 index 00000000..80a58991 --- /dev/null +++ b/server/utils/reminderCoordinatorUtils.js @@ -0,0 +1,73 @@ +// server/utils/reminderCoordinatorUtils.js +// Author: Subhash Chandra +// Reminder system for Coordinator approval on Form A.3 (Sprint 4) + +const cronJobManager = require("./cronUtils").cronJobManager; +const Evaluation = require("../models/Evaluation"); +const emailService = require("../services/emailService"); +const logger = require("./logger"); +const dayjs = require("dayjs"); + +function registerCoordinatorReminderJob() { + cronJobManager.registerJob( + "CoordinatorReminder", + "0 9 * * *", // 🔥 Every day at 9:00 AM (Production cron expression) + async () => { + try { + const threeDaysAgo = dayjs().subtract(3, "day").toDate(); + + // Find evaluations submitted but not yet approved + const pendingEvaluations = await Evaluation.find({ + status: "submitted", + updatedAt: { $lt: threeDaysAgo }, + coordinatorSignature: { $exists: false } // Coordinator hasn't signed yet + }); + + logger.info(`CoordinatorReminder: Found ${pendingEvaluations.length} pending submissions.`); + + for (const evaluation of pendingEvaluations) { + const toEmail = evaluation.coordinatorEmail; // Assuming you have coordinatorEmail in the schema + + if (!toEmail) { + logger.warn(`Skipping reminder: missing coordinator email for evaluation ID ${evaluation._id}`); + continue; + } + + const subject = "Approval Pending: Form A.3 Evaluation Reminder"; + const link = `https://yourdomain.com/coordinator/approval/${evaluation._id}`; // Change link properly if needed + + const html = ` +

Dear Coordinator,

+

This is a reminder to approve the Form A.3 evaluation for student ${evaluation.interneeName}.

+

Please review and approve it at the following link:

+

${link}

+

Thank you,
IPMS Team

+ `; + + try { + await emailService.sendEmail({ + to: toEmail, + subject, + html, + text: `Reminder to approve Form A.3 for ${evaluation.interneeName}. Link: ${link}`, + }); + + logger.info(`CoordinatorReminder: Email sent successfully to ${toEmail} for evaluation ${evaluation._id}`); + } catch (emailError) { + logger.error(`CoordinatorReminder: Failed to send email to ${toEmail}: ${emailError.message}`); + } + } + } catch (err) { + logger.error("CoordinatorReminder job failed: " + err.message); + } + }, + { + timezone: "America/Chicago", + runOnInit: false // ✅ Only run daily; don't trigger immediately + } + ); +} + +module.exports = { + registerCoordinatorReminderJob +}; From 395ce52a34afe43a0446b8b0755cb41d2e975bab Mon Sep 17 00:00:00 2001 From: Subhash Date: Mon, 28 Apr 2025 15:23:52 -0500 Subject: [PATCH 2/3] Reminder System --- server/index.js | 3 ++- server/utils/reminderCoordinatorUtils.js | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/index.js b/server/index.js index ea97d7ee..59e8b904 100644 --- a/server/index.js +++ b/server/index.js @@ -48,9 +48,10 @@ mongoose await registerAllJobs(); console.log("Cron jobs initialized successfully"); - // ✅ Register your coordinator reminder job here + //Register your coordinator reminder job here const { registerCoordinatorReminderJob } = require("./utils/reminderCoordinatorUtils"); registerCoordinatorReminderJob(); + //await manualCoordinatorReminderTest(); // This runs immediately for testing } catch (error) { console.error("Failed to initialize cron jobs:", error); } diff --git a/server/utils/reminderCoordinatorUtils.js b/server/utils/reminderCoordinatorUtils.js index 80a58991..f8b8dbb5 100644 --- a/server/utils/reminderCoordinatorUtils.js +++ b/server/utils/reminderCoordinatorUtils.js @@ -1,8 +1,7 @@ // server/utils/reminderCoordinatorUtils.js // Author: Subhash Chandra // Reminder system for Coordinator approval on Form A.3 (Sprint 4) - -const cronJobManager = require("./cronUtils").cronJobManager; +const cronJobManager = require("./cronUtils"); const Evaluation = require("../models/Evaluation"); const emailService = require("../services/emailService"); const logger = require("./logger"); @@ -11,7 +10,7 @@ const dayjs = require("dayjs"); function registerCoordinatorReminderJob() { cronJobManager.registerJob( "CoordinatorReminder", - "0 9 * * *", // 🔥 Every day at 9:00 AM (Production cron expression) + "0 9 * * *", // Every day at 9:00 AM (Production cron expression) async () => { try { const threeDaysAgo = dayjs().subtract(3, "day").toDate(); @@ -20,7 +19,7 @@ function registerCoordinatorReminderJob() { const pendingEvaluations = await Evaluation.find({ status: "submitted", updatedAt: { $lt: threeDaysAgo }, - coordinatorSignature: { $exists: false } // Coordinator hasn't signed yet + coordinatorSignature: null // Coordinator hasn't signed yet }); logger.info(`CoordinatorReminder: Found ${pendingEvaluations.length} pending submissions.`); @@ -63,7 +62,7 @@ function registerCoordinatorReminderJob() { }, { timezone: "America/Chicago", - runOnInit: false // ✅ Only run daily; don't trigger immediately + runOnInit: false // Only run daily; don't trigger immediately } ); } From 127a75a644383703cefd64c28465fd1d3985afa4 Mon Sep 17 00:00:00 2001 From: rithwik-d Date: Mon, 28 Apr 2025 17:32:14 -0500 Subject: [PATCH 3/3] Lock status updated --- client/src/pages/A3JobEvaluationForm.jsx | 30 +++++++++++----- server/index.js | 44 +++++++++++------------- server/models/Evaluation.js | 3 +- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/client/src/pages/A3JobEvaluationForm.jsx b/client/src/pages/A3JobEvaluationForm.jsx index cc510f71..beb8c6fa 100644 --- a/client/src/pages/A3JobEvaluationForm.jsx +++ b/client/src/pages/A3JobEvaluationForm.jsx @@ -146,10 +146,24 @@ const A3JobEvaluationForm = () => { // Submit the form to the backend const handleSubmit = async (e) => { e.preventDefault(); + if (!validateForm() || !formData.supervisorAgreement || !formData.coordinatorAgreement) { alert("Please confirm internee details and both signature agreements before submitting."); return; } + + // ✅ Build evaluations array with ALL 9 ITEMS + const evaluations = evaluationItems.slice(0, 3).map((item) => ({ + category: item, + rating: ratings[item], + comment: comments[item] || "" + })); + + if (evaluations.length !== 3 || evaluations.some(ev => !ev.rating)) { + alert("Please complete ratings for the required 3 evaluation items before submitting."); + return; + } + try { const response = await fetch( `${process.env.REACT_APP_API_URL}/api/evaluation`, @@ -161,28 +175,27 @@ const A3JobEvaluationForm = () => { interneeID: formData.interneeID, interneeEmail: formData.interneeEmail, supervisorSignature: formData.supervisorSignature, - coordinatorSignature: formData.coordinatorSignature, supervisorAgreement: formData.supervisorAgreement, + coordinatorSignature: formData.coordinatorSignature, coordinatorAgreement: formData.coordinatorAgreement, - ratings, - comments, + evaluations, + locked: true, // 🔥 ADD THIS LINE }), - } + } ); + if (response.ok) { alert("Evaluation submitted successfully!"); + // Reset form fields setFormData({ interneeName: "", interneeID: "", interneeEmail: "", - supervisorName: "", - supervisorJobTitle: "", - supervisorEmail: "", supervisorSignature: "", supervisorAgreement: false, coordinatorSignature: "", coordinatorAgreement: false, - locked: true, //locked when properly approved + locked: true, }); setRatings({}); setComments({}); @@ -198,6 +211,7 @@ const A3JobEvaluationForm = () => { console.error(err); } }; + // Show preview of signature (text or image) const renderSignaturePreview = (field) => { diff --git a/server/index.js b/server/index.js index 0a94c037..81f8d2a1 100644 --- a/server/index.js +++ b/server/index.js @@ -97,30 +97,27 @@ app.post("/api/createUser", async (req, res) => { }); // Temporary API for saving an evaluation -app.post("/api/evaluation", async (req, res) => { +app.post('/api/evaluation', async (req, res) => { try { - const { interneeName, interneeID, interneeEmail, supervisorSignature, supervisorAgreement, coordinatorSignature, coordinatorAgreement, ratings, comments } = req.body; - - //check if there's an existing evaluation for the given interneeID and email - const existingEvaluation = await Evaluation.findOne({ interneeID, interneeEmail }); - - if (existingEvaluation) { - //If evaluation is locked, prevent update - if (existingEvaluation.locked) { - return res.status(400).json({ error: "Evaluation is locked and cannot be modified." }); - } + // ✅ Extract fields directly from req.body + const { + interneeName, + interneeID, + interneeEmail, + supervisorSignature, + supervisorAgreement, + coordinatorSignature, + coordinatorAgreement, + evaluations, + locked, + } = req.body; - //If evaluation is not in 'draft' status, prevent update - if (existingEvaluation.status !== 'draft') { - return res.status(400).json({ error: "This evaluation has already been finalized and cannot be modified." }); - } - } - const evaluations = Object.keys(ratings).map((category) => ({ - category, - rating: ratings[category], - comment: comments[category] || "", - })); + + // ✅ Optional: Validate evaluations exist and have exactly 3 items + if (!evaluations || evaluations.length !== 3) { + return res.status(400).json({ error: 'Exactly 3 evaluation items must be provided' }); + } const newEvaluation = new Evaluation({ interneeName, @@ -131,8 +128,8 @@ app.post("/api/evaluation", async (req, res) => { coordinatorSignature, coordinatorAgreement, evaluations, - locked: false, - }); + locked, + }); await newEvaluation.save(); res.status(201).json({ message: "Evaluation saved successfully!" }); @@ -142,6 +139,7 @@ app.post("/api/evaluation", async (req, res) => { } }); + // Graceful shutdown process.on("SIGINT", async () => { try { diff --git a/server/models/Evaluation.js b/server/models/Evaluation.js index cbf5692e..933b9e5a 100644 --- a/server/models/Evaluation.js +++ b/server/models/Evaluation.js @@ -67,7 +67,8 @@ const evaluationSchema = new mongoose.Schema({ evaluations: { type: [evaluationItemSchema], validate: [arr => arr.length === 3, 'Exactly 3 evaluation items are required'] - }, + } + , supervisorSignature: { type: signatureSchema,