From 149289bb2e0b575bbbcba7f96180b63d47d0edcc Mon Sep 17 00:00:00 2001 From: Vijay Date: Sun, 13 Apr 2025 16:59:19 -0500 Subject: [PATCH 1/3] request detail view --- server/controllers/approvalController.js | 72 +++++++++++++++- server/index.js | 1 - server/middleware/authMiddleware.js | 15 +++- server/routes/approvalRoutes.js | 30 ++++++- server/seed.js | 104 +++++++++++++++++++++++ 5 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 server/seed.js diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index 8b3357a5e..5909aa9a6 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -1,4 +1,6 @@ const Submission = require("../models/Submission"); +const InternshipRequest = require("../models/InternshipRequest"); +const EmailService = require("../services/emailService"); // ✅ Get pending submissions for supervisor exports.getPendingSubmissions = async (req, res) => { @@ -6,7 +8,9 @@ exports.getPendingSubmissions = async (req, res) => { const submissions = await Submission.find({ supervisor_status: "pending" }); res.json(submissions); } catch (err) { - res.status(500).json({ message: "Failed to fetch pending submissions", error: err }); + res + .status(500) + .json({ message: "Failed to fetch pending submissions", error: err }); } }; @@ -27,9 +31,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 }); } @@ -52,9 +55,70 @@ 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 }); + } +}; +// Coordinator: Get Request Details +exports.getCoordinatorRequestDetails = async (req, res) => { + try { + const requestData = await InternshipRequest.findById( + req.params.id + ).populate("student", "name email"); + + if (!requestData) + return res.status(404).json({ message: "Request not found" }); + + res.status(200).json(requestData); + } catch (err) { + res.status(500).json({ message: "Failed to fetch details", error: err }); + } +}; + +// Coordinator: Approve Request +exports.coordinatorApproveRequest = async (req, res) => { + try { + const request = await InternshipRequest.findByIdAndUpdate( + req.params.id, + { status: "approved" }, + { new: true } + ); + + if (!request) return res.status(404).json({ message: "Request not found" }); + + await emailService.sendEmail({ + to: request.student.email, + subject: "Internship Request Approved", + html: `

Your internship request has been approved by the Coordinator.

`, + }); + + res.json({ message: "Request Approved Successfully" }); + } catch (err) { + res.status(500).json({ message: "Approval failed", error: err }); + } +}; + +// Coordinator: Reject Request +exports.coordinatorRejectRequest = async (req, res) => { + const { comment } = req.body; + try { + const request = await InternshipRequest.findByIdAndUpdate( + req.params.id, + { status: "rejected", comment }, + { new: true } + ); + + if (!request) return res.status(404).json({ message: "Request not found" }); + + await emailService.sendEmail({ + to: request.student.email, + subject: "Internship Request Rejected", + html: `

Your internship request was rejected by the Coordinator. Reason: ${comment}

`, }); + res.json({ message: "Request Rejected Successfully" }); } catch (err) { res.status(500).json({ message: "Rejection failed", error: err }); } diff --git a/server/index.js b/server/index.js index 8b71ee0cc..fc6784ac6 100644 --- a/server/index.js +++ b/server/index.js @@ -17,7 +17,6 @@ const cronJobManager = require("./utils/cronUtils"); const { registerAllJobs } = require("./jobs/registerCronJobs"); const Evaluation = require("./models/Evaluation"); - const app = express(); app.use(express.json()); app.use(cors()); diff --git a/server/middleware/authMiddleware.js b/server/middleware/authMiddleware.js index adef8663d..e0c14580c 100644 --- a/server/middleware/authMiddleware.js +++ b/server/middleware/authMiddleware.js @@ -1,11 +1,20 @@ exports.isSupervisor = (req, res, next) => { // const supervisor = Sup.find({$id: username}) - - req.user = { role: 'supervisor' }; // Mocking user role for demo + req.user = { role: "supervisor" }; // Mocking user role for demo if (req.user.role === "supervisor") { next(); } else { res.status(403).json({ message: "Access denied. Not a supervisor." }); } -}; \ No newline at end of file +}; + +exports.isCoordinator = (req, res, next) => { + req.user = { role: "coordinator" }; // Mocking role for now (or fetch from DB if implemented) + + if (req.user.role === "coordinator") { + next(); + } else { + res.status(403).json({ message: "Access denied. Not a coordinator." }); + } +}; diff --git a/server/routes/approvalRoutes.js b/server/routes/approvalRoutes.js index 0d8211b83..0ae97ade5 100644 --- a/server/routes/approvalRoutes.js +++ b/server/routes/approvalRoutes.js @@ -1,10 +1,34 @@ const express = require("express"); const router = express.Router(); -const { getPendingSubmissions, approveSubmission, rejectSubmission } = require("../controllers/approvalController"); -const { isSupervisor } = require("../middleware/authMiddleware"); +const { + getPendingSubmissions, + approveSubmission, + rejectSubmission, +} = require("../controllers/approvalController"); +const { isSupervisor, isCoordinator } = require("../middleware/authMiddleware"); +const { + getCoordinatorRequestDetails, + coordinatorApproveRequest, + coordinatorRejectRequest, +} = require("../controllers/approvalController"); router.get("/submissions/pending", isSupervisor, getPendingSubmissions); router.post("/submissions/:id/approve", isSupervisor, approveSubmission); router.post("/submissions/:id/reject", isSupervisor, rejectSubmission); +router.get( + "/coordinator/request/:id", + isCoordinator, + getCoordinatorRequestDetails +); +router.post( + "/coordinator/request/:id/approve", + isCoordinator, + coordinatorApproveRequest +); +router.post( + "/coordinator/request/:id/reject", + isCoordinator, + coordinatorRejectRequest +); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/server/seed.js b/server/seed.js new file mode 100644 index 000000000..5100b25d5 --- /dev/null +++ b/server/seed.js @@ -0,0 +1,104 @@ +const mongoose = require("mongoose"); +const User = require("./models/User"); +const InternshipRequest = require("./models/InternshipRequest"); +const Submission = require("./models/Submission"); +const Evaluation = require("./models/Evaluation"); + +mongoose + .connect("mongodb://localhost:27017/IPMS") + .then(() => { + console.log("MongoDB Connected Successfully"); + return seedData(); + }) + .catch((err) => console.log(err)); + +async function seedData() { + try { + await User.deleteMany(); + await InternshipRequest.deleteMany(); + await Submission.deleteMany(); + await Evaluation.deleteMany(); + + console.log("Old Data Removed"); + + // 1. Insert Student User + const studentUser = await User.create({ + userName: "Vijay Chirram", + email: "vijay.chirram@ou.edu", + password: "password123", + role: "student", + }); + + // 2. Insert Coordinator User + const coordinatorUser = await User.create({ + userName: "Coordinator OU", + email: "coordinator@ou.edu", + password: "password123", + role: "coordinator", + }); + + // 3. Insert Internship Request (Form A1) + const internshipRequest = await InternshipRequest.create({ + student: studentUser._id, + workplace: { + name: "Google", + website: "https://google.com", + phone: "1234567890", + }, + internshipAdvisor: { + name: "Dr. Smith", + jobTitle: "Manager", + email: "drsmith@google.com", + }, + creditHours: 3, + startDate: new Date("2025-01-01"), + endDate: new Date("2025-06-01"), + tasks: [ + { + description: "Web App Development", + outcomes: ["problemSolving", "communication"], + }, + ], + status: "submitted", + approvals: ["advisor"], + completedHours: 50, + }); + + // 4. Insert Submission (Optional) + await Submission.create({ + name: "Weekly Report 1", + student_name: "Vijay Chirram", + details: "Completed initial project setup", + supervisor_status: "pending", + }); + + // 5. Insert Evaluation (Optional) + await Evaluation.create({ + interneeId: studentUser._id, + internshipId: internshipRequest._id, + evaluations: [ + { + category: "Communication", + rating: "Satisfactory", + comment: "Excellent communication skills", + }, + ], + advisorSignature: { + type: "text", + value: "Dr.Smith", + }, + advisorAgreement: true, + coordinatorSignature: { + type: "text", + value: "Coordinator", + }, + coordinatorAgreement: true, + }); + + console.log("Dummy Data Inserted Successfully!"); + process.exit(0); + } catch (err) { + console.error(err); + process.exit(1); + } +} From 65edf7dcedc0ae9b6774ecd38799f1274bafefd5 Mon Sep 17 00:00:00 2001 From: Vijay Date: Sun, 13 Apr 2025 17:02:27 -0500 Subject: [PATCH 2/3] Updated routes in router.js --- client/src/router.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/router.js b/client/src/router.js index 740942425..e562e458f 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -3,7 +3,6 @@ import React from "react"; import { createBrowserRouter } from "react-router-dom"; import A1InternshipRequestForm from "./pages/A1InternshipRequestForm"; - // Layout import Layout from "./components/Layout"; @@ -15,6 +14,7 @@ import WeeklyProgressReportForm from "./pages/WeeklyProgressReportForm"; import A3JobEvaluationForm from "./pages/A3JobEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; +import CoordinatorRequestDetailView from "./pages/CoordinatorRequestDetailView"; // Create and export the router configuration const router = createBrowserRouter([ @@ -51,9 +51,12 @@ const router = createBrowserRouter([ path: "coordinator-dashboard", element: , }, + { + path: "coordinator/request/:id", + element: , + }, ], }, ]); - export default router; From f2e59660bf51fb695ef2fed8b3f4e39f26f06cee Mon Sep 17 00:00:00 2001 From: Vijay Date: Sun, 13 Apr 2025 19:42:38 -0500 Subject: [PATCH 3/3] final request detail view --- .../src/pages/CoordinatorRequestDetailView.js | 81 ++++++++++++++ .../styles/CoordinatorRequestDetailView.css | 17 +++ server/controllers/approvalController.js | 59 ++-------- server/routes/approvalRoutes.js | 10 -- server/seed.js | 104 ------------------ 5 files changed, 109 insertions(+), 162 deletions(-) create mode 100644 client/src/pages/CoordinatorRequestDetailView.js create mode 100644 client/src/styles/CoordinatorRequestDetailView.css delete mode 100644 server/seed.js diff --git a/client/src/pages/CoordinatorRequestDetailView.js b/client/src/pages/CoordinatorRequestDetailView.js new file mode 100644 index 000000000..3eadf0aaa --- /dev/null +++ b/client/src/pages/CoordinatorRequestDetailView.js @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; +import { useParams } from "react-router-dom"; +import "../styles/CoordinatorRequestDetailView.css"; + +const CoordinatorRequestDetailView = () => { + const { id } = useParams(); + const [data, setData] = useState(null); + + useEffect(() => { + axios + .get(`${process.env.REACT_APP_API_URL}/api/coordinator/request/${id}`) + .then((res) => setData(res.data)) + .catch((err) => console.log(err)); + }, [id]); + + if (!data) return

Loading...

; + + const { requestData, supervisorStatus } = data; + + return ( +
+

Internship Request Details

+ +
+

+ Student Name: {requestData.student.userName} +

+

+ Student Email: {requestData.student.email} +

+

+ Company: {requestData.workplace.name} +

+

+ Company Website: {requestData.workplace.website} +

+

+ Company Phone: {requestData.workplace.phone} +

+

+ Internship Advisor: {requestData.internshipAdvisor.name} ( + {requestData.internshipAdvisor.email}) +

+

+ Credit Hours: {requestData.creditHours} +

+

+ Start Date:{" "} + {new Date(requestData.startDate).toLocaleDateString()} +

+

+ End Date: {new Date(requestData.endDate).toLocaleDateString()} +

+

+ Supervisor Approval Status: {supervisorStatus} +

+ +

Tasks & CS Outcomes

+ + + + + + + + + {requestData.tasks.map((task, idx) => ( + + + + + ))} + +
Task DescriptionCS Outcomes
{task.description}{task.outcomes.join(", ")}
+
+
+ ); +}; + +export default CoordinatorRequestDetailView; diff --git a/client/src/styles/CoordinatorRequestDetailView.css b/client/src/styles/CoordinatorRequestDetailView.css new file mode 100644 index 000000000..f47f167dd --- /dev/null +++ b/client/src/styles/CoordinatorRequestDetailView.css @@ -0,0 +1,17 @@ +@import "./App.css"; + +.request-form { + max-width: 700px; + margin: 40px auto; + padding: 30px; + background-color: white; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); +} + +.section-title { + color: #9d2235; + margin-top: 20px; + font-weight: 600; + font-size: 1.1rem; +} diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index 5909aa9a6..65e2727b4 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -66,60 +66,23 @@ exports.getCoordinatorRequestDetails = async (req, res) => { try { const requestData = await InternshipRequest.findById( req.params.id - ).populate("student", "name email"); + ).populate("student", "userName email"); - if (!requestData) + if (!requestData) { return res.status(404).json({ message: "Request not found" }); + } - res.status(200).json(requestData); - } catch (err) { - res.status(500).json({ message: "Failed to fetch details", error: err }); - } -}; - -// Coordinator: Approve Request -exports.coordinatorApproveRequest = async (req, res) => { - try { - const request = await InternshipRequest.findByIdAndUpdate( - req.params.id, - { status: "approved" }, - { new: true } - ); - - if (!request) return res.status(404).json({ message: "Request not found" }); - - await emailService.sendEmail({ - to: request.student.email, - subject: "Internship Request Approved", - html: `

Your internship request has been approved by the Coordinator.

`, + const submissionData = await Submission.findOne({ + student_name: requestData.student.userName, }); - res.json({ message: "Request Approved Successfully" }); - } catch (err) { - res.status(500).json({ message: "Approval failed", error: err }); - } -}; - -// Coordinator: Reject Request -exports.coordinatorRejectRequest = async (req, res) => { - const { comment } = req.body; - try { - const request = await InternshipRequest.findByIdAndUpdate( - req.params.id, - { status: "rejected", comment }, - { new: true } - ); - - if (!request) return res.status(404).json({ message: "Request not found" }); - - await emailService.sendEmail({ - to: request.student.email, - subject: "Internship Request Rejected", - html: `

Your internship request was rejected by the Coordinator. Reason: ${comment}

`, + res.status(200).json({ + requestData, + supervisorStatus: submissionData + ? submissionData.supervisor_status + : "Not Submitted", }); - - res.json({ message: "Request Rejected Successfully" }); } catch (err) { - res.status(500).json({ message: "Rejection failed", error: err }); + res.status(500).json({ message: "Failed to fetch details", error: err }); } }; diff --git a/server/routes/approvalRoutes.js b/server/routes/approvalRoutes.js index 0ae97ade5..dc4d62ba8 100644 --- a/server/routes/approvalRoutes.js +++ b/server/routes/approvalRoutes.js @@ -20,15 +20,5 @@ router.get( isCoordinator, getCoordinatorRequestDetails ); -router.post( - "/coordinator/request/:id/approve", - isCoordinator, - coordinatorApproveRequest -); -router.post( - "/coordinator/request/:id/reject", - isCoordinator, - coordinatorRejectRequest -); module.exports = router; diff --git a/server/seed.js b/server/seed.js deleted file mode 100644 index 5100b25d5..000000000 --- a/server/seed.js +++ /dev/null @@ -1,104 +0,0 @@ -const mongoose = require("mongoose"); -const User = require("./models/User"); -const InternshipRequest = require("./models/InternshipRequest"); -const Submission = require("./models/Submission"); -const Evaluation = require("./models/Evaluation"); - -mongoose - .connect("mongodb://localhost:27017/IPMS") - .then(() => { - console.log("MongoDB Connected Successfully"); - return seedData(); - }) - .catch((err) => console.log(err)); - -async function seedData() { - try { - await User.deleteMany(); - await InternshipRequest.deleteMany(); - await Submission.deleteMany(); - await Evaluation.deleteMany(); - - console.log("Old Data Removed"); - - // 1. Insert Student User - const studentUser = await User.create({ - userName: "Vijay Chirram", - email: "vijay.chirram@ou.edu", - password: "password123", - role: "student", - }); - - // 2. Insert Coordinator User - const coordinatorUser = await User.create({ - userName: "Coordinator OU", - email: "coordinator@ou.edu", - password: "password123", - role: "coordinator", - }); - - // 3. Insert Internship Request (Form A1) - const internshipRequest = await InternshipRequest.create({ - student: studentUser._id, - workplace: { - name: "Google", - website: "https://google.com", - phone: "1234567890", - }, - internshipAdvisor: { - name: "Dr. Smith", - jobTitle: "Manager", - email: "drsmith@google.com", - }, - creditHours: 3, - startDate: new Date("2025-01-01"), - endDate: new Date("2025-06-01"), - tasks: [ - { - description: "Web App Development", - outcomes: ["problemSolving", "communication"], - }, - ], - status: "submitted", - approvals: ["advisor"], - completedHours: 50, - }); - - // 4. Insert Submission (Optional) - await Submission.create({ - name: "Weekly Report 1", - student_name: "Vijay Chirram", - details: "Completed initial project setup", - supervisor_status: "pending", - }); - - // 5. Insert Evaluation (Optional) - await Evaluation.create({ - interneeId: studentUser._id, - internshipId: internshipRequest._id, - evaluations: [ - { - category: "Communication", - rating: "Satisfactory", - comment: "Excellent communication skills", - }, - ], - advisorSignature: { - type: "text", - value: "Dr.Smith", - }, - advisorAgreement: true, - coordinatorSignature: { - type: "text", - value: "Coordinator", - }, - coordinatorAgreement: true, - }); - - console.log("Dummy Data Inserted Successfully!"); - process.exit(0); - } catch (err) { - console.error(err); - process.exit(1); - } -}