diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx
new file mode 100644
index 00000000..276bbb65
--- /dev/null
+++ b/client/src/pages/StudentDashboard.jsx
@@ -0,0 +1,141 @@
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import "../styles/StudentDashboard.css"; // Make sure you create this CSS
+
+const StudentDashboard = () => {
+ const navigate = useNavigate();
+
+ const user = JSON.parse(localStorage.getItem("ipmsUser"));
+ const ouEmail = user?.email;
+ const [approvalStatus, setApprovalStatus] = useState("not_submitted");
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const res = await fetch(`${process.env.REACT_APP_API_URL}/api/student`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ ouEmail }),
+ });
+
+ const data = await res.json();
+ setApprovalStatus(data.approvalStatus);
+ } catch (err) {
+ console.error("Error fetching internship data", err);
+ }
+ };
+
+ if (ouEmail) {
+ fetchData();
+ }
+ }, [ouEmail]);
+ console.log(approvalStatus);
+
+ return (
+
+
+
Welcome, {user.fullName}
+
+
+
+ {/* ------ FORM A1 Card ------ */}
+
+
+
Request Internship (FORM A1)
+
Track your internship journey
+
+ {approvalStatus === "not_submitted" && (
+
+ You have not submitted the form yet
+
+ )}
+
+ {(approvalStatus === "submitted" ||
+ approvalStatus === "pending manual review") && (
+
+ Your form is submitted and under review
+
+ )}
+
+ {approvalStatus === "approved" && (
+
Approved
+ )}
+
+
+
+
+
+ {/* ------ FORM A2 Card ------ */}
+
+
+
Weekly Report (Form A2)
+
+ {approvalStatus === "not_submitted" && (
+
+ Please fill your Form A1 first
+
+ )}
+
+ {approvalStatus === "draft" && (
+
+ Finish your Form A1 first
+
+ )}
+
+ {(approvalStatus === "submitted" ||
+ approvalStatus === "pending manual review") && (
+
+ Wait for your Form A1 to be approved
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default StudentDashboard;
diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js
index d4a786a0..3609375b 100644
--- a/client/src/pages/SupervisorDashboard.js
+++ b/client/src/pages/SupervisorDashboard.js
@@ -8,8 +8,57 @@ const SupervisorDashboard = () => {
const [selectedForm, setSelectedForm] = useState(null);
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState("");
-
- const token = localStorage.getItem("token") || "";
+
+ useEffect(() => {
+
+ // Token used for authentication for future
+ // Now it will only be empty
+ const token = localStorage.getItem("token") || "";
+
+ const fetchRequests = async () => {
+ try {
+ const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/supervisor/forms`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ const formatted = res.data.map(item => ({
+ _id: item._id,
+ name: item.student_id?.userName || item.student_id?.name || "N/A",
+ student_id: item.student?._id || item._id,
+ form_type: item.form_type,
+ createdAt: item.createdAt,
+ supervisor_status: item.supervisor_status || "pending",
+ fullForm: item,
+ workplace: {
+ name: item.workplace?.name || "N/A",
+ website: item.workplace?.website || "N/A",
+ phone: item.workplace?.phone || "N/A",
+ },
+ internshipAdvisor: {
+ name: item.internshipAdvisor?.name || "N/A",
+ jobTitle: item.internshipAdvisor?.jobTitle || "N/A",
+ email: item.internshipAdvisor?.email || "N/A",
+ },
+ creditHours: item.creditHours || 0,
+ startDate: item.startDate || "N/A",
+ endDate: item.endDate || "N/A",
+ tasks: item.tasks || [],
+ status: item.status || "pending",
+ supervisor_comment: item.supervisor_comment || "N/A"
+ }));
+
+
+ setRequests(formatted);
+ setLoading(false);
+ } catch (err) {
+ console.error("Error fetching forms:", err);
+ setMessage("Error fetching forms.", err);
+ setLoading(false);
+ }
+ };
const fetchRequests = async () => {
try {
@@ -63,75 +112,93 @@ const SupervisorDashboard = () => {
);
setMessage(res.data.message || `${action} successful`);
- setRequests((prev) => prev.filter((req) => req._id !== id));
- setSelectedForm(null);
+ setRequests(prev => prev.filter(req => req._id !== id)); // remove from table
+ return true;
} catch (err) {
console.error(`Failed to ${action} request:`, err);
setMessage(`Failed to ${action} request.`);
+ return false;
}
};
+
const openFormView = (form) => setSelectedForm(form);
const closeFormView = () => setSelectedForm(null);
const formatDate = (date) => new Date(date).toLocaleDateString();
- let content;
+ const sortedRequests = [...requests]
+ .filter((res) => res.supervisor_status?.toLowerCase() === "pending")
+ .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
+
+ let content;
- if (loading) {
- content =
Loading...
;
- } else if (requests.length === 0) {
- content = (
-
-
No pending approvals.
-
- );
- } else {
- content = (
-
-
-
- | Student Name |
- Student ID |
- Form Type |
- Date Submitted |
- Status |
-
-
-
- {requests.map((req) => (
- openFormView(req.fullForm)}>
- | {req.name} |
- {req.student_id} |
- {req.form_type} |
- {formatDate(req.createdAt)} |
-
-
- {req.supervisor_status}
-
- |
+
+ if (loading) {
+ content = Loading...
;
+ } else if (sortedRequests.length === 0) {
+ content = (
+
+
No pending approvals.
+
+ );
+ } else {
+ content = (
+
+
+
+ | Student Name |
+ Sooner ID |
+ Student Email |
+ Form Type |
+ Submitted |
+ Status |
- ))}
-
-
+
+
+ {sortedRequests.map((req) => {
+ console.log(req); // Log the entire request object
+ console.log(req.Name); // Log the student's full name if populated
+ return (
+
+ | {req.interneeName || "N/A"} |
+
+
+ |
+ {req.interneeEmail || req.ouEmail || "N/A"} |
+ {req.form_type} |
+ {formatDate(req.createdAt)} |
+
+
+ {req.supervisor_status || req.status}
+
+ |
+
+ );
+ })}
+
+
+ );
+ }
+
+ return (
+
+
Supervisor Dashboard
+ {message &&
{message}
}
+ {content}
+ {selectedForm && (
+
+ handleAction(selectedForm.form_type, id, action, comment, signature)
+ }
+ />
+ )}
+
);
- }
-
- return (
-
-
Supervisor Dashboard
- {message &&
{message}
}
- {content}
- {selectedForm && (
-
- )}
-
- );
};
export default SupervisorDashboard;
diff --git a/client/src/pages/TokenRenewal.jsx b/client/src/pages/TokenRenewal.jsx
new file mode 100644
index 00000000..8db7edeb
--- /dev/null
+++ b/client/src/pages/TokenRenewal.jsx
@@ -0,0 +1,59 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+
+const TokenRenewal = () => {
+ const { token } = useParams();
+ const [responseMessage, setResponseMessage] = useState('');
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const renewToken = async () => {
+ try {
+ const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/renew`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ token }),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ setResponseMessage({ text: '✅ Success: Your token has been renewed!', success: true });
+ } else {
+ setResponseMessage({ text: `❌ Error: ${data.message}`, success: false });
+ }
+ } catch (error) {
+ setResponseMessage({ text: '❌ Error: Unable to process your request.', success: false });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (token) {
+ renewToken();
+ } else {
+ setResponseMessage({ text: '❌ Error: No token found in the URL.', success: false });
+ setLoading(false);
+ }
+ }, [token]);
+
+ return (
+
+
Token Renewal
+ {loading ? (
+
⏳ Processing your token renewal...
+ ) : (
+
+ {responseMessage.text}
+
+ )}
+
+ );
+};
+
+export default TokenRenewal;
diff --git a/client/src/pages/ViewFormModal.js b/client/src/pages/ViewFormModal.js
index f8a467b9..ffb19bd6 100644
--- a/client/src/pages/ViewFormModal.js
+++ b/client/src/pages/ViewFormModal.js
@@ -5,47 +5,32 @@ const ViewFormModal = ({ formData, onClose, onAction, onActionComplete }) => {
const [comment, setComment] = useState("");
const [signature, setSignature] = useState("");
const [error, setError] = useState("");
-
- const handleAction = async (action) => {
- if (!comment.trim()) {
- setError("Comment is required before taking action.");
- return;
- }
- if (!signature.trim()) {
- setError("Signature is required before approval/rejection.");
- return;
- }
-
+ const handleDecision = (action) => {
+ if (!comment.trim()) return setError("Comment is required.");
+ if (!signature.trim()) return setError("Signature is required.");
setError("");
- const fullComment = `${comment.trim()} | Supervisor Signature: ${signature.trim()}`;
- await onAction(formData._id, formData.form_type, action, fullComment);
- onActionComplete(); // refresh table
- onClose(); // close modal
+ onAction(formData._id, action, comment.trim(), signature.trim());
};
- return (
-
-
-
A.1 Internship Request Form
-
-
+ // ✅ Inserted rendering helpers
+ const renderA1 = () => (
+ <>
+ A1 – Internship Request Form
+
- | Field |
- Details |
+ |
+ |
- | Student Name: {formData.student?.userName || formData.student?.name || "N/A"} |
- Student ID: {formData.student?._id || "N/A"} |
-
-
- | Email: {formData.student?.email || "N/A"} |
- Phone: {formData.workplace?.phone || "N/A"} |
+ Student Name: {formData.interneeName || "N/A"} |
+ Email: {formData.interneeEmail || "N/A"} |
| Workplace Name: {formData.workplace?.name || "N/A"} |
+ Phone: {formData.workplace?.phone || "N/A"} |
Website: {formData.workplace?.website || "N/A"} |
@@ -74,39 +59,71 @@ const ViewFormModal = ({ formData, onClose, onAction, onActionComplete }) => {
))}
+ >
+ );
-
-
- setSignature(e.target.value)}
- placeholder="Enter your full name"
- style={{ width: "100%", padding: "6px", marginTop: "5px", borderRadius: "4px" }}
- />
-
+ const renderA3 = () => (
+ <>
+ A3 – Final Job Performance Evaluation
+ Name: {formData.interneeName}
+ Email: {formData.interneeEmail}
-
-
-
+ Evaluation Items
+
+
+
+ | Category |
+ Rating |
+ Comment |
+
+
+
+ {formData.evaluations?.map((item, i) => (
+
+ | {item.category} |
+ {item.rating} |
+ {item.comment || "-"} |
+
+ ))}
+
+
+ >
+ );
- {error && {error}
}
+ const renderSignaturesAndActions = () => (
+ <>
+ Supervisor Review
+
+ setSignature(e.target.value)}
+ style={{ width: "100%", marginBottom: "8px" }}
+ />
+
+