diff --git a/client/src/pages/A1InternshipRequestForm.js b/client/src/pages/A1InternshipRequestForm.js index 82950c9c..a0af21f7 100644 --- a/client/src/pages/A1InternshipRequestForm.js +++ b/client/src/pages/A1InternshipRequestForm.js @@ -263,12 +263,41 @@ const A1InternshipRequestForm = ({ userRole = "student" }) => { const submitFormData = async () => { try { + // Fetch logged-in student ID from localStorage or context + const studentId = localStorage.getItem("studentId"); // You must store this during login + + const payload = { + student: studentId, + workplace: { + name: formData.workplaceName, + website: formData.website, + phone: formData.phone + }, + internshipAdvisor: { + name: formData.advisorName, + jobTitle: formData.advisorJobTitle, + email: formData.advisorEmail + }, + creditHours: parseInt(formData.creditHours), + startDate: formData.startDate, + endDate: formData.endDate, + tasks: formData.tasks, + status: "submitted", + approvals: ["advisor"] + }; + const response = await fetch(`${process.env.REACT_APP_API_URL}/api/form/submit`, { method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(formData), + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(payload) }); - if (!response.ok) throw new Error("Failed to submit form"); + + if (!response.ok) { + throw new Error("Failed to submit form", { cause: response }); + } + const data = await response.json(); return data; } catch (error) { @@ -276,6 +305,7 @@ const A1InternshipRequestForm = ({ userRole = "student" }) => { throw error; } }; + const sendTaskDescriptions = async (descriptions) => { try { diff --git a/client/src/pages/CoordinatorDashboard.js b/client/src/pages/CoordinatorDashboard.js index 27650c70..70f2fc31 100644 --- a/client/src/pages/CoordinatorDashboard.js +++ b/client/src/pages/CoordinatorDashboard.js @@ -1,19 +1,33 @@ import React, { useEffect, useState } from "react"; -import axios from "axios"; import { useNavigate } from "react-router-dom"; -import "../styles/SupervisorDashboard.css"; +import axios from "axios"; +import "../styles/SupervisorDashboard.css"; + const CoordinatorDashboard = () => { - const [activeTab, setActiveTab] = useState("requests"); // 'requests' or 'reports' + const [activeTab, setActiveTab] = useState("requests"); const navigate = useNavigate(); - // TEAM A's Internship Requests Logic + + // Internship Requests (Form A1) const [requests, setRequests] = useState([]); const [loadingRequests, setLoadingRequests] = useState(true); - // == INTERNSHIP REQUESTS (FORM A1) == + // Weekly Reports (Form A2) + const [reports, setReports] = useState([]); + const [loadingReports, setLoadingReports] = useState(true); + + // Job Evaluations (Form A3) + const [evaluations, setEvaluations] = useState([]); + const [loadingEvaluations, setLoadingEvaluations] = useState(true); + + // Manual Reviews (Failed A1) + const [manualReviews, setManualReviews] = useState([]); + const [loadingManualReviews, setLoadingManualReviews] = useState(true); + useEffect(() => { - if (activeTab === "requests") { - fetchInternshipRequests(); - } + if (activeTab === "requests") fetchInternshipRequests(); + if (activeTab === "reports") fetchWeeklyReports(); + if (activeTab === "evaluations") fetchEvaluations(); + if (activeTab === "manualReviews") fetchManualReviews(); }, [activeTab]); const fetchInternshipRequests = async () => { @@ -27,25 +41,10 @@ const CoordinatorDashboard = () => { } }; - // == WEEKLY REPORT (FORM A2) == - - const [reportGroups, setReportGroups] = useState([]); - const [loadingReports, setLoadingReports] = useState(true); - - useEffect(() => { - if (activeTab === "reports") { - fetchReportGroups(); - } - }, [activeTab]); - - const fetchReportGroups = async () => { + const fetchWeeklyReports = async () => { try { - const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/reports/supervised-groups`); - const filtered = res.data?.groups?.filter(group => { - const key = `coordinator_reviewed_${group.groupIndex}`; - return !localStorage.getItem(key); - }); - setReportGroups(filtered || []); + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/coordinator/reports`); + setReports(res.data.reports || []); } catch (err) { console.error("Error fetching reports:", err); } finally { @@ -53,14 +52,28 @@ const CoordinatorDashboard = () => { } }; - const handleReviewClick = (group) => { - localStorage.setItem(`coordinator_reviewed_${group.groupIndex}`, "true"); - navigate(`/review-cumulative/${group.groupIndex}`); + const fetchEvaluations = async () => { + try { + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/coordinator/evaluations`); + setEvaluations(res.data || []); + } catch (err) { + console.error("Error fetching evaluations:", err); + } finally { + setLoadingEvaluations(false); + } + }; + + const fetchManualReviews = async () => { + try { + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/coordinator/manual-review-a1`); + setManualReviews(res.data || []); + } catch (err) { + console.error("Error fetching manual review forms:", err); + } finally { + setLoadingManualReviews(false); + } }; - - // Render UI - return (

Coordinator Dashboard

@@ -68,29 +81,33 @@ const CoordinatorDashboard = () => { {/* Tabs */}
- + + +
- {/* Tab: Internship Requests */} + {/* Internship Requests */} {activeTab === "requests" && ( <> - {loadingRequests ?

Loading...

: ( + {loadingRequests ?

Loading requests...

: ( - + - {requests.map(req => ( + {requests.map((req) => ( - - - - + + + + ))} @@ -99,23 +116,81 @@ const CoordinatorDashboard = () => { )} - {/* Tab: Weekly Reports Review */} + {/* Weekly Reports */} {activeTab === "reports" && ( <> {loadingReports ?

Loading reports...

: ( - reportGroups.length === 0 - ?

No reports to review

- : reportGroups.map(group => ( -
-

Weeks: {group.weeks?.join(", ")}

- - + reports.length === 0 ?

No reports to review

: ( + reports.map((report) => ( +
+

Week: {report.week}

+

Hours: {report.hours}

+

Tasks: {report.tasks}

)) + ) + )} + + )} + + {/* Job Evaluations */} + {activeTab === "evaluations" && ( + <> + {loadingEvaluations ?

Loading evaluations...

: ( + evaluations.length === 0 ?

No evaluations pending

: ( +
Student NameStudent ID Company StatusAction
{req.student.studentName}{req.student_id}{req.workplace.name}{req.coordinator_status}{req.student?.name || "-"}{req.workplace?.name || "-"}{req.coordinator_status || "-"} + +
+ + + + + + + + + {evaluations.map((evalItem) => ( + + + + + + ))} + +
Internee NameInternee EmailAction
{evalItem.interneeName}{evalItem.interneeEmail} + +
+ ) + )} + + )} + + {/* Manual Reviews */} + {activeTab === "manualReviews" && ( + <> + {loadingManualReviews ?

Loading manual reviews...

: ( + manualReviews.length === 0 ?

No manual review forms.

: ( + + + + + + + + + + + {manualReviews.map((form) => ( + + + + + + + ))} + +
Student NameEmailCompanyActions
{form.student?.userName || "N/A"}{form.student?.email || "N/A"}{form.workplace?.name || "N/A"} + +
+ ) )} )} diff --git a/client/src/pages/CoordinatorEvaluationReview.jsx b/client/src/pages/CoordinatorEvaluationReview.jsx new file mode 100644 index 00000000..81b57616 --- /dev/null +++ b/client/src/pages/CoordinatorEvaluationReview.jsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import axios from "axios"; +import "../styles/CoordinatorRequestDetailView.css"; // Reuse styles + +const CoordinatorEvaluationReview = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const [evaluation, setEvaluation] = useState(null); + + useEffect(() => { + const fetchEvaluationDetails = async () => { + try { + const res = await axios.get( + `${process.env.REACT_APP_API_URL}/api/coordinator/evaluations` + ); + const matchedEvaluation = res.data.find((form) => form._id === id); + setEvaluation(matchedEvaluation || null); + } catch (err) { + console.error("Error fetching evaluation form:", err); + } + }; + + fetchEvaluationDetails(); + }, [id]); + + const handleApprove = async () => { + try { + const res = await axios.post( + `${process.env.REACT_APP_API_URL}/api/coordinator/evaluation/${id}/approve` + ); + alert(res.data.message); + navigate("/coordinator-dashboard"); + } catch (err) { + console.error("Error approving evaluation:", err); + alert("Error approving evaluation form."); + } + }; + + const handleReject = async () => { + const reason = prompt("Please enter a reason for rejection:"); + if (!reason) { + alert("Rejection reason is required!"); + return; + } + try { + const res = await axios.post( + `${process.env.REACT_APP_API_URL}/api/coordinator/evaluation/${id}/reject`, + { reason } + ); + alert(res.data.message); + navigate("/coordinator-dashboard"); + } catch (err) { + console.error("Error rejecting evaluation:", err); + alert("Error rejecting evaluation form."); + } + }; + + if (!evaluation) return

Loading evaluation details...

; + + return ( +
+

Job Evaluation (Form A3) Review

+ +
+

Internee Name: {evaluation.interneeName}

+

Internee Email: {evaluation.interneeEmail}

+ +

Evaluation Categories

+ + + + + + + + + + {evaluation.evaluations.map((item, idx) => ( + + + + + + ))} + +
CategoryRatingComments
{item.category}{item.rating}{item.comment || "N/A"}
+ +
+ + + +
+
+
+ ); +}; + +export default CoordinatorEvaluationReview; diff --git a/client/src/pages/CoordinatorManualReviewView.js b/client/src/pages/CoordinatorManualReviewView.js new file mode 100644 index 00000000..50a9ed31 --- /dev/null +++ b/client/src/pages/CoordinatorManualReviewView.js @@ -0,0 +1,90 @@ +import React, { useEffect, useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import axios from "axios"; +import "../styles/CoordinatorRequestDetailView.css"; // Reuse same styling + +const CoordinatorManualReviewView = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const [formData, setFormData] = useState(null); + + useEffect(() => { + const fetchManualReviewForm = async () => { + try { + const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/coordinator/manual-review-a1`); + const matchedForm = res.data.find((form) => form._id === id); + setFormData(matchedForm || null); + } catch (err) { + console.error("Error fetching manual review form:", err); + } + }; + fetchManualReviewForm(); + }, [id]); + + const handleApprove = async () => { + try { + const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/coordinator/manual-review-a1/${id}/approve`); + alert(res.data.message); + navigate("/coordinator-dashboard"); + } catch (err) { + console.error("Approval failed:", err); + alert("Error approving manual review form."); + } + }; + + const handleReject = async () => { + const reason = prompt("Please enter a reason for rejection:"); + if (!reason) { + alert("Rejection reason is required!"); + return; + } + try { + const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/coordinator/manual-review-a1/${id}/reject`, { reason }); + alert(res.data.message); + navigate("/coordinator-dashboard"); + } catch (err) { + console.error("Rejection failed:", err); + alert("Error rejecting manual review form."); + } + }; + + if (!formData) return

Loading form details...

; + + return ( +
+

Manual Review (Failed A1 Form)

+ +
+

Student: {formData.student?.userName || "N/A"}

+

Email: {formData.student?.email || "N/A"}

+

Company: {formData.workplace?.name || "N/A"}

+ +

Tasks & CS Outcomes

+ + + + + + + + + {formData.tasks.map((task, idx) => ( + + + + + ))} + +
TaskOutcomes
{task.description}{task.outcomes?.join(", ") || "N/A"}
+ +
+ + + +
+
+
+ ); +}; + +export default CoordinatorManualReviewView; diff --git a/client/src/pages/CoordinatorRequestDetailView.js b/client/src/pages/CoordinatorRequestDetailView.js index 8c8a12f5..f9e6827e 100644 --- a/client/src/pages/CoordinatorRequestDetailView.js +++ b/client/src/pages/CoordinatorRequestDetailView.js @@ -6,14 +6,24 @@ import "../styles/CoordinatorRequestDetailView.css"; const CoordinatorRequestDetailView = () => { const { id } = useParams(); const navigate = useNavigate(); - const [data, setData] = useState(null); + const [requestData, setRequestData] = useState(null); + const [supervisorStatus, setSupervisorStatus] = 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]); + const fetchRequestDetails = async () => { + try { + const res = await axios.get( + `${process.env.REACT_APP_API_URL}/api/coordinator/request/${id}` + ); + setRequestData(res.data.requestData); + setSupervisorStatus(res.data.supervisorStatus); + } catch (err) { + console.error("Error fetching request details:", err); + } + }; + + fetchRequestDetails(); + }, [id]); // ✅ Only depends on id const handleApprove = async () => { try { @@ -24,14 +34,16 @@ const CoordinatorRequestDetailView = () => { navigate("/coordinator-dashboard"); } catch (err) { console.error("Approval failed:", err); - alert("Error approving request."); + alert("Error approving the request."); } }; const handleReject = async () => { const reason = prompt("Please enter a reason for rejection:"); - if (!reason) return alert("Rejection reason required!"); - + if (!reason) { + alert("Rejection reason is required!"); + return; + } try { const res = await axios.post( `${process.env.REACT_APP_API_URL}/api/coordinator/request/${id}/reject`, @@ -41,31 +53,21 @@ const CoordinatorRequestDetailView = () => { navigate("/coordinator-dashboard"); } catch (err) { console.error("Rejection failed:", err); - alert("Error rejecting request."); + alert("Error rejecting the request."); } }; - if (!data) return

Loading...

; - - const { requestData, supervisorStatus } = data; + if (!requestData) return

Loading request details...

; return (

Internship Request Details

-

- Student: {requestData.student.userName} -

-

- Email: {requestData.student.email} -

-

- Company: {requestData.workplace.name} -

-

- Supervisor Status: {supervisorStatus} -

+

Student: {requestData.student?.name || "N/A"}

+

Email: {requestData.student?.email || "N/A"}

+

Company: {requestData.workplace?.name || "N/A"}

+

Supervisor Status: {supervisorStatus}

Tasks & CS Outcomes

@@ -79,25 +81,16 @@ const CoordinatorRequestDetailView = () => { {requestData.tasks.map((task, idx) => ( - + ))}
{task.description}{task.outcomes.join(", ")}{task.outcomes?.join(", ") || "N/A"}
-
- - - +
+ + +
diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index a190cf5d..ed96bafb 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import "../styles/App.css"; import { FaEnvelope, FaLock, FaEye, FaEyeSlash } from "react-icons/fa"; @@ -13,31 +13,22 @@ function Home() { const [formData, setFormData] = useState({ email: "", password: "", - role: "student", }); const [showPassword, setShowPassword] = useState(false); - const [role] = useState("student"); - - // Sync role into formData.role - useEffect(() => { - setFormData((prev) => ({ ...prev, role })); - }, [role]); const handleInputChange = (e) => { const { name, value } = e.target; - setFormData({ - ...formData, + setFormData((prev) => ({ + ...prev, [name]: value, - }); + })); }; const handleSubmit = async (e) => { e.preventDefault(); - const { email: ouEmail, password, role } = formData; - if (!ouEmail || !password || !role) { return Swal.fire({ icon: "warning", @@ -55,46 +46,45 @@ function Home() { "Content-Type": "application/json", }, body: JSON.stringify({ ouEmail, password, role }), - }, + } ); const data = await response.json(); if (response.ok) { const user = data.user; - if(role === "student"){ - // Store only required fields - const limitedUserInfo = { - fullName: user.fullName, - id: user._id, - email:user.ouEmail - }; - - localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo)); - navigate("/student-dashboard"); - }else if(role === "supervisor"){ + if (role === "student") { + const limitedUserInfo = { + fullName: user.fullName, + id: user._id, + email: user.ouEmail, + }; + localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo)); + navigate("/student-dashboard"); + } else if (role === "supervisor") { Swal.fire({ icon: "success", title: "Login Successful 🌟", - text: `Welcome back, ${role}!`, + text: `Welcome back, Supervisor!`, }); navigate("/supervisor-dashboard"); - } else{ + } else if (role === "coordinator") { Swal.fire({ icon: "success", title: "Login Successful 🌟", - text: `Welcome back, ${role}!`, + text: `Welcome back, Coordinator!`, }); navigate("/coordinator-dashboard"); - } + } } else { Swal.fire({ icon: "error", title: "Login Failed", - html: data.message + " " + - (data.renewalLink - ? `Please click here to request a new token.` - : "Something went wrong."), + html: + data.message + + (data.renewalLink + ? ` Please click here to request a new token.` + : " Something went wrong."), }); } } catch (error) { @@ -136,27 +126,13 @@ function Home() { formData.role === r ? "selected" : "" }`} onClick={() => - setFormData({ - ...formData, - role: r, - }) + setFormData((prev) => ({ ...prev, role: r })) } - >

{r.charAt(0).toUpperCase() + r.slice(1)}

-
))}
@@ -206,15 +182,7 @@ function Home() { -
+
+ {/* Submissions Table */}

Your Internship Submissions

@@ -196,11 +190,11 @@ const StudentDashboard = () => { {submissions.map((req) => ( - {req.workplace.name} + {req.workplace?.name || "-"} {req.status} {new Date(req.createdAt).toLocaleDateString()} - {req.reminders?.length === 2 && !req.coordinator_responded ? ( + {req.reminders?.length === 2 && !req.coordinatorResponded ? ( <>