From 2112c514521deed0a8c4a8b104f6c3adb7087b87 Mon Sep 17 00:00:00 2001 From: Nanvithaa Date: Mon, 14 Apr 2025 15:37:44 -0500 Subject: [PATCH 1/3] creating new branch for PR GroupE --- client/src/pages/SupervisorDashboard.js | 116 ++++++++++++------------ client/src/pages/ViewFormModal.js | 44 ++++----- server/routes/formRoutes.js | 81 ++++++++--------- 3 files changed, 116 insertions(+), 125 deletions(-) diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 18cc054d..89d30c2e 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -12,16 +12,25 @@ const SupervisorDashboard = () => { useEffect(() => { const fetchRequests = async () => { try { - const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/submissions/pending`); - - setRequests(res.data); + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/form/internshiprequests`); + const formatted = res.data.map(item => ({ + _id: item._id, + name: item.workplace?.name || "N/A", + student_id: item._id, // display _id of InternshipRequest + form_type: "A1", + createdAt: item.createdAt, + supervisor_status: "pending", + fullForm: item + })); + setRequests(formatted); setLoading(false); } catch (err) { - console.error("Error fetching requests:", err); - setMessage("Error fetching requests."); + console.error("Error fetching Internship A1 forms:", err); + setMessage("Error fetching Internship A1 forms."); setLoading(false); } }; + fetchRequests(); }, []); @@ -30,8 +39,7 @@ const SupervisorDashboard = () => { if (!confirmed) return; try { - const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/submissions/${id}/${action}`, { comment }); - + const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/submissions/${id}/${action}`, { comment }); setMessage(res.data.message || `${action} successful`); setRequests(prev => prev.filter(req => req._id !== id)); setSelectedForm(null); @@ -44,62 +52,52 @@ const SupervisorDashboard = () => { const openFormView = (form) => setSelectedForm(form); const closeFormView = () => setSelectedForm(null); - const formatDate = (dateStr) => new Date(dateStr).toLocaleDateString(); - - const sortedRequests = [...requests].sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); - - let content; - - if (loading) { - content =

Loading...

; - } - else if (sortedRequests.length === 0) { - content = ( -
-
No pending approvals.
-
- ); - } - else { - content = ( - - - - - - - - - - - - {sortedRequests.map((req) => ( - - - - - - - - ))} - -
Student NameStudent IDForm TypeDate SubmittedStatus
{req.name} - - {req.form_type}{formatDate(req.createdAt)} - - {req.supervisor_status} - -
- ); - } + const formatDate = (date) => new Date(date).toLocaleDateString(); return (

Supervisor Dashboard

{message &&

{message}

} - {content} + + {loading ? ( +

Loading...

+ ) : requests.length === 0 ? ( +
+
No pending approvals.
+
+ ) : ( + + + + + + + + + + + + {requests.map((req) => ( + + + + + + + + ))} + +
Student NameStudent IDForm TypeDate SubmittedStatus
{req.name} + + {req.form_type}{formatDate(req.createdAt)} + + {req.supervisor_status} + +
+ )} + {selectedForm && ( { ); }; -export default SupervisorDashboard; \ No newline at end of file +export default SupervisorDashboard; diff --git a/client/src/pages/ViewFormModal.js b/client/src/pages/ViewFormModal.js index e913db19..4727c86d 100644 --- a/client/src/pages/ViewFormModal.js +++ b/client/src/pages/ViewFormModal.js @@ -2,7 +2,6 @@ import React, { useState } from "react"; import "../styles/SupervisorDashboard.css"; const ViewFormModal = ({ formData, onClose, onAction }) => { - const form = typeof formData.details === "string" ? JSON.parse(formData.details) : formData.details; const [comment, setComment] = useState(""); const [error, setError] = useState(""); @@ -11,32 +10,33 @@ const ViewFormModal = ({ formData, onClose, onAction }) => { setError("Comment is required before taking action."); return; } - setError(""); // clear error + setError(""); onAction(formData._id, action, comment); }; return (
-

Form: {formData.form_type}

-

Student: {formData.name}

+

Form: A1

+

Student ID: {formData.student}

+

Workplace: {formData.workplace?.name}

+

Advisor: {formData.internshipAdvisor?.name}

+

Credit Hours: {formData.creditHours}

+

Start Date: {new Date(formData.startDate).toLocaleDateString()}

+

End Date: {new Date(formData.endDate).toLocaleDateString()}

- {form.tasks && ( -
- Tasks: -
    {form.tasks.map((task, i) =>
  • {task}
  • )}
-
- )} - - {form.outcomes && ( -
- Outcomes: -
    {form.outcomes.map((o, i) =>
  • {o}
  • )}
-
- )} - - {form.week &&

Week: {form.week}

} - {form.lessonsLearned &&

Lessons Learned: {form.lessonsLearned}

} +
+ Tasks: +
    + {formData.tasks?.map((task, index) => ( +
  • + Description: {task.description} +
    + Outcomes: {task.outcomes.join(", ")} +
  • + ))} +
+
@@ -47,10 +47,10 @@ const ViewFormModal = ({ formData, onClose, onAction }) => { rows={4} style={{ width: "100%", marginTop: "5px", borderRadius: "4px", padding: "8px" }} /> - {error &&

{error}

} + {error &&

{error}

}
-
+
diff --git a/server/routes/formRoutes.js b/server/routes/formRoutes.js index c80f3ebe..37a83a39 100644 --- a/server/routes/formRoutes.js +++ b/server/routes/formRoutes.js @@ -1,71 +1,64 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const { insertFormData } = require('../services/insertData'); +const InternshipRequest = require("../models/InternshipRequest"); +const { insertFormData } = require("../services/insertData"); -let status = ''; +// GET route to fetch all internship requests +router.get("/internshiprequests", async (req, res) => { + try { + const requests = await InternshipRequest.find().sort({ createdAt: -1 }); + res.status(200).json(requests); + } catch (err) { + console.error("Error fetching internship requests:", err); + res.status(500).json({ message: "Server error while fetching internship requests" }); + } +}); -// Validate required fields +// Validate and submit form function validateFormData(formData) { const requiredFields = [ - 'workplaceName', - 'website', - 'phone', - 'advisorName', - 'advisorJobTitle', - 'advisorEmail', - 'creditHours', - 'startDate', - 'endDate', - 'tasks' + "workplaceName", + "website", + "phone", + "advisorName", + "advisorJobTitle", + "advisorEmail", + "creditHours", + "startDate", + "endDate", + "tasks" ]; for (const field of requiredFields) { - if (!formData[field] || formData[field] === '') { + if (!formData[field] || formData[field] === "") { return `Missing or empty required field: ${field}`; } } if (!Array.isArray(formData.tasks) || formData.tasks.length === 0) { - return 'Tasks must be a non-empty array'; + return "Tasks must be a non-empty array"; } - // for (const [index, task] of formData.tasks.entries()) { - // if (!task.description || !task.outcomes) { - // return `Task at index ${index} is missing description or outcomes`; - // } - // } - // uncomment below if student has to fill in task outcomes - // const filledTasks = formData.tasks.filter((task) => task.description && task.outcomes ); - // if (filledTasks.length < 3) - // return `At least 3 tasks must have description and outcomes; only ${filledTasks.length} do`; - - const tasks = formData.tasks; - console.log(tasks); - if (tasks.filter((task) => task.description).length < 3) - return 'At least 3 tasks must be provided'; - const uniqueOutcomes = new Set(); - tasks.forEach((task) => { - if (Array.isArray(task.outcomes)) { - task.outcomes.forEach(outcome => uniqueOutcomes.add(outcome)); - } + const outcomes = new Set(); + formData.tasks.forEach((task) => { + task.outcomes?.forEach(o => outcomes.add(o)); }); - formData.status = uniqueOutcomes.size < 3 ? 'pending manual review' : 'submitted'; + + formData.status = outcomes.size < 3 ? "pending manual review" : "submitted"; return null; } -router.post('/submit', async (req, res) => { +router.post("/submit", async (req, res) => { const formData = req.body; - const validationError = validateFormData(formData); - if (validationError) { - return res.status(400).json({ message: validationError }); - } + const error = validateFormData(formData); + if (error) return res.status(400).json({ message: error }); try { await insertFormData(formData); - res.status(200).json({ message: 'Form received and handled!', status, manual: formData.status !== 'submitted'}); + res.status(200).json({ message: "Form received and stored." }); } catch (error) { - console.error('Error handling form data:', error); - res.status(500).json({ message: 'Something went wrong' }); + console.error("Insert error:", error); + res.status(500).json({ message: "Something went wrong" }); } }); From 45a6599e8d41219e9d1ebcb9c41bf2460a608fa1 Mon Sep 17 00:00:00 2001 From: Nanvithaa Date: Mon, 14 Apr 2025 19:04:55 -0500 Subject: [PATCH 2/3] Everything is working now --- client/src/pages/SupervisorDashboard.js | 3 +- server/controllers/approvalController.js | 9 +- server/models/InternshipRequest.js | 112 +++++++++++++---------- server/routes/formRoutes.js | 20 +++- 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 89d30c2e..23be731a 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -39,7 +39,8 @@ const SupervisorDashboard = () => { if (!confirmed) return; try { - const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/submissions/${id}/${action}`, { comment }); + const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/form/internshiprequests/${id}/${action}`, { comment }); + setMessage(res.data.message || `${action} successful`); setRequests(prev => prev.filter(req => req._id !== id)); setSelectedForm(null); diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index 089412fb..145e9908 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -19,7 +19,8 @@ exports.approveSubmission = async (req, res) => { const { comment } = req.body; try { - const submission = await Submission.findByIdAndUpdate( + const InternshipRequest = require("../models/InternshipRequest"); + const submission = await InternshipRequest.findByIdAndUpdate( id, { supervisor_status: "Approved", @@ -34,9 +35,8 @@ exports.approveSubmission = async (req, res) => { res.json({ message: "Submission approved and forwarded to Coordinator", - updatedSubmission: submission + updatedSubmission: submission, }); - } catch (err) { res.status(500).json({ message: "Approval failed", error: err }); } @@ -63,9 +63,8 @@ exports.rejectSubmission = async (req, res) => { res.json({ message: "Submission rejected", - updatedSubmission: submission + updatedSubmission: submission, }); - } catch (err) { res.status(500).json({ message: "Rejection failed", error: err }); } diff --git a/server/models/InternshipRequest.js b/server/models/InternshipRequest.js index 3732f04d..91fb8936 100644 --- a/server/models/InternshipRequest.js +++ b/server/models/InternshipRequest.js @@ -2,71 +2,89 @@ const mongoose = require("mongoose"); // why are we commonjs const ObjectId = mongoose.Schema.Types.ObjectId; const Task = new mongoose.Schema({ - _id: false, - description: { - type: String, - required: true - }, - outcomes: { - type: [String], - enum: ['problemSolving','solutionDevelopment', 'communication', 'decisionMaking', 'collaboration', 'application'] - } + _id: false, + description: { + type: String, + required: true, + }, + outcomes: { + type: [String], + enum: [ + "problemSolving", + "solutionDevelopment", + "communication", + "decisionMaking", + "collaboration", + "application", + ], + }, }); -const formA1 = new mongoose.Schema({ - student: { // get student's name, email, id from User - type: ObjectId, - required: true, - ref: 'User' +const formA1 = new mongoose.Schema( + { + student: { + // get student's name, email, id from User + type: ObjectId, + required: true, + ref: "User", }, workplace: { - name: { - type: String, - required: true, - }, - website: String, - phone: String, // TODO how to validate this? + name: { + type: String, + required: true, + }, + website: String, + phone: String, // TODO how to validate this? }, internshipAdvisor: { - name: String, - jobTitle: String, - email: { - type: String, - required: true - } + name: String, + jobTitle: String, + email: { + type: String, + required: true, + }, }, creditHours: { - type: Number, - required: true, - enum: [1, 2, 3] + type: Number, + required: true, + enum: [1, 2, 3], }, startDate: { - type: Date, - required: true + type: Date, + required: true, }, - endDate: { // TODO how to make sure endDate is later than startDate? - type: Date, - required: true + endDate: { + // TODO how to make sure endDate is later than startDate? + type: Date, + required: true, }, tasks: { - type: [Task], - required: true + type: [Task], + required: true, }, status: { - type: String, - required: true, - enum: ['draft', 'submitted','pending manual review' ,'approved'] + type: String, + required: true, + enum: ["draft", "submitted", "pending manual review", "approved"], + }, + supervisor_status: { + type: String, + }, + supervisor_comment: { + type: String, }, approvals: { - type: [String], - enum: ['advisor', 'coordinator'] + type: [String], + enum: ["advisor", "coordinator"], }, reminders: [Date], // requiredHours is an easily derived attribute // TODO needs to be a virtual getter that checks this student's WeeklyReports - completedHours: Number -}, { timestamps: true }); -formA1.virtual("requiredHours").get(function() { - return this.creditHours * 60; -}) + completedHours: Number, + }, + { timestamps: true } +); +formA1.virtual("requiredHours").get(function () { + return this.creditHours * 60; +}); -module.exports = mongoose.model("InternshipRequest", formA1); \ No newline at end of file +module.exports = mongoose.model("InternshipRequest", formA1); diff --git a/server/routes/formRoutes.js b/server/routes/formRoutes.js index 37a83a39..3ed75f52 100644 --- a/server/routes/formRoutes.js +++ b/server/routes/formRoutes.js @@ -2,11 +2,25 @@ const express = require("express"); const router = express.Router(); const InternshipRequest = require("../models/InternshipRequest"); const { insertFormData } = require("../services/insertData"); +const { + getPendingSubmissions, + approveSubmission, + rejectSubmission +} = require("../controllers/approvalController"); -// GET route to fetch all internship requests +router.post("/internshiprequests/:id/approve", approveSubmission); +router.post("/internshiprequests/:id/reject", rejectSubmission); + +// GET route to fetch internship requests without supervisor_comment and supervisor_status router.get("/internshiprequests", async (req, res) => { try { - const requests = await InternshipRequest.find().sort({ createdAt: -1 }); + const requests = await InternshipRequest.find({ + status: "submitted", + approvals: { $all: ["advisor", "coordinator"] }, + supervisor_comment: { $exists: false }, + supervisor_status: { $exists: false } + }).sort({ createdAt: -1 }); + res.status(200).json(requests); } catch (err) { console.error("Error fetching internship requests:", err); @@ -14,6 +28,7 @@ router.get("/internshiprequests", async (req, res) => { } }); + // Validate and submit form function validateFormData(formData) { const requiredFields = [ @@ -62,4 +77,5 @@ router.post("/submit", async (req, res) => { } }); + module.exports = router; From 87439dfca2a08c4f1537cccfb36a254677e97111 Mon Sep 17 00:00:00 2001 From: Nanvithaa Date: Mon, 14 Apr 2025 19:12:10 -0500 Subject: [PATCH 3/3] Everything is working now --- server/controllers/approvalController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index 145e9908..bf6d5f70 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -42,13 +42,13 @@ exports.approveSubmission = async (req, res) => { } }; -// ✅ Supervisor Rejects a submission exports.rejectSubmission = async (req, res) => { const { id } = req.params; const { comment } = req.body; try { - const submission = await Submission.findByIdAndUpdate( + const InternshipRequest = require("../models/InternshipRequest"); + const submission = await InternshipRequest.findByIdAndUpdate( id, { supervisor_status: "Rejected", @@ -62,7 +62,7 @@ exports.rejectSubmission = async (req, res) => { } res.json({ - message: "Submission rejected", + message: "Submission rejected and sent back to student", updatedSubmission: submission, }); } catch (err) {