import React, {useState, useEffect} from "react"; import "../styles/addcourse.css"; import "../styles/modal.css"; import "../styles/course.css"; import {Select} from "../Components/Select"; /** * The AddCourse component displays a form for adding a new course to the system. * @param handleAddCourseModal * @param getCourses * @returns {Element} * @constructor */ const AddCourse = ({handleAddCourseModal, getCourses}) => { const [courseCode, setCourseCode] = useState(""); // State for storing the course code const [courseName, setCourseName] = useState(""); // State for storing the course name const [file, setFile] = useState(null); // State for storing the file const [semester, setSemester] = useState(""); // State for storing the semester const [year, setYear] = useState(null); // State for storing the year const [rosterFileError, setRosterFileError] = useState([]); // State for storing the roster file error const [duplicateError, setDuplicateError] = useState(""); // State for storing the duplicate error const [showModal, setShowModal] = useState(false); // State for showing the modal const [instructors, setInstructors] = useState([]); // array of instructor objects selected including their id and name const [allInstructors, setAllInstructors] = useState([]); // array of all instructors in the database const [selectedInstructors, setSelectedInstructors] = useState([]); // array of selected instructor ids to send to backend const formData = new FormData(); /** * Determines the current year based on the current date. * @returns {number} */ const getCurrentYear = () => { const date = new Date(); return date.getFullYear(); }; // Using 2023-2024 course schedule /** * Determines the current semester based on the current date. * @returns {number} */ const getCurrentSemester = () => { const date = new Date(); const month = date.getMonth(); // 0 for January, 1 for February, etc. const day = date.getDate(); // Summer Sessions (May 30 to Aug 18) if ( (month === 4 && day >= 30) || (month > 4 && month < 7) || (month === 7 && day <= 18) ) { return 3; // Summer } // Fall Semester (Aug 28 to Dec 20) if ( (month === 7 && day >= 28) || (month > 7 && month < 11) || (month === 11 && day <= 20) ) { return 4; // Fall } // Winter Session (Dec 28 to Jan 19) if ((month === 11 && day >= 28) || (month === 0 && day <= 19)) { return 1; // Winter } // If none of the above conditions are met, it must be Spring (Jan 24 to May 19) return 2; // Spring }; /** * Converts semester names to their corresponding integer codes. */ const getFutureSemesters = () => { const date = new Date(); const currentYear = getCurrentYear(); const currentSemester = getCurrentSemester(); const futureSemesters = []; let startSem; if (currentSemester === 1) startSem = "winter"; if (currentSemester === 2) startSem = "spring"; if (currentSemester === 3) startSem = "summer"; if (currentSemester === 4) startSem = "fall"; let year = currentYear; // Include 4 semesters from the current one for (let i = 0; i < 4; i++) { futureSemesters.push({ value: `${startSem}_${year}`, text: `${startSem.charAt(0).toUpperCase() + startSem.slice(1)} ${year}`, }); startSem = getNextSemester(startSem); if (startSem === "winter") { year++; } } return futureSemesters; }; /** * Returns the next semester given the current semester. * @param currentSemester * @returns {string} */ const getNextSemester = (currentSemester) => { switch (currentSemester) { case "winter": return "spring"; case "spring": return "summer"; case "summer": return "fall"; case "fall": return "winter"; default: return ""; } }; const futureSemesters = getFutureSemesters(); // Array of future semesters const [semesters, setSemesters] = useState(futureSemesters); // State for storing the future semesters // fetch the courses to display on the sidebar useEffect(() => { setYear(getCurrentYear()); let currSem = getCurrentSemester(); if (currSem === 1) setSemester("winter"); if (currSem === 2) setSemester("spring"); if (currSem === 3) setSemester("summer"); if (currSem === 4) setSemester("fall"); // Fetch all instructors fetch(process.env.REACT_APP_API_URL + "getInstructors.php", { method: "GET", headers: { "Content-Type": "application/x-www-form-urlencoded", }, }) .then((res) => res.json()) .then((result) => { let fetchedInstructors = []; result.map((instructor) => { let currentInstructor = { label: instructor[1], value: instructor[0], }; fetchedInstructors.push(currentInstructor); }); setAllInstructors(fetchedInstructors); }) .catch((err) => { console.log(err); }); }, []); // Everytime an instructor is selected/deselected the selectedInstructors state updates useEffect(() => { let instructorIds = []; instructors.map((instructor) => { instructorIds.push(+instructor.value); }); setSelectedInstructors(instructorIds); }, [instructors]); /** * Handles the change of the semester. */ const handleSemesterChange = (e) => { const selectedValue = e.target.value; // For example, "fall_2024" const [newSemester, newYear] = selectedValue.split("_"); // Splits to ["fall", "2024"] // Update the states setSemester(newSemester); setYear(parseInt(newYear)); }; /** * Formats the roster file error. * @param input */ function formatRosterError(input) { // Split the string into an array on the "Line" pattern, then filter out empty strings const lines = input .split(/(Line \d+)/) .filter((line) => line.trim() !== ""); // Combine adjacent elements so that each "Line #" and its message are in the same element const combinedLines = []; for (let i = 0; i < lines.length; i += 2) { combinedLines.push(lines[i] + (lines[i + 1] || "")); } return combinedLines } /** * Handles the form submission. * @param e */ const handleSubmit = (e) => { e.preventDefault(); formData.append("course-code", courseCode); formData.append("course-name", courseName); formData.append("course-year", year); formData.append("roster-file", file); // Assuming `file` is a File object formData.append("semester", semester); formData.append("additional-instructors", selectedInstructors); // Send the form data to the API fetch(process.env.REACT_APP_API_URL + "courseAdd.php", { method: "POST", body: formData, }) // Parse the response to JSON format .then((res) => res.text()) .then((result) => { // If the result is a string and not empty, it means there is an error if (typeof result === "string" && result !== "") { try { const parsedResult = JSON.parse(result); console.log(parsedResult); if (parsedResult["roster-file"]) { setShowModal(true); const updatedError = formatRosterError( parsedResult["roster-file"] ); setRosterFileError(updatedError); } else if (parsedResult["duplicate"]) { setDuplicateError(parsedResult["duplicate"]); } } catch (e) { console.log("Failed to parse JSON: ", e); } } else { // Class is valid, so we can just navigate to the home page handleAddCourseModal(); getCourses(); setRosterFileError([]); setDuplicateError(""); } }) .catch((err) => { console.log(err); }); }; const handleModalClose = () => { setShowModal(false); // Close the modal }; // The AddCourse component renders a form to add a new course. return ( <>

Add Course

{/*The form element where the onSubmit event is handled by the handleSubmit function.*/}
{/* Section for course code and name with potential duplicate error messages. */}
{/* Input fields for course code and name. */}
{/* Displays error message if the course being added is a duplicate. */} {duplicateError && (

This course already exists

)}
{/* File input for course roster CSV file with specific requirements. */}
{/* Dropdown for selecting the course's semester and year. */}
{/* Select component for choosing additional instructors. */}