diff --git a/client/src/pages/A3JobEvaluationForm.jsx b/client/src/pages/A3JobEvaluationForm.jsx index f04c9b07..cb63ddf3 100644 --- a/client/src/pages/A3JobEvaluationForm.jsx +++ b/client/src/pages/A3JobEvaluationForm.jsx @@ -38,11 +38,15 @@ const evaluationItems = [ const A3JobEvaluationForm = () => { // Form state management const [formData, setFormData] = useState({ + interneeName: "", + interneeID: "", + interneeEmail: "", advisorSignature: "", advisorAgreement: false, coordinatorSignature: "", coordinatorAgreement: false, }); + const [errors, setErrors] = useState({}); // Ratings and comments const [ratings, setRatings] = useState({}); @@ -58,6 +62,24 @@ const A3JobEvaluationForm = () => { const [selectedFont, setSelectedFont] = useState(fonts[0]); const [activeTab, setActiveTab] = useState("type"); + // For validation of the form contents + const validateForm = () => { + const newErrors = {}; + 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.coordinatorSignature) newErrors.coordinatorSignature = "Signature is required."; + evaluationItems.forEach((item) => { + if (!ratings[item]) { + newErrors[`${item}_rating`] = "Please select one of these"; // Error message + } + }); + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + // Signature canvas ref const sigCanvasRef = useRef(null); @@ -71,6 +93,7 @@ const A3JobEvaluationForm = () => { // Handle form input changes const handleChange = (field, value) => { setFormData((prev) => ({ ...prev, [field]: value })); + setErrors((prev) => ({ ...prev, [field]: undefined })); }; // Rating selection @@ -120,8 +143,8 @@ const A3JobEvaluationForm = () => { // Submit the form to the backend const handleSubmit = async (e) => { e.preventDefault(); - if (!formData.advisorAgreement || !formData.coordinatorAgreement) { - alert("Please confirm both signature agreements before submitting."); + if (!validateForm() || !formData.advisorAgreement || !formData.coordinatorAgreement) { + alert("Please confirm internee details and both signature agreements before submitting."); return; } try { @@ -130,12 +153,25 @@ const A3JobEvaluationForm = () => { { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ formData, ratings, comments }), + body: JSON.stringify({ + interneeName: formData.interneeName, + interneeID: formData.interneeID, + interneeEmail: formData.interneeEmail, + advisorSignature: formData.advisorSignature, + coordinatorSignature: formData.coordinatorSignature, + advisorAgreement: formData.advisorAgreement, + coordinatorAgreement: formData.coordinatorAgreement, + ratings, + comments, + }), } ); if (response.ok) { alert("Evaluation submitted successfully!"); setFormData({ + interneeName: "", + interneeID: "", + interneeEmail: "", advisorSignature: "", advisorAgreement: false, coordinatorSignature: "", @@ -191,10 +227,33 @@ const A3JobEvaluationForm = () => {

A.3 – Job Performance Evaluation

+ + +
+
Internee Details
+ + Name + handleChange("interneeName", e.target.value)} isInvalid={!!errors.interneeName} placeholder="Enter full name" style={{ maxWidth: "300px" }}/> + {errors.interneeName} + + + + Sooner ID + handleChange("interneeID", e.target.value)} isInvalid={!!errors.interneeID} placeholder="Enter 9-digit student ID" style={{ maxWidth: "300px" }}/> + {errors.interneeID} + + + Email + handleChange("interneeEmail", e.target.value)} isInvalid={!!errors.interneeEmail} placeholder="Enter student email" style={{ maxWidth: "300px" }}/> + {errors.interneeEmail} + +
+ +
@@ -207,41 +266,53 @@ const A3JobEvaluationForm = () => { {evaluationItems.map((item, index) => ( - - + + {/* Radios grouped in one cell */} + - - - + + + {/* Show the error below both radio buttons */} + {errors[`${item}_rating`] && ( + + {errors[`${item}_rating`]} + + )} + + + {/* Comments box */} + + ))}
{item} + {item} +
handleRatingChange(item, "Satisfactory")} - required + isInvalid={!!errors[`${item}_rating`]} /> -
- handleRatingChange(item, "Unsatisfactory") - } + onChange={() => handleRatingChange(item, "Unsatisfactory")} + isInvalid={!!errors[`${item}_rating`]} /> - - - handleCommentChange(item, e.target.value) - } - placeholder="Enter comments" - style={{ minWidth: "250px" }} - /> -
+ handleCommentChange(item, e.target.value)} + placeholder="Enter comments" + style={{ minWidth: "250px" }} + /> +
@@ -265,6 +336,7 @@ const A3JobEvaluationForm = () => { > {renderSignaturePreview("advisorSignature")}
+ {errors.advisorSignature} { > {renderSignaturePreview("coordinatorSignature")} + {errors.coordinatorSignature} { }); app.post("/api/evaluation", async (req, res) => { try { - const { formData, ratings, comments } = req.body; + const { interneeName, interneeID, interneeEmail, advisorSignature, advisorAgreement, coordinatorSignature, coordinatorAgreement, ratings, comments } = req.body; const evaluations = Object.keys(ratings).map((category) => ({ category, @@ -104,10 +104,13 @@ app.post("/api/evaluation", async (req, res) => { })); const newEvaluation = new Evaluation({ - advisorSignature: formData.advisorSignature, - advisorAgreement: formData.advisorAgreement, - coordinatorSignature: formData.coordinatorSignature, - coordinatorAgreement: formData.coordinatorAgreement, + interneeName, + interneeID, + interneeEmail, + advisorSignature, + advisorAgreement, + coordinatorSignature, + coordinatorAgreement, evaluations, }); diff --git a/server/models/Evaluation.js b/server/models/Evaluation.js index d104353f..0ea16faf 100644 --- a/server/models/Evaluation.js +++ b/server/models/Evaluation.js @@ -20,9 +20,30 @@ const evaluationItemSchema = new mongoose.Schema({ }, { _id: false }); const evaluationSchema = new mongoose.Schema({ - interneeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: false }, internshipId: { type: mongoose.Schema.Types.ObjectId, ref: 'Internship', required: false }, + interneeName: { + type: String, + required: true, + trim: true, + minlength: 2, + maxlength: 100 + }, + + interneeID: { + type: String, + required: true, + match: [/^\d{9}$/, 'Sooner ID must be a 9-digit number'] // Sooner ID validation + }, + + interneeEmail: { + type: String, + required: true, + match: [/\S+@\S+\.\S+/, 'Invalid email format'], // Email format validation + lowercase: true, + trim: true + }, + evaluations: { type: [evaluationItemSchema], validate: [arr => arr.length > 0, 'At least one evaluation item is required'] @@ -35,6 +56,6 @@ const evaluationSchema = new mongoose.Schema({ }, { timestamps: true }); -evaluationSchema.index({ interneeId: 1, internshipId: 1 }); +evaluationSchema.index({ interneeID: 1, internshipId: 1 }); module.exports = mongoose.model('Evaluation', evaluationSchema);