diff --git a/client/package-lock.json b/client/package-lock.json index 7f3445e..63b5ea0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "mongoose": "^8.3.1", "react": "^18.2.0", "react-bootstrap": "^2.10.2", + "react-csv": "^2.2.2", "react-dom": "^18.2.0", "react-icons": "^5.4.0", "react-router-dom": "^6.28.0", @@ -15339,6 +15340,11 @@ } } }, + "node_modules/react-csv": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", + "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==" + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/client/package.json b/client/package.json index 67219c7..f466e56 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "mongoose": "^8.3.1", "react": "^18.2.0", "react-bootstrap": "^2.10.2", + "react-csv": "^2.2.2", "react-dom": "^18.2.0", "react-icons": "^5.4.0", "react-router-dom": "^6.28.0", diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index 67ab71c..f18505c 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -1,5 +1,87 @@ +// import React, { useState, useEffect } from "react"; +// import axios from "axios"; + +// const ConsolidatedTable = () => { +// const [data, setData] = useState([]); +// const [loading, setLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// try { +// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); +// setData(response.data); +// setLoading(false); +// } catch (error) { +// console.error("Error fetching table data:", error); +// setLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// if (loading) { +// return
Loading...
; +// } + +// return ( +//
+//

Consolidated Table

+// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// {data.map((row, index) => ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ))} +// +//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+//
+// ); +// }; + +// export default ConsolidatedTable; + + + import React, { useState, useEffect } from "react"; import axios from "axios"; +import { CSVLink } from "react-csv"; const ConsolidatedTable = () => { const [data, setData] = useState([]); @@ -24,53 +106,81 @@ const ConsolidatedTable = () => { return
Loading...
; } + // Extract unique faculty names + const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; + return (
-

Consolidated Table

- - - - - - - - - - - - - - - - - - - - - - - {data.map((row, index) => ( - - - - - - - - - - - - - - - - - - - ))} - -
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+

Faculty Tables with Download Option

+ {uniqueTeachers.map((teacher, index) => { + // Filter rows for the current teacher + const teacherData = data.filter((row) => row.Name === teacher); + + return ( +
+

{teacher}'s Table

+ + + + + + + + + + + + + + + + + + + + + + + {teacherData.map((row, idx) => ( + + + + + + + + + + + + + + + + + + + ))} + +
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+ {/* CSV Download Button */} + + Download CSV + +
+ ); + })}
); }; diff --git a/client/src/Pages/CourseForm.css b/client/src/Pages/CourseForm.css index 1099137..e8e4714 100644 --- a/client/src/Pages/CourseForm.css +++ b/client/src/Pages/CourseForm.css @@ -115,3 +115,16 @@ button[type="submit"] { width: 100%; } } +.remove-faculty-btn { + padding: 0px 0px; + background: none; + border: none; + color: red; + cursor: pointer; + font-size: 16px; + margin-left: 10px; +} + +.remove-faculty-btn:hover { + color: darkred; +} \ No newline at end of file diff --git a/client/src/Pages/CourseForm.jsx b/client/src/Pages/CourseForm.jsx index 72c0416..bba7082 100644 --- a/client/src/Pages/CourseForm.jsx +++ b/client/src/Pages/CourseForm.jsx @@ -23,6 +23,15 @@ const CourseForm = () => { pwdPaperSetter: "", }); + const [tempAssignments, setTempAssignments] = useState({ + oralsPracticals: [], + assessment: [], + reassessment: [], + paperSetting: [], + moderation: [], + pwdPaperSetter: [], + }); + const [errors, setErrors] = useState({}); // Fetch faculty list on mount @@ -45,6 +54,15 @@ const CourseForm = () => { setFormData({ ...formData, [name]: value }); + if (value.trim() === "") { + // Clear suggestions if input is empty + setSuggestions((prev) => ({ + ...prev, + [name]: [], + })); + return; + } + // Filter suggestions for the current field if (options.faculties.length > 0) { const filteredSuggestions = options.faculties.filter((faculty) => @@ -57,49 +75,91 @@ const CourseForm = () => { } }; - // Validate the form + const handleAddFaculty = (field) => { + const selectedFaculty = options.faculties.find( + (faculty) => faculty.name === formData[field] + ); + + if (selectedFaculty) { + setTempAssignments((prev) => ({ + ...prev, + [field]: [...prev[field], selectedFaculty], + })); + setFormData({ ...formData, [field]: "" }); // Clear input field + setSuggestions((prev) => ({ ...prev, [field]: [] })); // Clear suggestions + } + }; + + const handleRemoveFaculty = (field, index) => { + setTempAssignments((prev) => { + const updatedAssignments = [...prev[field]]; // Create a shallow copy of the current list for this field + updatedAssignments.splice(index, 1); // Remove the faculty at the specified index + return { ...prev, [field]: updatedAssignments }; // Update the tempAssignments state + }); + }; + const validateForm = () => { const newErrors = {}; - Object.keys(formData).forEach((field) => { - if (!formData[field]) { - newErrors[field] = "This field is required"; + + // Validate that each field in tempAssignments has at least one assigned faculty + Object.keys(tempAssignments).forEach((field) => { + if (!tempAssignments[field] || tempAssignments[field].length === 0) { + newErrors[field] = "At least one faculty must be assigned."; } }); + setErrors(newErrors); - return Object.keys(newErrors).length === 0; + return Object.keys(newErrors).length === 0; // Form is valid if no errors }; // Handle form submission const handleSubmit = async (e) => { e.preventDefault(); // Prevent default form submission behavior + + // Validate the form based on tempAssignments if (validateForm()) { try { const groupedTasks = {}; - // Group tasks by facultyId - Object.entries(formData).forEach(([field, value]) => { - const assignedFaculty = options.faculties.find( - (faculty) => faculty.name === value - ); - if (assignedFaculty) { - if (!groupedTasks[assignedFaculty.facultyId]) { - groupedTasks[assignedFaculty.facultyId] = { - facultyId: assignedFaculty.facultyId, - courseId: course?.courseId || id, - tasks: [], - }; + // Transform tempAssignments into grouped tasks by facultyId + Object.entries(tempAssignments).forEach(([field, facultyList]) => { + facultyList.forEach((faculty) => { + // Assuming faculty is an object, not just the faculty name + const assignedFaculty = options.faculties.find( + (optionFaculty) => optionFaculty.facultyId === faculty.facultyId + ); + + if (assignedFaculty) { + // Check if the facultyId already exists in groupedTasks + if (!groupedTasks[assignedFaculty.facultyId]) { + groupedTasks[assignedFaculty.facultyId] = { + facultyId: assignedFaculty.facultyId, + courseId: course?.courseId || id, + tasks: [], + }; + } + // Push the task (field) into the tasks array for that faculty + groupedTasks[assignedFaculty.facultyId].tasks.push(field); } - groupedTasks[assignedFaculty.facultyId].tasks.push(field); - } + }); }); - const payload = Object.values(groupedTasks); // Convert the grouped tasks into an array + console.log(groupedTasks); + const payload = Object.values(groupedTasks); // Convert grouped tasks into an array console.log("Saving appointment with payload:", payload); - await saveAppointment(payload); // Save to backend + + if (payload.length === 0) { + throw new Error("No assignments to submit."); + } + + // Call API to save appointments + await saveAppointment(payload); await updateCourseStatus(course?.courseId || id); + console.log("Form submitted successfully:", payload); - const filteredCourses = JSON.parse(localStorage.getItem("filteredCourses")) || []; + const filteredCourses = + JSON.parse(localStorage.getItem("filteredCourses")) || []; // Redirect to courses page after successful submission navigate("/courses", { @@ -111,8 +171,6 @@ const CourseForm = () => { }, }, }); - - } catch (error) { console.error("Failed to save appointment:", error); } @@ -149,7 +207,21 @@ const CourseForm = () => { onChange={handleInputChange} placeholder={`Search faculty for ${label}`} /> -