From 186ca8007d57805e0bb66e05f4fada0c796c6c60 Mon Sep 17 00:00:00 2001 From: Doruk Ayhan Date: Wed, 16 Apr 2025 00:35:39 -0500 Subject: [PATCH 1/9] InternshipRequest.student is now sooner id instead of User reference --- client/src/pages/A1InternshipRequestForm.js | 4 ++-- server/models/InternshipRequest.js | 7 ++++--- server/routes/formRoutes.js | 4 ++++ server/services/insertData.js | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/client/src/pages/A1InternshipRequestForm.js b/client/src/pages/A1InternshipRequestForm.js index 81191c88..0b63a159 100644 --- a/client/src/pages/A1InternshipRequestForm.js +++ b/client/src/pages/A1InternshipRequestForm.js @@ -81,7 +81,7 @@ const A1InternshipRequestForm = () => { const validateForm = () => { const namePattern = /^[A-Za-z\s]+$/; - const numberPattern = /^[0-9]+$/; + const soonerIdPattern = /^[0-9]{9}$/; const phonePattern = /^[0-9]{10}$/; const emailPattern = /^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$/; @@ -91,7 +91,7 @@ const A1InternshipRequestForm = () => { else if (!namePattern.test(formData.interneeName)) newErrors.interneeName = "Name should contain only letters and spaces"; if (!formData.soonerId) newErrors.soonerId = "Sooner ID is required"; - else if (!numberPattern.test(formData.soonerId)) newErrors.soonerId = "Sooner ID should be numeric"; + else if (!soonerIdPattern.test(formData.soonerId)) newErrors.soonerId = "Sooner ID should be a 9-digit number"; if (!formData.interneeEmail) newErrors.interneeEmail = "Email is required"; else if (!emailPattern.test(formData.interneeEmail)) newErrors.interneeEmail = "Invalid email format"; diff --git a/server/models/InternshipRequest.js b/server/models/InternshipRequest.js index 3732f04d..df9ac416 100644 --- a/server/models/InternshipRequest.js +++ b/server/models/InternshipRequest.js @@ -13,10 +13,11 @@ const Task = new mongoose.Schema({ } }); const formA1 = new mongoose.Schema({ - student: { // get student's name, email, id from User - type: ObjectId, + student: { + type: Number, required: true, - ref: 'User' + min: [0, 'Sooner ID can\'t be negative'], + max: [999999999, 'Sooner ID should be a 9-digit number'] }, workplace: { name: { diff --git a/server/routes/formRoutes.js b/server/routes/formRoutes.js index c80f3ebe..b465f448 100644 --- a/server/routes/formRoutes.js +++ b/server/routes/formRoutes.js @@ -7,6 +7,7 @@ let status = ''; // Validate required fields function validateFormData(formData) { const requiredFields = [ + 'soonerId', 'workplaceName', 'website', 'phone', @@ -25,6 +26,9 @@ function validateFormData(formData) { } } + if (!/^[0-9]{9}$/.test(formData.soonerId)) + return `Sooner ID must be a 9-digit number, not ${formData.soonerId}`; + if (!Array.isArray(formData.tasks) || formData.tasks.length === 0) { return 'Tasks must be a non-empty array'; } diff --git a/server/services/insertData.js b/server/services/insertData.js index 7668f7c6..75c5d616 100644 --- a/server/services/insertData.js +++ b/server/services/insertData.js @@ -9,7 +9,7 @@ async function insertFormData(formData) { // Assumes global mongoose connection is already established elsewhere in app const formattedData = { - student: new mongoose.Types.ObjectId(), // TODO: Replace with actual signed-in student ID + student: parseInt(formData.soonerId), workplace: { name: formData.workplaceName, website: formData.website, From 3fa4ce5fa7353b4bfbfb46afee32ca83d3b4e291 Mon Sep 17 00:00:00 2001 From: nethra4321 <108627909+nethra4321@users.noreply.github.com> Date: Fri, 18 Apr 2025 00:52:56 -0500 Subject: [PATCH 2/9] saving changes --- client/src/pages/SupervisorDashboard.js | 124 +++++++++++++----------- client/src/pages/ViewFormModal.js | 88 ++++++++++++----- server/routes/formRoutes.js | 32 +++++- server/services/insertData.js | 4 + 4 files changed, 163 insertions(+), 85 deletions(-) diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 18cc054d..077cdeef 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -1,3 +1,4 @@ + import React, { useEffect, useState } from "react"; import axios from "axios"; import "../styles/SupervisorDashboard.css"; @@ -12,16 +13,31 @@ const SupervisorDashboard = () => { useEffect(() => { const fetchRequests = async () => { try { - const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/submissions/pending`); + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/form/internshiprequests`); + console.log("Fetched internship requests:", res.data); // debug log + + const formatted = res.data + .map(item => ({ + _id: item._id, + name: item.student?.userName || item.student?.name || "N/A", + student_id: item.student?._id || item._id, - setRequests(res.data); + form_type: "A1", + createdAt: item.createdAt, + supervisor_status: item.supervisor_status || "pending", + fullForm: item + })) + .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); // oldest first + + 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,7 +46,10 @@ 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)); @@ -43,63 +62,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..55cc551e 100644 --- a/client/src/pages/ViewFormModal.js +++ b/client/src/pages/ViewFormModal.js @@ -2,8 +2,8 @@ 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 [signature, setSignature] = useState(""); const [error, setError] = useState(""); const handleDecision = (action) => { @@ -11,34 +11,75 @@ const ViewFormModal = ({ formData, onClose, onAction }) => { setError("Comment is required before taking action."); return; } - setError(""); // clear error - onAction(formData._id, action, comment); + if (!signature.trim()) { + setError("Signature is required before approval/rejection."); + return; + } + + const payloadComment = `${comment.trim()} | Supervisor Signature: ${signature.trim()}`; + setError(""); + onAction(formData._id, action, payloadComment); }; return (
-

Form: {formData.form_type}

-

Student: {formData.name}

+

A.1 Internship Request Form

- {form.tasks && ( -
- Tasks: -
    {form.tasks.map((task, i) =>
  • {task}
  • )}
-
- )} + + + + + + + + + + + + + + + + + + + + + + + +
Student Name: {formData.interneeName}Sooner ID: {formData.soonerId}
Email: {formData.interneeEmail}Phone: {formData.phone}
Workplace Name: {formData.workplaceName}Website: {formData.website}
Advisor Name: {formData.advisorName}Advisor Email: {formData.advisorEmail}
Credit Hours: {formData.creditHours} + Start Date: {new Date(formData.startDate).toLocaleDateString()} +
+ End Date: {new Date(formData.endDate).toLocaleDateString()} +
- {form.outcomes && ( -
- Outcomes: -
    {form.outcomes.map((o, i) =>
  • {o}
  • )}
-
- )} - - {form.week &&

Week: {form.week}

} - {form.lessonsLearned &&

Lessons Learned: {form.lessonsLearned}

} +
+ Task Descriptions & Outcomes: +
    + {formData.tasks?.map((task, index) => ( +
  • + Task {index + 1}: {task.description} +
    + Outcomes: {task.outcomes?.join(", ") || "N/A"} +
  • + ))} +
+
+ + setSignature(e.target.value)} + placeholder="Enter your full name" + style={{ width: "100%", padding: "6px", marginTop: "5px", borderRadius: "4px" }} + /> +
+ +