forked from CSI-KJSCE/appointment_to_examiner
369 lines
12 KiB
JavaScript
369 lines
12 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
|
import { useLocation, useParams, useNavigate } from "react-router-dom";
|
|
import { fetchFaculties, saveAppointment } from "../api";
|
|
import "./CourseForm.css";
|
|
import "./Navbar.jsx";
|
|
import Navbar from "./Navbar.jsx";
|
|
import Footer from "./Footer.jsx";
|
|
|
|
const CourseForm = () => {
|
|
const { id } = useParams();
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const { course, academicYear } = location.state || {};
|
|
|
|
const [options, setOptions] = useState({ faculties: [] });
|
|
const [suggestions, setSuggestions] = useState({});
|
|
const [formData, setFormData] = useState({
|
|
oralsPracticals: "",
|
|
assessment: "",
|
|
reassessment: "",
|
|
paperSetting: "",
|
|
moderation: "",
|
|
pwdPaperSetter: "",
|
|
});
|
|
|
|
const [tempAssignments, setTempAssignments] = useState({
|
|
oralsPracticals: [],
|
|
assessment: [],
|
|
reassessment: [],
|
|
paperSetting: [],
|
|
moderation: [],
|
|
pwdPaperSetter: [],
|
|
});
|
|
|
|
const [examPeriod, setExamPeriod] = useState({
|
|
year: academicYear || "",
|
|
startMonth: "",
|
|
endMonth: "",
|
|
});
|
|
|
|
const [errors, setErrors] = useState({});
|
|
|
|
// State to check if the form is currently submitting
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const fetchOptionsAndFaculties = async () => {
|
|
try {
|
|
const facultiesData = await fetchFaculties();
|
|
const filteredFaculties = facultiesData.filter(
|
|
(faculty) => faculty.courses.includes(course?.courseId || id) // Only faculties with this course
|
|
);
|
|
setOptions((prev) => ({ ...prev, faculties: filteredFaculties }));
|
|
} catch (error) {
|
|
console.error("Failed to fetch faculties:", error);
|
|
}
|
|
};
|
|
fetchOptionsAndFaculties();
|
|
}, []);
|
|
|
|
const handleInputChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFormData({ ...formData, [name]: value });
|
|
if (value.trim() === "") {
|
|
setSuggestions((prev) => ({ ...prev, [name]: [] }));
|
|
return;
|
|
}
|
|
if (options.faculties.length > 0) {
|
|
const filteredSuggestions = options.faculties.filter((faculty) =>
|
|
faculty.name.toLowerCase().includes(value.toLowerCase())
|
|
);
|
|
setSuggestions((prev) => ({ ...prev, [name]: filteredSuggestions }));
|
|
}
|
|
};
|
|
|
|
const handleAddFaculty = (field) => {
|
|
if (!formData[field]) return;
|
|
|
|
const selectedFaculty = options.faculties.find(
|
|
(faculty) => faculty.name === formData[field]
|
|
);
|
|
|
|
if (selectedFaculty) {
|
|
setTempAssignments((prev) => ({
|
|
...prev,
|
|
[field]: [...prev[field], selectedFaculty],
|
|
}));
|
|
setFormData({ ...formData, [field]: "" });
|
|
}
|
|
};
|
|
|
|
const handleRemoveFaculty = (field, index) => {
|
|
setTempAssignments((prev) => {
|
|
const updatedAssignments = [...prev[field]];
|
|
updatedAssignments.splice(index, 1);
|
|
return { ...prev, [field]: updatedAssignments };
|
|
});
|
|
};
|
|
|
|
const validateForm = () => {
|
|
const newErrors = {};
|
|
Object.keys(tempAssignments).forEach((field) => {
|
|
if (!tempAssignments[field] || tempAssignments[field].length === 0) {
|
|
newErrors[field] = "At least one faculty must be assigned.";
|
|
}
|
|
});
|
|
|
|
if (!examPeriod.year || !examPeriod.startMonth || !examPeriod.endMonth) {
|
|
newErrors.examPeriod = "Exam period is required.";
|
|
}
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
if (validateForm()) {
|
|
try {
|
|
const groupedTasks = {};
|
|
const academicYear = course?.academicYear || examPeriod.year;
|
|
Object.entries(tempAssignments).forEach(([field, facultyList]) => {
|
|
facultyList.forEach((faculty) => {
|
|
const assignedFaculty = options.faculties.find(
|
|
(optionFaculty) => optionFaculty.facultyId === faculty.facultyId
|
|
);
|
|
if (assignedFaculty) {
|
|
if (!groupedTasks[assignedFaculty.facultyId]) {
|
|
groupedTasks[assignedFaculty.facultyId] = {
|
|
facultyId: assignedFaculty.facultyId,
|
|
courseId: course?.courseId || id,
|
|
tasks: [],
|
|
examPeriod: `${examPeriod.startMonth} - ${examPeriod.endMonth}`,
|
|
academicYear,
|
|
};
|
|
}
|
|
groupedTasks[assignedFaculty.facultyId].tasks.push(field);
|
|
}
|
|
});
|
|
});
|
|
|
|
const payload = Object.values(groupedTasks);
|
|
|
|
// Start loading so user knows it is saving
|
|
setLoading(true);
|
|
|
|
await saveAppointment(payload);
|
|
// await updateCourseStatus(course?.courseId || id);
|
|
|
|
const filteredCourses =
|
|
JSON.parse(localStorage.getItem("filteredCourses")) || [];
|
|
navigate("/courses", {
|
|
state: {
|
|
courses: filteredCourses,
|
|
academicYear: academicYear,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to save appointment:", error);
|
|
// If error comes, stop the loading
|
|
setLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Navbar />
|
|
<div>
|
|
<div className="courseFormContainer">
|
|
<div className="courseFormFormContainer">
|
|
<h2 className="courseFormHeader">Course Info</h2>
|
|
<form className="courseForm" onSubmit={handleSubmit}>
|
|
<div>
|
|
<label className="courseFormLabel">
|
|
Course ID:
|
|
<input
|
|
type="text"
|
|
className="courseFormInput courseFormReadOnly"
|
|
value={course?.courseId || id}
|
|
readOnly
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label className="courseFormLabel">
|
|
Course Name:
|
|
<input
|
|
type="text"
|
|
className="courseFormInput courseFormReadOnly"
|
|
value={course?.name || ""}
|
|
readOnly
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className={errors.examPeriod ? "courseFormErrorSelect" : ""}>
|
|
<label className="courseFormLabel">Exam Period:</label>
|
|
<input
|
|
type="text"
|
|
className="courseFormInput courseFormReadOnly"
|
|
value={examPeriod.year}
|
|
readOnly
|
|
/>
|
|
<select
|
|
className="courseFormSelect"
|
|
value={examPeriod.startMonth}
|
|
onChange={(e) =>
|
|
setExamPeriod({ ...examPeriod, startMonth: e.target.value })
|
|
}
|
|
>
|
|
<option value="">Start Month</option>
|
|
{[
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December",
|
|
].map((month) => (
|
|
<option key={month} value={month}>
|
|
{month}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
className="courseFormSelect"
|
|
value={examPeriod.endMonth}
|
|
onChange={(e) =>
|
|
setExamPeriod({ ...examPeriod, endMonth: e.target.value })
|
|
}
|
|
>
|
|
<option value="">End Month</option>
|
|
{[
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December",
|
|
].map((month) => (
|
|
<option key={month} value={month}>
|
|
{month}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{errors.examPeriod && (
|
|
<span className="courseFormErrorMessage">
|
|
{errors.examPeriod}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{[
|
|
{ name: "oralsPracticals", label: "Orals/Practicals" },
|
|
{ name: "assessment", label: "Assessment" },
|
|
{ name: "reassessment", label: "Reassessment" },
|
|
{ name: "paperSetting", label: "Paper Setting" },
|
|
{ name: "moderation", label: "Moderation" },
|
|
{ name: "pwdPaperSetter", label: "PwD Paper Setter" },
|
|
].map(({ name, label }) => (
|
|
<div
|
|
key={name}
|
|
className={errors[name] ? "courseFormErrorInput" : ""}
|
|
>
|
|
<label className="courseFormLabel">
|
|
{label}:
|
|
<select
|
|
className="courseFormSelect"
|
|
name={name}
|
|
value={formData[name]}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, [name]: e.target.value })
|
|
}
|
|
>
|
|
<option value="">Select Faculty</option>
|
|
{options.faculties.map((faculty) => (
|
|
<option key={faculty.facultyId} value={faculty.name}>
|
|
{faculty.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button
|
|
type="button"
|
|
className="courseFormButton"
|
|
onClick={() => handleAddFaculty(name)}
|
|
disabled={!formData[name].trim()}
|
|
>
|
|
Add
|
|
</button>
|
|
<ul
|
|
className="courseFormSuggestions"
|
|
id={`suggestions-${name}`}
|
|
style={{
|
|
display:
|
|
(suggestions[name] || []).length > 0
|
|
? "block"
|
|
: "none",
|
|
}}
|
|
>
|
|
{(suggestions[name] || []).map((faculty) => (
|
|
<li
|
|
className="courseFormSuggestionsItem"
|
|
key={faculty.facultyId}
|
|
onClick={() => {
|
|
setFormData({ ...formData, [name]: faculty.name });
|
|
setSuggestions((prev) => ({ ...prev, [name]: [] }));
|
|
}}
|
|
>
|
|
{faculty.name}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</label>
|
|
{tempAssignments[name].length > 0 && (
|
|
<ul className="courseFormTempList">
|
|
{tempAssignments[name].map((faculty, index) => (
|
|
<li className="courseFormTempListItem" key={index}>
|
|
{faculty.name}
|
|
<button
|
|
type="button"
|
|
className="courseFormRemoveFacultyBtn"
|
|
onClick={() => handleRemoveFaculty(name, index)}
|
|
>
|
|
✕
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{errors[name] && (
|
|
<span className="courseFormErrorMessage">
|
|
{errors[name]}
|
|
</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
<button
|
|
type="submit"
|
|
className="courseFormButton"
|
|
style={{ gridColumn: "1 / -1" }}
|
|
disabled={loading} // Disable button when loading
|
|
>
|
|
{/* Change text based on loading state */}
|
|
{loading ? "Saving..." : "Submit"}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<Footer />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default CourseForm;
|