Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
62 changes: 53 additions & 9 deletions client/src/pages/SupervisorDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const SupervisorDashboard = () => {
const [selectedForm, setSelectedForm] = useState(null);
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState("");

useEffect(() => {

// Token used for authentication for future
Expand Down Expand Up @@ -82,22 +82,24 @@ 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();

const sortedRequests = [...requests]
.filter((req) => req.status.toLowerCase() === "submitted")
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));

.filter((req) => req.supervisor_status?.toLowerCase() === "pending")
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
let content;

if (loading) {
Expand Down Expand Up @@ -147,16 +149,58 @@ const SupervisorDashboard = () => {
<div className="dashboard-container">
<h2>Supervisor Dashboard</h2>
{message && <p className="status-msg">{message}</p>}
{content}
{loading ? (
<p>Loading...</p>
) : sortedRequests.length === 0 ? (
<div className="empty-message-container">
<div className="empty-message">No pending approvals.</div>
</div>
) : (
<table className="dashboard-table">
<thead>
<tr>
<th>Student Name</th>
<th>Sooner ID</th>
<th>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 || req.studentName}</td>
<td>
<button className="link-button" onClick={() => openFormView(req)}>
{req.interneeID || req.soonerId}
</button>
</td>
<td>{req.interneeEmail || req.studentEmail}</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>
</table>
)}

{selectedForm && (
<ViewFormModal
formData={selectedForm}
onClose={closeFormView}
onAction={handleAction}
onAction={(id, action, comment, signature) =>
handleAction(selectedForm.form_type, id, action, comment, signature)
}
/>
)}
</div>
);
};

export default SupervisorDashboard;
export default SupervisorDashboard;
126 changes: 67 additions & 59 deletions client/src/pages/ViewFormModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,17 @@ const ViewFormModal = ({ formData, onClose, onAction }) => {
const [error, setError] = useState("");

const handleDecision = (action) => {
if (!comment.trim()) {
setError("Comment is required before taking action.");
return;
}
if (!signature.trim()) {
setError("Signature is required before approval/rejection.");
return;
}

const payloadComment = `${comment.trim()} | Supervisor Signature: ${signature.trim()}`;
if (!comment.trim()) return setError("Comment is required.");
if (!signature.trim()) return setError("Signature is required.");
setError("");
onAction(formData._id, formData.form_type, action, payloadComment);
onAction(formData._id, action, comment.trim(), signature.trim());
};

return (
<div className="modal-overlay">
<div className="modal-box">
<h2>A.1 Internship Request Form</h2>

<table className="modal-details-table">
// ✅ Inserted rendering helpers
const renderA1 = () => (
<>
<h2>A1 – Internship Request Form</h2>
<table className="modal-details-table">
<thead>
<tr>
<th>Field</th>
Expand Down Expand Up @@ -60,52 +51,69 @@ const ViewFormModal = ({ formData, onClose, onAction }) => {
</tr>
</tbody>
</table>
</>
);

<div style={{ marginTop: "15px" }}>
<strong>Task Descriptions & Outcomes:</strong>
<ul>
{formData.tasks?.map((task, index) => (
<li key={index} style={{ marginBottom: "10px" }}>
<strong>Task {index + 1}:</strong> {task.description}
<br />
<strong>Outcomes:</strong> {task.outcomes?.join(", ") || "N/A"}
</li>
))}
</ul>
</div>

<div>
<label><strong>Supervisor Signature:</strong></label>
<input
type="text"
value={signature}
onChange={(e) => setSignature(e.target.value)}
placeholder="Enter your full name"
style={{ width: "100%", padding: "6px", marginTop: "5px", borderRadius: "4px" }}
/>
</div>

<div style={{ marginTop: "10px" }}>
<label><strong>Comment:</strong></label>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Add your comment before approval or rejection"
rows={4}
style={{ width: "100%", marginTop: "5px", borderRadius: "4px", padding: "8px" }}
/>
</div>
const renderA3 = () => (
<>
<h2>A3 – Final Job Performance Evaluation</h2>
<p><strong>Name:</strong> {formData.interneeName}</p>
<p><strong>Email:</strong> {formData.interneeEmail}</p>
<p><strong>Sooner ID:</strong> {formData.interneeID}</p>

{error && <p style={{ color: "red", marginTop: "5px" }}>{error}</p>}
<h3>Evaluation Items</h3>
<table className="dashboard-table">
<thead>
<tr>
<th>Category</th>
<th>Rating</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
{formData.evaluations?.map((item, i) => (
<tr key={i}>
<td>{item.category}</td>
<td>{item.rating}</td>
<td>{item.comment || "-"}</td>
</tr>
))}
</tbody>
</table>
</>
);

<div style={{ display: "flex", justifyContent: "center", gap: "10px", marginTop: "15px" }}>
<button className="approve" onClick={() => handleDecision("approve")}>Approve</button>
<button className="reject" onClick={() => handleDecision("reject")}>Reject</button>
</div>
const renderSignaturesAndActions = () => (
<>
<h3>Supervisor Review</h3>
<label>Signature:</label>
<input
type="text"
value={signature}
onChange={(e) => setSignature(e.target.value)}
style={{ width: "100%", marginBottom: "8px" }}
/>
<label>Comment:</label>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={3}
style={{ width: "100%" }}
/>
{error && <p style={{ color: "red" }}>{error}</p>}
<div style={{ marginTop: "10px", display: "flex", justifyContent: "center", gap: "10px" }}>
<button className="approve" onClick={() => handleDecision("approve")}>Approve</button>
<button className="reject" onClick={() => handleDecision("reject")}>Reject</button>
<button onClick={onClose}>Close</button>
</div>
</>
);

<div style={{ marginTop: "10px", textAlign: "center" }}>
<button className="reject" onClick={onClose}>Close</button>
</div>
return (
<div className="modal-overlay">
<div className="modal-box" style={{ maxHeight: "90vh", overflowY: "auto" }}>
{formData.form_type === "A1" ? renderA1() : renderA3()}
{renderSignaturesAndActions()}
</div>
</div>
);
Expand Down
26 changes: 16 additions & 10 deletions server/controllers/approvalController.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,21 @@ exports.handleSupervisorFormAction = async (req, res, action) => {
if (!form) {
return res.status(404).json({ message: "Form not found" });
}

const emailSubject = `Form ${action === "approve" ? "Approved" : "Rejected"}`;
let emailBody = `<p>Your ${form_type} form has been ${action}ed by the supervisor.</p>`;
if (comment) {
emailBody += `<p>Comment: ${comment}</p>`;
}

const studentEmail =
form.student_id?.email ||
form.interneeEmail ||
form.studentEmail ||
null;

if (!studentEmail) {
console.warn("⚠️ No student email found for form:", form._id);
} else {
const emailSubject = `Form ${action === "approve" ? "Approved" : "Rejected"}`;
let emailBody = `<p>Your ${form_type} form has been ${action}ed by the supervisor.</p>`;
if (comment) {
emailBody += `<p>Comment: ${comment}</p>`;
}

const student = await UserTokenRequest.findById(form.student_id);

Expand All @@ -108,7 +117,7 @@ exports.handleSupervisorFormAction = async (req, res, action) => {
subject: emailSubject,
html: emailBody,
});

res.status(200).json({
message: `Form ${action}ed successfully`,
updatedForm: form,
Expand All @@ -119,12 +128,10 @@ exports.handleSupervisorFormAction = async (req, res, action) => {
}
};


// =========================================== //
// Coordinator Dashboard //
// =========================================== //

// Coordinator Dashboard: Get All Internship Requests
exports.getCoordinatorRequests = async (req, res) => {
try {
const requests = await InternshipRequest.find({
Expand All @@ -140,7 +147,6 @@ exports.getCoordinatorRequests = async (req, res) => {
exports.getCoordinatorRequestDetails = async (req, res) => {
try {
const requestData = await InternshipRequest.findById(req.params.id).lean();

if (!requestData) {
return res.status(404).json({ message: "Request not found" });
}
Expand Down
3 changes: 2 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const cronJobManager = require("./utils/cronUtils");
const { registerAllJobs } = require("./jobs/registerCronJobs");
const Evaluation = require("./models/Evaluation");


const app = express();
app.use(express.json());
app.use(cors());
Expand Down Expand Up @@ -143,4 +144,4 @@ process.on("SIGINT", async () => {
});

const PORT = process.env.PORT || 5001;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
2 changes: 1 addition & 1 deletion server/models/Evaluation.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ const evaluationSchema = new mongoose.Schema({

evaluationSchema.index({ interneeID: 1, internshipId: 1 });

module.exports = mongoose.model('Evaluation', evaluationSchema);
module.exports = mongoose.model('Evaluation', evaluationSchema);
2 changes: 1 addition & 1 deletion server/routes/approvalRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ router.post(
coordinatorRejectRequest
);

module.exports = router;
module.exports = router;
9 changes: 9 additions & 0 deletions server/routes/formRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ function validateFormData(formData) {
return `Missing or empty required field: ${field}`;
}
}
});


if (!Array.isArray(formData.tasks) || formData.tasks.length === 0) {
return "Tasks must be a non-empty array";
Expand All @@ -59,7 +61,14 @@ router.post("/submit", async (req, res) => {
if (!formData.studentId) {
return res.status(400).json({ message: "Missing studentId in form data" });
}
});

// ===================
// A3: Evaluation Form
// ===================

// Submit A3 form
router.post("/submit-a3", async (req, res) => {
try {
await insertFormData(formData); // pass studentId through
res.status(200).json({ message: "Form received and stored." });
Expand Down