Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions client/src/pages/A3JobEvaluationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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({});
Expand All @@ -198,6 +211,7 @@ const A3JobEvaluationForm = () => {
console.error(err);
}
};


// Show preview of signature (text or image)
const renderSignaturePreview = (field) => {
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/internshipRequestController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const InternshipRequest = require("../models/internshiprequest");
const InternshipRequest = require("../models/InternshipRequest");
const WeeklyReport = require("../models/WeeklyReport");

exports.getA1ByEmail = async (req, res) => {
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/reportController.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
53 changes: 30 additions & 23 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const presentationRoutes = require("./routes/presentationRoutes");
const User = require("./models/User");
const Evaluation = require("./models/Evaluation");


const fourWeekReportRoutes = require("./routes/fourWeekReportRoutes");
const path = require("path");

// Import cron job manager and register jobs
const cronJobManager = require("./utils/cronUtils");
const { registerAllJobs } = require("./jobs/registerCronJobs");
Expand Down Expand Up @@ -47,6 +51,11 @@ mongoose
try {
await registerAllJobs(); // Register cronjobs
console.log("Cron jobs initialized successfully");

//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);
}
Expand Down Expand Up @@ -97,30 +106,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,
Expand All @@ -131,8 +137,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!" });
Expand All @@ -142,6 +148,7 @@ app.post("/api/evaluation", async (req, res) => {
}
});


// Graceful shutdown
process.on("SIGINT", async () => {
try {
Expand Down
3 changes: 2 additions & 1 deletion server/models/Evaluation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
72 changes: 72 additions & 0 deletions server/utils/reminderCoordinatorUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// server/utils/reminderCoordinatorUtils.js
// Author: Subhash Chandra
// Reminder system for Coordinator approval on Form A.3 (Sprint 4)
const cronJobManager = require("./cronUtils");
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: null // 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 = `
<p>Dear Coordinator,</p>
<p>This is a reminder to approve the Form A.3 evaluation for student <strong>${evaluation.interneeName}</strong>.</p>
<p>Please review and approve it at the following link:</p>
<p><a href="${link}">${link}</a></p>
<p>Thank you,<br/>IPMS Team</p>
`;

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
};
Loading