Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a960c4d
A1 form Completed
Nanvithaa Apr 19, 2025
a002be9
Merge branch 'GroupE-development' into GroupE-frontend
Nanvithaa Apr 19, 2025
df90b23
Removed SoonerID
MRPHFitch Apr 23, 2025
b38d274
Merge branch 'GroupE-frontend' into GroupE-development
MRPHFitch Apr 23, 2025
17c7efd
Merge pull request #143 from IPMS-Project/GroupE-development
MRPHFitch Apr 23, 2025
e57d7d9
All forms displayed
MRPHFitch Apr 23, 2025
d2e7939
Week calculation for length of internship
MRPHFitch Apr 23, 2025
f11f8d5
Establishing RBAC
MRPHFitch Apr 23, 2025
6d14559
Adjusting layout of forms
MRPHFitch Apr 23, 2025
921e8d4
Merge branch 'GroupE-frontend' into GroupE/frontend
MRPHFitch Apr 25, 2025
7b05f67
Merge pull request #152 from IPMS-Project/GroupE/frontend
MRPHFitch Apr 25, 2025
9d056a7
Fixed server and router issue
MRPHFitch Apr 25, 2025
ca3e9fc
Removed SoonerID & fixed Controller issue
MRPHFitch Apr 25, 2025
5056c4f
Fixed Errors in order to npm start
MRPHFitch Apr 25, 2025
8cc098f
Merge branch 'GroupE-frontend' into GroupE/frontend
MRPHFitch Apr 25, 2025
da147f0
Merge pull request #153 from IPMS-Project/GroupE/frontend
MRPHFitch Apr 25, 2025
f4a83bb
Pulled main again and fixed issues
MRPHFitch Apr 25, 2025
cc74e7a
Updated dependencies for UseEffect
MRPHFitch Apr 25, 2025
7aea5aa
Group D's changes suck
MRPHFitch Apr 26, 2025
8691d9f
Backend correct get forms, reminder
HozenDev Apr 26, 2025
0def169
Merge branch 'GroupE-frontend' of github.com:IPMS-Project/IPMS into G…
HozenDev Apr 26, 2025
6eb2dee
Better A1 Render, Correct Email Reminder DB save, Redirection
HozenDev Apr 26, 2025
6259f5a
Correct A3 disapear on handle A1 form of same student
HozenDev Apr 26, 2025
d9e8f81
Merge GroupE-backend branch & correct fix A3 disappear after handle A…
HozenDev Apr 26, 2025
63f376c
Update with main
HozenDev Apr 27, 2025
1d4d4a3
Update router.js
Nanvithaa Apr 28, 2025
8cfa53c
Fixed the last N/A issues
MRPHFitch Apr 28, 2025
7f0fc7f
Merge branch 'GroupE-development' of https://github.com/IPMS-Project/…
MRPHFitch Apr 28, 2025
74dc239
Fixing Errors
MRPHFitch Apr 28, 2025
905177d
New Error Fix
MRPHFitch Apr 28, 2025
14f2253
mpor React from "react";
Nanvithaa Apr 28, 2025
02bb2ed
updated the viewfor modal Merge branch 'GroupE-development' of https:…
Nanvithaa Apr 28, 2025
b212d5a
Resetting fixes that were undone
MRPHFitch Apr 28, 2025
fefb7e3
Update token.js
Nanvithaa Apr 29, 2025
10bf16e
Merge branch 'main' into GroupE-development
sanjay347 Apr 29, 2025
348741e
correct retrieve information from A1
HozenDev Apr 29, 2025
5a26a13
Updated with main origin
HozenDev Apr 29, 2025
52e31b0
Merge branch 'main' into GroupE-development
Nanvithaa Apr 29, 2025
933f2ce
Correct bad approval / reject for A1
HozenDev Apr 29, 2025
54bb08c
Merge modification
HozenDev Apr 29, 2025
8109df9
Add Supervisor Protection
HozenDev Apr 29, 2025
7f38708
Correct A3 forms filter and info retrieve
HozenDev Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions client/src/pages/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,25 @@ function Home() {
semester: user.semester,
};

localStorage.clear();
localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo));
localStorage.setItem("ouEmail", user.ouEmail);
navigate("/student-dashboard");
} else if (role === "supervisor") {
// Store only required fields
const limitedUserInfo = {
role: role,
};
const token = data.user.token;

localStorage.clear();
localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo));
localStorage.setItem("token", token);

Swal.fire({
icon: "success",
title: "Login Successful 🌟",
text: `Welcome back, ${role}!`,
icon: "success",
title: "Login Successful 🌟",
text: `Welcome back, ${role}!`,
});
navigate("/supervisor-dashboard");
} else {
Expand Down Expand Up @@ -249,4 +260,4 @@ function Home() {
);
}

export default Home;
export default Home;
9 changes: 9 additions & 0 deletions client/src/pages/ProtectedSupervisor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";
import { Navigate } from "react-router-dom";

const ProtectedSupervisor = ({ children }) => {
const user = JSON.parse(localStorage.getItem("ipmsUser"));
return user && user.role === "supervisor" ? children : <Navigate to="/" />;
};

export default ProtectedSupervisor;
194 changes: 104 additions & 90 deletions client/src/pages/SupervisorDashboard.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import React, { useEffect, useState } from "react";
import axios from "axios";
import "../styles/SupervisorDashboard.css";
Expand All @@ -9,28 +10,25 @@ const SupervisorDashboard = () => {
const [selectedForm, setSelectedForm] = useState(null);
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState("");

useEffect(() => {
const token = localStorage.getItem("token") || "";

const fetchRequests = async () => {
const token = localStorage.getItem("token") || "";

useEffect(() => {
const fetchRequests = async () => {
try {
const res = await axios.get(
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/supervisor/forms`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
});

const formatted = res.data.map((item) => ({
const formatted = response.data.map(item => ({
_id: item._id,
interneeName: item.interneeName || item.student_id?.userName || "N/A",
soonerId: item.soonerId || item.student_id?.soonerId || "N/A",
interneeEmail: item.interneeEmail || item.student_id?.email || "N/A",
interneeName: item.student?.name || item.studentId?.fullName || item.interneeName || "N/A",
interneeEmail: item.student?.email || item.studentId?.ouEmail || item.interneeEmail || "N/A",
form_type: item.form_type,
createdAt: item.createdAt,
createdAt: item.createdAt || item.submittedAt,
supervisor_status: item.supervisor_status || "pending",
fullForm: item,
workplace: {
Expand All @@ -48,27 +46,44 @@ const SupervisorDashboard = () => {
endDate: item.endDate || "N/A",
tasks: item.tasks || [],
status: item.status || "pending",
supervisor_comment: item.supervisor_comment || "N/A",
supervisor_comment: item.supervisor_comment || "N/A"
}));

setLoading(false);
setRequests(formatted);

} catch (err) {
console.error("Error fetching forms:", err);
setMessage("Error fetching forms.");
} finally {
setLoading(false);
if (err.response) {
if (err.response.status === 401) {
console.error("Unauthorized access. Redirecting to login...");
setMessage("Unauthorized access. Redirecting to login...");
localStorage.removeItem("token");
window.location.href = "/";
}
else if (err.response.status === 403) {
console.error("Forbidden access. Redirecting to login...");
setMessage("Forbidden access. Redirecting to login...");
window.location.href = "/";
}
else if (err.response.status === 500) {
console.error("Server error. Please try again later.");
setMessage("Server error. Please try again later.");
}
else {
console.error("Unexpected error:", err.message);
setMessage("Unexpected error. Please try again.");
}
}

setLoading(false);
}
};

fetchRequests();
}, []);

const handleAction = async (form_type, id, action, comment, signature) => {
const token = localStorage.getItem("token");
}, [token, setLoading]);

const confirmed = window.confirm(
`Are you sure you want to ${action} this request?`
);
const handleAction = async (id, form_type, action, comment, signature) => {
const confirmed = window.confirm(`Are you sure you want to ${action} this request?`);
if (!confirmed) return;

try {
Expand All @@ -94,78 +109,77 @@ const SupervisorDashboard = () => {

const openFormView = (form) => setSelectedForm(form);
const closeFormView = () => setSelectedForm(null);

const formatDate = (date) => new Date(date).toLocaleDateString();

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 = <p>Loading...</p>;
} else if (sortedRequests.length === 0) {
content = (
<div className="empty-message-container">
<div className="empty-message">No pending approvals.</div>
</div>
);
} else {
content = (
<table className="dashboard-table">
<thead>
<tr>
<th>Student Name</th>
<th>Sooner ID</th>
<th>Student Email</th>
<th>Form Type</th>
<th>Submitted</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{sortedRequests.map((req) => (
<tr key={req._id}>
<td>{req.interneeName}</td>
<td>
<button
className="link-button"
onClick={() => openFormView(req)}
>
{req.soonerId}
</button>
</td>
<td>{req.interneeEmail}</td>
<td>{req.form_type}</td>
<td>{formatDate(req.createdAt)}</td>
<td>
<span className={`status-badge ${req.supervisor_status}`}>
{req.supervisor_status}
</span>
</td>

let content;
if (loading) {
content = <p>Loading...</p>;
} else if (sortedRequests.length === 0) {
content = (
<div className="empty-message-container">
<div className="empty-message">No pending approvals.</div>
</div>
);
} else {
content = (
<table className="dashboard-table">
<thead>
<tr>
<th>Student Name</th>
<th>Student Email</th>
<th>Form Type</th>
<th>Submitted</th>
<th>Status</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{sortedRequests.map((req) => {
return (
<tr
key={req._id}
className="clickable-row"
onClick={() => openFormView(req)}
style={{ cursor: "pointer" }}
>
<td>{req.interneeName || "N/A"}</td>
<td>{req.interneeEmail || "N/A"}</td>
<td>{req.form_type}</td>
<td>{formatDate(req.createdAt)}</td>
<td>
<span className={`status-badge ${req.supervisor_status || req.status}`}>
{req.supervisor_status || req.status}
</span>
</td>
</tr>
);
}
})}
</tbody>

return (
<div className="dashboard-container">
<h2>Supervisor Dashboard</h2>
{message && <p className="status-msg">{message}</p>}
{content}
{selectedForm && (
<ViewFormModal
formData={selectedForm}
onClose={closeFormView}
onAction={(id, action, comment, signature) =>
handleAction(selectedForm.form_type, id, action, comment, signature)
}
/>
)}
</div>
);
};
</table>
);
}

return (
<div className="dashboard-container">
<h2>Supervisor Dashboard</h2>
{message && <p className="status-msg">{message}</p>}
{content}
{selectedForm && (
<ViewFormModal
formData={selectedForm}
onClose={closeFormView}
onAction={(id, action, comment, signature) =>
handleAction(id, selectedForm.form_type, action, comment, signature)
}
/>
)}
</div>
);
};

export default SupervisorDashboard;
Loading