diff --git a/client/src/pages/CoordinatorCumulativeReviewForm.js b/client/src/pages/CoordinatorCumulativeReviewForm.js index 0dd496df..f96ecf86 100644 --- a/client/src/pages/CoordinatorCumulativeReviewForm.js +++ b/client/src/pages/CoordinatorCumulativeReviewForm.js @@ -47,6 +47,7 @@ const CoordinatorCumulativeReviewForm = () => { await axios.post(`${process.env.REACT_APP_API_URL}/api/reports/coordinator-comments`, { groupIndex: parseInt(groupIndex), + coordinator_status: "approved", comments: coordinatorComment.trim(), weeks, }); @@ -60,7 +61,28 @@ const CoordinatorCumulativeReviewForm = () => { Swal.fire("Error", "Failed to submit comment. Please try again.", "error"); } }; + + // Submit coordinator coordinator rejction + const handleReject = async () => { + try { + const weeks = reports.map((report) => report.week); // extract weeks + + await axios.post(`${process.env.REACT_APP_API_URL}/api/reports/coordinator-comments`, { + groupIndex: parseInt(groupIndex), + coordinator_status: "rejected", + comments: "", + weeks, + }); + + Swal.fire("Success", "Coordinator rejected weekly update FORM A2.", "success"); + localStorage.setItem("reviewedGroupIndex", groupIndex); // ✅ For dashboard refresh + navigate("/coordinator-dashboard"); + } catch (err) { + console.error("[CoordinatorCumulativeReviewForm] Failed to reject weekly report", err); + Swal.fire("Error", "Failed to reject weekly report. Please try again.", "error"); + } + }; if (loading) { return

Loading...

; @@ -108,6 +130,9 @@ const CoordinatorCumulativeReviewForm = () => {
+ diff --git a/client/src/pages/CoordinatorDashboard.js b/client/src/pages/CoordinatorDashboard.js index ed712155..d33c06c5 100644 --- a/client/src/pages/CoordinatorDashboard.js +++ b/client/src/pages/CoordinatorDashboard.js @@ -1,18 +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); + // 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 () => { @@ -25,25 +40,11 @@ const CoordinatorDashboard = () => { setLoadingRequests(false); } }; - // Group D's Weekly Report Review Logic - 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 { @@ -51,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

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

Loading...

: ( + {loadingRequests ?

Loading requests...

: ( - + - {requests.map(req => ( + {requests.map((req) => ( - - - - + + + + ))} @@ -97,23 +116,84 @@ 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(", ")}

-
    - {group.reports.map((r, i) => ( -
  • Week {r.week} — Hours: {r.hours} — Tasks: {r.tasks}
  • - ))} -
- + 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.studentName}{req.studentId}{req.companyName}{req.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 5f27617b..4b4912f0 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,28 +13,20 @@ 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) { @@ -62,49 +54,50 @@ function Home() { 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, + academicAdvisor: user.academicAdvisor, semester: user.semester, }; localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo)); localStorage.setItem("ouEmail", user.ouEmail); + 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 { + Swal.fire({ icon: "success", title: "Login Successful 🌟", - text: `Welcome back, ${role}!`, + text: `Welcome back, Coordinator!`, }); navigate("/coordinator-dashboard"); } - // Swal.fire({ - // icon: "success", - // title: "Login Successful", - // text: `Welcome back, `, - // }); } else { Swal.fire({ icon: "error", title: "Login Failed", html: data.message + + " " + (data.renewalLink ? `Please click here to request a new token.` : "Something went wrong."), + }); } } catch (error) { @@ -146,10 +139,7 @@ function Home() { formData.role === r ? "selected" : "" }`} onClick={() => - setFormData({ - ...formData, - role: r, - }) + setFormData((prev) => ({ ...prev, role: r })) } > @@ -205,15 +195,7 @@ function Home() {
-
+