Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
539baff
Added Id, Name and Address to Form A.3
rithwik-d Apr 17, 2025
fc7648f
Merge branch 'GroupB/development' of https://github.com/IPMS-Project/…
rithwik-d Apr 17, 2025
1c5db44
Updated emailIntegration for Form A.3
rithwik-d Apr 18, 2025
04457fe
Read Only Fields for Internship Advisor Details
lumry Apr 19, 2025
c5f64c9
Read Only Fields for Internship Advisor Details
lumry Apr 19, 2025
b7806b0
Read Only Fields for Internship Advisor Details
rithwik-d Apr 20, 2025
48c0619
Merge branch 'GroupB/development' of https://github.com/IPMS-Project/…
rithwik-d Apr 20, 2025
4f47cd5
Sprint 3: Evaluation Submission Flow implemented (Form A.3)
ttabirami12062 Apr 21, 2025
9ddeb57
Updated Evaluation.js schema for Sprint 3: Form A.3 submission flow
ttabirami12062 Apr 21, 2025
f0be007
Implemented coordinator approval flow for Form A.1 (update status su…
ttabirami12062 Apr 27, 2025
84b86a9
Advisor to Supervisor
lumry Apr 27, 2025
5c1e755
Updating with team changes
lumry Apr 28, 2025
833dd62
Final commit: Completed Coordinator Approval Flow
ttabirami12062 Apr 28, 2025
1d43f8e
storing work
lumry Apr 28, 2025
ab47397
locked form changes
lumry Apr 28, 2025
cf35f48
Updating locked fields for Form A.3
lumry Apr 28, 2025
ef701d4
Updating the locked status for Form A.3
lumry Apr 28, 2025
4c8f25f
form lockdown edits
lumry Apr 28, 2025
919dadf
Fixed module imports, added Submission model, cleaned approvalRoutes
ttabirami12062 Apr 28, 2025
7486f1e
Resolved merge conflict in insertData.js
ttabirami12062 Apr 28, 2025
4bf7607
Merge pull request #164 from IPMS-Project/GroupB/FormLockdown
ttabirami12062 Apr 28, 2025
d3c798d
minor changes
lumry Apr 28, 2025
c97a3e5
Merge branch 'GroupB/development' of https://github.com/IPMS-Project/…
lumry Apr 28, 2025
3cf54c3
Minor locked logic changes
lumry Apr 28, 2025
7fbebf8
Merge pull request #165 from IPMS-Project/GroupB/development
ttabirami12062 Apr 28, 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
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.4.1",
"react-scripts": "5.0.1",
"react-scripts": "^5.0.1",
"react-signature-canvas": "^1.1.0-alpha.2",
"react-toastify": "^11.0.5",
"sweetalert2": "^11.17.2",
Expand Down
117 changes: 77 additions & 40 deletions client/src/pages/A3JobEvaluationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Modal,
Tab,
Nav,
Alert,
} from "react-bootstrap";
import SignatureCanvas from "react-signature-canvas";
import "../styles/A3JobEvaluationForm.css";
Expand Down Expand Up @@ -41,22 +42,24 @@ const A3JobEvaluationForm = () => {
interneeName: "",
interneeID: "",
interneeEmail: "",
advisorSignature: "",
advisorAgreement: false,
supervisorSignature: "",
supervisorAgreement: false,
coordinatorSignature: "",
coordinatorAgreement: false,
locked: false, //not locked with fresh form
});
const [supervisorDetails, setSupervisorDetails] = useState(null);
const [errors, setErrors] = useState({});

// Ratings and comments
const [ratings, setRatings] = useState({});
const [comments, setComments] = useState({});

// Modal state
const [showModal, setShowModal] = useState(false);
const [activeSignatureTarget, setActiveSignatureTarget] = useState("advisor");
const [activeSignatureTarget, setActiveSignatureTarget] = useState("supervisor");
const [typedSignatures, setTypedSignatures] = useState({
advisor: "",
supervisor: "",
coordinator: "",
});
const [selectedFont, setSelectedFont] = useState(fonts[0]);
Expand All @@ -68,7 +71,7 @@ const A3JobEvaluationForm = () => {
if (!formData.interneeName?.trim()) newErrors.interneeName = "Name is required.";
if (!/^\d{9}$/.test(formData.interneeID || "")) newErrors.interneeID = "Enter a valid 9-digit Sooner ID.";
if (!/\S+@\S+\.\S+/.test(formData.interneeEmail || "")) newErrors.interneeEmail = "Invalid email.";
if (!formData.advisorSignature) newErrors.advisorSignature = "Signature is required.";
if (!formData.supervisorSignature) newErrors.supervisorSignature = "Signature is required.";
if (!formData.coordinatorSignature) newErrors.coordinatorSignature = "Signature is required.";
evaluationItems.forEach((item) => {
if (!ratings[item]) {
Expand Down Expand Up @@ -109,8 +112,8 @@ const A3JobEvaluationForm = () => {
// Handle inserting signature from modal
const handleSignatureInsert = () => {
const targetField =
activeSignatureTarget === "advisor"
? "advisorSignature"
activeSignatureTarget === "supervisor"
? "supervisorSignature"
: "coordinatorSignature";
if (activeTab === "type" && typedSignatures[activeSignatureTarget].trim()) {
//handleChange(targetField, JSON.stringify({ type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont }));
Expand Down Expand Up @@ -143,7 +146,7 @@ const A3JobEvaluationForm = () => {
// Submit the form to the backend
const handleSubmit = async (e) => {
e.preventDefault();
if (!validateForm() || !formData.advisorAgreement || !formData.coordinatorAgreement) {
if (!validateForm() || !formData.supervisorAgreement || !formData.coordinatorAgreement) {
alert("Please confirm internee details and both signature agreements before submitting.");
return;
}
Expand All @@ -157,9 +160,9 @@ const A3JobEvaluationForm = () => {
interneeName: formData.interneeName,
interneeID: formData.interneeID,
interneeEmail: formData.interneeEmail,
advisorSignature: formData.advisorSignature,
supervisorSignature: formData.supervisorSignature,
coordinatorSignature: formData.coordinatorSignature,
advisorAgreement: formData.advisorAgreement,
supervisorAgreement: formData.supervisorAgreement,
coordinatorAgreement: formData.coordinatorAgreement,
ratings,
comments,
Expand All @@ -172,14 +175,18 @@ const A3JobEvaluationForm = () => {
interneeName: "",
interneeID: "",
interneeEmail: "",
advisorSignature: "",
advisorAgreement: false,
supervisorName: "",
supervisorJobTitle: "",
supervisorEmail: "",
supervisorSignature: "",
supervisorAgreement: false,
coordinatorSignature: "",
coordinatorAgreement: false,
locked: true, //locked when properly approved
});
setRatings({});
setComments({});
setTypedSignatures({ advisor: "", coordinator: "" });
setTypedSignatures({ supervisor: "", coordinator: "" });
sigCanvasRef.current?.clear();
} else {
const err = await response.json();
Expand Down Expand Up @@ -231,29 +238,44 @@ const A3JobEvaluationForm = () => {
style={{ backgroundColor: "#fff", maxWidth: "900px", width: "100%" }}
>
<Form onSubmit={handleSubmit}>
<Row className="justify-content-center">
<Row className="justify-content-center" style={{ marginBottom: "20px" }}>
<Col xs={12}>
<div className="border-box">
<h5 style={{ backgroundColor: '#9d2235', color: 'white', padding: '8px', borderRadius: '5px', textAlign: "center", width: '100%',}}>Internee Details</h5>
<Form.Group controlId="interneeName">
<Form.Label>Name</Form.Label>
<Form.Control type="text" value={formData.interneeName} onChange={(e) => handleChange("interneeName", e.target.value)} isInvalid={!!errors.interneeName} placeholder="Enter full name" style={{ maxWidth: "300px" }}/>
<Form.Control type="text" value={formData.interneeName} onChange={(e) => handleChange("interneeName", e.target.value)} isInvalid={!!errors.interneeName} placeholder="Enter full name" style={{ maxWidth: "300px" }} disabled={formData.locked}/>
<Form.Text className="text-danger">{errors.interneeName}</Form.Text>

</Form.Group>
<Form.Group controlId="interneeID">
<Form.Label>Sooner ID</Form.Label>
<Form.Control type="text" maxLength={9} value={formData.interneeID} onChange={(e) => handleChange("interneeID", e.target.value)} isInvalid={!!errors.interneeID} placeholder="Enter 9-digit student ID" style={{ maxWidth: "300px" }}/>
<Form.Control type="text" maxLength={9} value={formData.interneeID} onChange={(e) => handleChange("interneeID", e.target.value)} isInvalid={!!errors.interneeID} placeholder="Enter 9-digit student ID" style={{ maxWidth: "300px" }} disabled={formData.locked}/>
<Form.Text className="text-danger">{errors.interneeID}</Form.Text>
</Form.Group>
<Form.Group controlId="interneeEmail">
<Form.Label>Email</Form.Label>
<Form.Control type="email" value={formData.interneeEmail} onChange={(e) => handleChange("interneeEmail", e.target.value)} isInvalid={!!errors.interneeEmail} placeholder="Enter student email" style={{ maxWidth: "300px" }}/>
<Form.Control type="email" value={formData.interneeEmail} onChange={(e) => handleChange("interneeEmail", e.target.value)} isInvalid={!!errors.interneeEmail} placeholder="Enter student email" style={{ maxWidth: "300px" }} disabled={formData.locked}/>
<Form.Text className="text-danger">{errors.interneeEmail}</Form.Text>
</Form.Group>
</div>
<div className="border-box mt-4">
<h5 style={{ backgroundColor: '#9d2235', color: 'white', padding: '8px', borderRadius: '5px', textAlign: "center", width: '100%' }}> Internship Supervisor Details </h5>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control type="text" value={formData.supervisorName} readOnly style={{ maxWidth: "300px" }} />
</Form.Group>
<Form.Group>
<Form.Label>Job Title</Form.Label>
<Form.Control type="text" value={formData.supervisorJobTitle} readOnly style={{ maxWidth: "300px" }} />
</Form.Group>
<Form.Group>
<Form.Label>Email</Form.Label>
<Form.Control type="email" value={formData.supervisorEmail} readOnly style={{ maxWidth: "300px" }} />
</Form.Group>
</div>
</Col>
</Row>

<Table bordered responsive className="text-center custom-table">
<thead>
<tr>
Expand All @@ -280,6 +302,7 @@ const A3JobEvaluationForm = () => {
checked={ratings[item] === "Satisfactory"}
onChange={() => handleRatingChange(item, "Satisfactory")}
isInvalid={!!errors[`${item}_rating`]}
disabled={formData.locked}
/>
<Form.Check
type="radio"
Expand All @@ -290,6 +313,7 @@ const A3JobEvaluationForm = () => {
checked={ratings[item] === "Unsatisfactory"}
onChange={() => handleRatingChange(item, "Unsatisfactory")}
isInvalid={!!errors[`${item}_rating`]}
disabled={formData.locked}
/>
</div>

Expand Down Expand Up @@ -318,10 +342,10 @@ const A3JobEvaluationForm = () => {
</Table>

{/* Signature section */}
<Row className="mb-4">
<Col md={6} className="mb-3 mb-md-0">
<Row className="mb-4 d-flex flex-column flex-md-row gap-3">
<Col md={6} style={{ flex: 1 }} className="mb-3 mb-md-0">
<Form.Group>
<Form.Label>Internship Advisor Signature</Form.Label>
<Form.Label>Internship Supervisor Signature</Form.Label>
<div
style={{
cursor: "pointer",
Expand All @@ -330,26 +354,29 @@ const A3JobEvaluationForm = () => {
padding: "6px 0",
}}
onClick={() => {
setActiveSignatureTarget("advisor");
setShowModal(true);
if (!formData.locked) {
setActiveSignatureTarget("supervisor");
setShowModal(true);
}
}}
>
{renderSignaturePreview("advisorSignature")}
{renderSignaturePreview("supervisorSignature")}
</div>
<Form.Text className="text-danger">{errors.advisorSignature}</Form.Text>
<Form.Text className="text-danger">{errors.supervisorSignature}</Form.Text>
<Form.Check
type="checkbox"
className="mt-2"
label="I agree that by typing/drawing my name, I am electronically signing this document."
checked={formData.advisorAgreement}
checked={formData.supervisorAgreement}
onChange={(e) =>
handleChange("advisorAgreement", e.target.checked)
handleChange("supervisorAgreement", e.target.checked)
}
required
disabled={formData.locked}
/>
</Form.Group>
</Col>
<Col md={6}>
<Col md={6} style={{ flex: 1 }}>
<Form.Group>
<Form.Label>Internship Coordinator Signature</Form.Label>
<div
Expand All @@ -360,8 +387,10 @@ const A3JobEvaluationForm = () => {
padding: "6px 0",
}}
onClick={() => {
setActiveSignatureTarget("coordinator");
setShowModal(true);
if (!formData.locked) {
setActiveSignatureTarget("coordinator");
setShowModal(true);
}
}}
>
{renderSignaturePreview("coordinatorSignature")}
Expand All @@ -376,21 +405,29 @@ const A3JobEvaluationForm = () => {
handleChange("coordinatorAgreement", e.target.checked)
}
required
disabled={formData.locked}
/>
</Form.Group>
</Col>
</Row>

{/* Submit button */}
<div className="text-center">
<Button
type="submit"
className="px-5 text-white"
style={{ backgroundColor: "#9d2235", borderColor: "#9d2235" }}
>
Submit Evaluation
</Button>
</div>
<div className="text-center mt-4">
{formData.locked ? (
<Alert variant="info">
This form has been finalized and is locked for editing.
</Alert>
) : (
<Button
type="submit"
className="px-5 text-white"
style={{ backgroundColor: "#9d2235", borderColor: "#9d2235" }}
disabled={formData.locked} // Ensure the button is also disabled
>
Submit Evaluation
</Button>
)}
</div>
</Form>
</Container>

Expand Down
84 changes: 34 additions & 50 deletions client/src/pages/CoordinatorDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ const CoordinatorDashboard = () => {
setLoadingRequests(false);
}
};
// Group D's Weekly Report Review Logic

const [reportGroups, setReportGroups] = useState([]);
const [loadingReports, setLoadingReports] = useState(true);
const approveForm = async (formId) => {
try {
await axios.post(
`${process.env.REACT_APP_API_URL}/api/approval/form/${formId}/approve`
);
alert("Form approved successfully!");
fetchRequests(); // refresh the list after approving
} catch (err) {
console.error("Failed to approve form:", err);
alert("Failed to approve form!");
}
};

useEffect(() => {
if (activeTab === "reports") {
Expand Down Expand Up @@ -69,53 +78,28 @@ const CoordinatorDashboard = () => {
<button onClick={() => setActiveTab("reports")} className={activeTab === "reports" ? "active" : ""}>Weekly Reports Review</button>
</div>

{/* Tab: Internship Requests */}
{activeTab === "requests" && (
<>
{loadingRequests ? <p>Loading...</p> : (
<table className="dashboard-table">
<thead>
<tr>
<th>Student Name</th>
<th>Student ID</th>
<th>Company</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{requests.map(req => (
<tr key={req._id}>
<td>{req.studentName}</td>
<td>{req.studentId}</td>
<td>{req.companyName}</td>
<td>{req.status}</td>
</tr>
))}
</tbody>
</table>
)}
</>
)}

{/* Tab: Weekly Reports Review */}
{activeTab === "reports" && (
<>
{loadingReports ? <p>Loading reports...</p> : (
reportGroups.length === 0
? <p>No reports to review</p>
: reportGroups.map(group => (
<div className="report-group-card" key={group.groupIndex}>
<h4>Weeks: {group.weeks?.join(", ")}</h4>
<ul>
{group.reports.map((r, i) => (
<li key={i}>Week {r.week} — Hours: {r.hours} — Tasks: {r.tasks}</li>
))}
</ul>
<button onClick={() => handleReviewClick(group)}>Review & Comment</button>
</div>
))
)}
</>
{requests.length === 0 ? (
<p>No Pending Requests</p>
) : (
requests.map((req) => (
<div key={req._id} className="request-card">
<p>Company: {req.workplace.name}</p>
<div style={{ display: "flex", gap: "10px" }}>
<button
className="btn-approve"
onClick={() => approveForm(req._id)}
>
Approve
</button>
<button
className="btn-view"
onClick={() => navigate(`/coordinator/request/${req._id}`)}
>
View Details
</button>
</div>
</div>
))
)}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion client/src/router.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import { createBrowserRouter } from "react-router-dom";
import A1InternshipRequestForm from "./pages/A1InternshipRequestForm";

// Layout
import Layout from "./components/Layout";
Expand All @@ -10,6 +9,7 @@ import Home from "./pages/Home";
import SignUp from "./pages/SignUp";
import NotFound from "./pages/NotFound";
import WeeklyProgressReportForm from "./pages/WeeklyProgressReportForm";
import A1InternshipRequestForm from "./pages/A1InternshipRequestForm";
import A3JobEvaluationForm from "./pages/A3JobEvaluationForm";
import ActivateAccount from "./pages/ActivateAccount";
import A4PresentationEvaluationForm from "./pages/A4PresentationEvaluationForm";
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"cors": "^2.8.5"
}
}
Loading
Loading