From a0a54d213e2a6a85bbc8d068b0278170bef4d140 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:11:48 +0000 Subject: [PATCH 1/9] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c574d05..15798be 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "clsx": "^2.1.1", "csv-parser": "^3.0.0", "debounce": "^2.1.1", + "isomorphic-dompurify": "^2.16.0", "lodash": "^4.17.21", "lodash.debounce": "^4.0.8", "lru-cache": "^11.0.1", From d1dc9d4f6eb174d8511afd87167fe4a9539b03f3 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:12:43 +0000 Subject: [PATCH 2/9] Update FAQDropdown.tsx --- src/app/components/FAQDropdown.tsx | 69 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/app/components/FAQDropdown.tsx b/src/app/components/FAQDropdown.tsx index adcc797..09a32f3 100644 --- a/src/app/components/FAQDropdown.tsx +++ b/src/app/components/FAQDropdown.tsx @@ -1,46 +1,45 @@ import { useState } from "react"; +import DOMPurify from "isomorphic-dompurify"; interface FAQDropdownProps { - question: string; - answer: string; + question: string; + answer: string; } const FAQDropdown: React.FC = ({ question, answer }) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); - const toggleDropdown = () => { - setIsOpen((prev) => !prev); - }; + const toggleDropdown = () => { + setIsOpen((prev) => !prev); + }; - return ( -
- - {isOpen && ( -
-
-
- )} -
- ); + // Sanitize the answer content + const sanitizedAnswer = DOMPurify.sanitize(answer); + + return ( +
+ + {isOpen && ( +
+
+
+ )} +
+ ); }; export default FAQDropdown; From c8e4d82882146ec04e9bc31db4dae99eea3d1a4d Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:13:25 +0000 Subject: [PATCH 3/9] Update page.tsx --- src/app/faq/page.tsx | 108 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 56 deletions(-) diff --git a/src/app/faq/page.tsx b/src/app/faq/page.tsx index 302cdbf..2ea4c56 100644 --- a/src/app/faq/page.tsx +++ b/src/app/faq/page.tsx @@ -53,61 +53,57 @@ const FAQs = [ ]; const FAQPage = () => { - return ( -
-
- (window.location.href = "/")} - className="text-2xl cursor-pointer ml-4 mt-1 text-gray-300" - aria-label="Home" - /> -
-
-
-

- - MAV - - - GRADES - -

-
-
-

- - Frequently Asked Questions - + return ( +
+ {/* Home Icon */} +
+ (window.location.href = "/")} + className="text-3xl cursor-pointer text-gray-300 hover:text-gray-100 transition-colors duration-200" + aria-label="Home" + /> +
+ + {/* Logo */} +
+

+ MAV + GRADES

-
- {FAQs.map((faq, index) => ( - - ))} -
- -
- Developed by{" "} - - ACM @ UTA - - . Not affiliated with or sponsored by UT Arlington. -
© 2024{" "} - - ACM @ UT Arlington - - . All rights reserved. -
- ); - }; - - export default FAQPage; - \ No newline at end of file + + {/* Page Title */} +

+ Frequently Asked Questions +

+ + {/* FAQ Items */} +
+ {FAQs.map((faq, index) => ( + + ))} +
+ + {/* Footer */} +
+ Developed by{" "} + + ACM @ UTA + + . Not affiliated with or sponsored by UT Arlington. +
© {new Date().getFullYear()}{" "} + + ACM @ UT Arlington + + . All rights reserved. +
+
+ ); +}; + +export default FAQPage; From 28c5da27df58d7dcc094de7d140ef2476702b686 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:07:48 +0000 Subject: [PATCH 4/9] Update GradesInfoCard.tsx --- src/app/components/GradesInfoCard.tsx | 120 +++++++++++++------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/src/app/components/GradesInfoCard.tsx b/src/app/components/GradesInfoCard.tsx index ed6ff48..05a33f5 100644 --- a/src/app/components/GradesInfoCard.tsx +++ b/src/app/components/GradesInfoCard.tsx @@ -1,65 +1,61 @@ import React from "react"; -export const GradesInfoCard = () => { - return ( - <> -

- Grading Information -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GradeDescription
AExcellent
BGood
CFair
DPassing, Below Average
FFailure
IIncomplete
WWithdrawn
QWithdrawn - No Penalty
PPass
RResearch
ZNo Credit
- - ); +interface GradeInfo { + grade: string; + description: string; +} + +export const GradesInfoCard: React.FC = () => { + const grades: GradeInfo[] = [ + { grade: "A", description: "Excellent" }, + { grade: "B", description: "Good" }, + { grade: "C", description: "Fair" }, + { grade: "D", description: "Passing, Below Average" }, + { grade: "F", description: "Failure" }, + { grade: "I", description: "Incomplete" }, + { grade: "W", description: "Withdrawn" }, + { grade: "Q", description: "Withdrawn - No Penalty" }, + { grade: "P", description: "Pass" }, + { grade: "R", description: "Research" }, + { grade: "Z", description: "No Credit" }, + ]; + + return ( +
+

+ Grading Information +

+
+ + + + + + + + + + {grades.map((item, index) => ( + + + + + ))} + +
Grading Information Table
+ Grade + + Description +
{item.grade}{item.description}
+
+
+ ); }; From ef9268bd023a1dd265253d80cac6dd495e1f4218 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:08:01 +0000 Subject: [PATCH 5/9] Update SearchBar.tsx --- src/app/components/SearchBar.tsx | 230 ++++++++++++++++--------------- 1 file changed, 117 insertions(+), 113 deletions(-) diff --git a/src/app/components/SearchBar.tsx b/src/app/components/SearchBar.tsx index 12eb4f1..8160571 100644 --- a/src/app/components/SearchBar.tsx +++ b/src/app/components/SearchBar.tsx @@ -1,137 +1,141 @@ "use client"; -import React, { useState, useEffect, useCallback, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import debounce from "lodash.debounce"; import { FaSearch } from "react-icons/fa"; interface Suggestion { - suggestion: string; - type: string; + suggestion: string; + type: "professor" | "course"; } + interface SearchBarProps { - initialValue?: string; - resetState?: () => void; - course?: string; - professor?: string; + initialValue?: string; + resetState?: () => void; + course?: string; + professor?: string; } -export default function SearchBar({ - initialValue = "", - resetState, - course, - professor, -}: SearchBarProps) { - const [searchInput, setSearchInput] = useState(initialValue); - const [suggestions, setSuggestions] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); +const SearchBar: React.FC = ({ + initialValue = "", + resetState, + course, + professor, +}) => { + const [searchInput, setSearchInput] = useState(initialValue); + const [suggestions, setSuggestions] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const inputRef = useRef(null); - useEffect(() => { - setSearchInput(initialValue); // Update input when initialValue changes - }, [initialValue]); + // Debounced fetchSuggestions function + const fetchSuggestions = useRef( + debounce(async (input: string) => { + if (input.length > 1) { + setIsLoading(true); + try { + const response = await fetch(`/api/courses/search?query=${input}`); + const data = await response.json(); + setSuggestions(data); + } catch (error) { + console.error("Error fetching suggestions:", error); + setSuggestions([]); + } finally { + setIsLoading(false); + } + } else { + setSuggestions([]); + } + }, 300) // Debounce time increased to reduce backend calls + ).current; - // Create a debounced version of the fetchSuggestions function - const fetchSuggestions = useRef( - debounce(async (input: string) => { - if (input.length > 1) { - setIsLoading(true); // Start loading - try { - const response = await fetch( - `/api/courses/search?query=${input}` - ); - const data = await response.json(); - setSuggestions(data); - } catch (error) { - console.error("Error fetching suggestions:", error); - setSuggestions([]); // Clear suggestions on error - } finally { - setIsLoading(false); // Stop loading - } - } else { - setSuggestions([]); - } - }, 100) // Decrease the debounce time for quicker suggestion response, but might lead to more backend calls - ).current; + useEffect(() => { + return () => { + fetchSuggestions.cancel(); // Clean up on unmount + }; + }, [fetchSuggestions]); - useEffect(() => { - return () => { - fetchSuggestions.cancel(); // Clean up on unmount - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + setSearchInput(initialValue); + }, [initialValue]); - const handleSearch = (suggestion: string) => { + const handleSearch = (query: string) => { + setSearchInput(query); + setSuggestions([]); - setSearchInput(suggestion); - setSuggestions([]); + if (!(course === query || professor === query)) { + resetState && resetState(); + } - if (!(course === suggestion || professor === suggestion)) { - if (resetState) { - resetState(); - } - } - // Check if the suggestion is a professor or a course - const isProfessor = suggestions.find( - (s) => s.suggestion === suggestion && s.type === "professor" - ); + // Find the suggestion type + const suggestionItem = suggestions.find((s) => s.suggestion === query); + const isProfessor = suggestionItem?.type === "professor"; - // Splitting the input string to extract the subject_id and course_number - const parts = suggestion.split(' '); - if (parts.length >= 2) { - const coursePrefix = parts[0]; - const courseNumber = parts[1]; + if (isProfessor) { + router.push(`/results?professor=${encodeURIComponent(query)}`); + } else { + router.push(`/results?course=${encodeURIComponent(query)}`); + } + }; - // Check if the second part is a four-digit number - if (courseNumber.length === 4 && !isNaN(Number(courseNumber))) { - suggestion = `${coursePrefix} ${courseNumber}`; - } - } + const handleInputChange = (e: React.ChangeEvent) => { + const input = e.target.value; + setSearchInput(input); + fetchSuggestions(input); + }; - if (isProfessor) { - router.push(`/results?professor=${encodeURIComponent(suggestion)}`); // Redirect to professor results + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + if (suggestions.length > 0) { + handleSearch(suggestions[0].suggestion); } else { - router.push(`/results?course=${encodeURIComponent(suggestion)}`); // Redirect to course results + handleSearch(searchInput); } - }; + } + }; - const handleInputChange = (e: React.ChangeEvent) => { - const input = e.target.value; - setSearchInput(input); - fetchSuggestions(input); // Call the debounced function - }; + const handleSuggestionClick = (suggestion: string) => { + handleSearch(suggestion); + inputRef.current?.blur(); // Remove focus from input after selection + }; - return ( -
-
- e.key === 'Enter' && handleSearch(suggestions[0].suggestion)} - className="w-full p-3 border border-gray-500 rounded-xl shadow-sm focus:outline-none focus:border-blue-500 bg-white bg-opacity-10" - /> - handleSearch(suggestions[0].suggestion)} - className="absolute right-3 top-1/2 transform -translate-y-1/2 cursor-pointer text-gray-300 w-4 h-4" - /> -
- {isLoading && ( -
- )} - {suggestions.length > 0 && !isLoading && ( -
    - {suggestions.map((suggestion, index) => ( -
  • handleSearch(suggestion.suggestion)} - className="px-4 py-2 hover:bg-blue-100 cursor-pointer text-black" - > - {suggestion.suggestion} -
  • - ))} -
- )} + return ( +
+
+ + handleSearch(searchInput)} + className="absolute right-3 top-1/2 transform -translate-y-1/2 cursor-pointer text-gray-300 w-4 h-4" + />
- ); -} + {isLoading && ( +
+ Loading... +
+ )} + {suggestions.length > 0 && !isLoading && ( +
    + {suggestions.map((suggestion, index) => ( +
  • handleSuggestionClick(suggestion.suggestion)} + className="px-4 py-2 hover:bg-blue-100 cursor-pointer text-black" + > + {suggestion.suggestion} +
  • + ))} +
+ )} +
+ ); +}; + +export default SearchBar; From 78770b8dad73c4f76f175138157d63c1e47301c4 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:08:11 +0000 Subject: [PATCH 6/9] Update SelectionDropdowns.tsx --- src/app/components/SelectionDropdowns.tsx | 375 +++++++++++----------- 1 file changed, 179 insertions(+), 196 deletions(-) diff --git a/src/app/components/SelectionDropdowns.tsx b/src/app/components/SelectionDropdowns.tsx index 7b70913..e7d209c 100644 --- a/src/app/components/SelectionDropdowns.tsx +++ b/src/app/components/SelectionDropdowns.tsx @@ -1,207 +1,190 @@ import React, { useEffect } from "react"; -// TODO: import this from SideBar.tsx +interface CourseSection { + subject_id: string; + course_number: string; + semester: string; + year: string; + section_number: string; +} + interface SelectionDropdownsProps { - selectedProfessor: string; - selectedCourseSubject: string | null; - selectedYear: string | null; - setSelectedYear: (year: string | null) => void; - selectedSemester: string | null; - setSelectedSemester: (semester: string | null) => void; - finalFilteredCourses: Array<{ - subject_id: string; - course_number: string; - semester: string; - year: string; - section_number: string; - }>; - selectedCourse: string; - selectedSection: { - subject_id: string; - course_number: string; - semester: string; - year: string; - section_number: string; - } | null; - setSelectedSection: ( - section: { - subject_id: string; - course_number: string; - semester: string; - year: string; - section_number: string; - } | null - ) => void; - years: string[]; - semesters: string[]; + selectedProfessor: string; + selectedCourseSubject: string | null; + selectedYear: string | null; + setSelectedYear: (year: string | null) => void; + selectedSemester: string | null; + setSelectedSemester: (semester: string | null) => void; + finalFilteredCourses: CourseSection[]; + selectedCourse: string; + selectedSection: CourseSection | null; + setSelectedSection: (section: CourseSection | null) => void; + years: string[]; + semesters: string[]; } const SelectionDropdowns: React.FC = ({ - selectedProfessor, - selectedCourseSubject, - selectedYear, - setSelectedYear, - selectedSemester, - setSelectedSemester, - finalFilteredCourses, - selectedCourse, - selectedSection, - setSelectedSection, - years, - semesters, + selectedProfessor, + selectedCourseSubject, + selectedYear, + setSelectedYear, + selectedSemester, + setSelectedSemester, + finalFilteredCourses, + selectedCourse, + selectedSection, + setSelectedSection, + years, + semesters, }) => { - // Set the latest year and semester when the professor or course changes - useEffect(() => { - const courseMatches = finalFilteredCourses.filter( - (course) => - `${course.subject_id} ${course.course_number}` === selectedCourse - ); - - if (courseMatches.length > 0) { - // Sort courses to find the latest year and semester - const sortedCourses = [...courseMatches].sort( - (a, b) => - parseInt(b.year) - parseInt(a.year) || - semesters.indexOf(b.semester) - semesters.indexOf(a.semester) - ); - - const latestCourse = sortedCourses[0]; - - // Only set year, semester, and section if not already set - if (!selectedYear) setSelectedYear(latestCourse.year); - if (!selectedSemester) setSelectedSemester(latestCourse.semester); - if (!selectedSection) setSelectedSection(latestCourse); - } - }, [ - selectedProfessor, - selectedCourse, - finalFilteredCourses, - semesters, - selectedYear, - selectedSemester, - selectedSection, - setSelectedYear, - setSelectedSemester, - setSelectedSection, - ]); - return ( -
-

- {`Sections for Professor: ${selectedProfessor}`} -

-
{" "} - {/* Year Dropdown */} -
- - -
- {/* Semester Dropdown */} -
- - -
- {/* Section List */} - {selectedYear && selectedSemester && ( -
- - -
- )} + // Set the latest year and semester when the professor or course changes + useEffect(() => { + if (!selectedCourse) return; + + const courseMatches = finalFilteredCourses.filter( + (course) => `${course.subject_id} ${course.course_number}` === selectedCourse + ); + + if (courseMatches.length > 0) { + // Sort courses to find the latest year and semester + const sortedCourses = [...courseMatches].sort((a, b) => { + // Compare years + const yearDifference = parseInt(b.year) - parseInt(a.year); + if (yearDifference !== 0) return yearDifference; + + // Compare semesters + return semesters.indexOf(b.semester) - semesters.indexOf(a.semester); + }); + + const latestCourse = sortedCourses[0]; + + // Only set year, semester, and section if not already set + if (!selectedYear) setSelectedYear(latestCourse.year); + if (!selectedSemester) setSelectedSemester(latestCourse.semester); + if (!selectedSection) setSelectedSection(latestCourse); + } + }, [ + selectedProfessor, + selectedCourse, + finalFilteredCourses, + semesters, + selectedYear, + selectedSemester, + selectedSection, + setSelectedYear, + setSelectedSemester, + setSelectedSection, + ]); + + // Handlers + const handleYearChange = (e: React.ChangeEvent) => { + setSelectedYear(e.target.value); + setSelectedSemester(null); + setSelectedSection(null); + }; + + const handleSemesterChange = (e: React.ChangeEvent) => { + setSelectedSemester(e.target.value); + setSelectedSection(null); + }; + + const handleSectionChange = (e: React.ChangeEvent) => { + const selectedSectionNumber = e.target.value; + const selected = finalFilteredCourses.find( + (course) => + course.section_number === selectedSectionNumber && + course.year === selectedYear && + course.semester === selectedSemester && + `${course.subject_id} ${course.course_number}` === selectedCourse + ); + setSelectedSection(selected || null); + }; + + // Filtered sections based on selections + const filteredSections = finalFilteredCourses.filter( + (course) => + course.year === selectedYear && + course.semester === selectedSemester && + `${course.subject_id} ${course.course_number}` === selectedCourse + ); + + return ( +
+

+ {`Sections for Professor: ${selectedProfessor}`} +

+
+ + {/* Year Dropdown */} +
+ + +
+ + {/* Semester Dropdown */} +
+ +
- ); + + {/* Section Dropdown */} + {selectedYear && selectedSemester && ( +
+ + +
+ )} +
+ ); }; export default SelectionDropdowns; From 693f5acb68c5dde0ed8280e1845610363e5b33d6 Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:08:20 +0000 Subject: [PATCH 7/9] Update SideBar.tsx --- src/app/components/SideBar.tsx | 513 +++++++++++++++------------------ 1 file changed, 225 insertions(+), 288 deletions(-) diff --git a/src/app/components/SideBar.tsx b/src/app/components/SideBar.tsx index 6403039..09145de 100644 --- a/src/app/components/SideBar.tsx +++ b/src/app/components/SideBar.tsx @@ -3,306 +3,243 @@ import SelectionDropdowns from "./SelectionDropdowns"; import ToggleSwitch from "./ToggleSwitch"; export interface Course { - subject_id: string; - course_number: string; - instructor1: string; - section_number: string; - semester: string; - year: string; - course_gpa: string; - grades_count: number; - grades_A: number; - grades_B: number; - grades_C: number; - grades_D: number; - grades_F: number; - grades_I: number; - grades_P: number; - grades_Q: number; - grades_W: number; - grades_Z: number; - grades_R: number; + subject_id: string; + course_number: string; + instructor1: string; + section_number: string; + semester: string; + year: string; + course_gpa: string; + grades_count: number; + grades_A: number; + grades_B: number; + grades_C: number; + grades_D: number; + grades_F: number; + grades_I: number; + grades_P: number; + grades_Q: number; + grades_W: number; + grades_Z: number; + grades_R: number; } interface SideBarProps { - professors: string[]; - selectedProfessor: string | undefined; - setSelectedProfessor: (professor: string | undefined) => void; - years: string[]; - selectedYear: string | null; - setSelectedYear: (year: string | null) => void; - selectedCourse: string | undefined; - setSelectedCourse: (course: string | undefined) => void; - coursesToDisplay: any[]; - setCoursesToDisplay: (course: Course[]) => void; - semesters: string[]; - selectedSemester: string | null; - setSelectedSemester: (semester: string | null) => void; - finalFilteredCourses: any[]; - selectedSection: any | null; - setSelectedSection: (section: any | null) => void; - routeType: "course" | "professor" | null; - selectedItems: Map; - setSelectedItems: React.Dispatch>>; + professors: string[]; + selectedProfessor?: string; + setSelectedProfessor: (professor?: string) => void; + years: string[]; + selectedYear: string | null; + setSelectedYear: (year: string | null) => void; + selectedCourse?: string; + setSelectedCourse: (course?: string) => void; + coursesToDisplay: Course[]; + semesters: string[]; + selectedSemester: string | null; + setSelectedSemester: (semester: string | null) => void; + finalFilteredCourses: Course[]; + selectedSection: Course | null; + setSelectedSection: (section: Course | null) => void; + routeType: "course" | "professor" | null; + selectedItems: Map; + setSelectedItems: React.Dispatch>>; } const SideBar: React.FC = ({ - professors, - selectedProfessor, - setSelectedProfessor, - years, - selectedYear, - selectedCourse, - coursesToDisplay, - setCoursesToDisplay, - setSelectedCourse, - setSelectedYear, - semesters, - selectedSemester, - setSelectedSemester, - finalFilteredCourses, - selectedSection, - setSelectedSection, - routeType, - selectedItems, - setSelectedItems, + professors, + selectedProfessor, + setSelectedProfessor, + years, + selectedYear, + setSelectedYear, + selectedCourse, + setSelectedCourse, + coursesToDisplay, + semesters, + selectedSemester, + setSelectedSemester, + finalFilteredCourses, + selectedSection, + setSelectedSection, + routeType, + selectedItems, + setSelectedItems, }) => { - const [openProfessorAccordion, setOpenProfessorAccordion] = useState< - number | null - >(null); - const [openCourseAccordion, setOpenCourseAccordion] = useState< - number | null - >(null); - const [checkboxEnabled, setCheckboxEnabled] = useState(false); - - // State to store whether a checkbox has been checked for a professor/course - const [checkboxState, setCheckboxState] = useState>( - new Map() - ); - - // State to track if selections for year, semester, and section are complete for each professor/course - const [isSelectionComplete, setIsSelectionComplete] = useState< - Map - >(new Map()); - - const handleToggleChange = (enabled: boolean) => { - setCheckboxEnabled(enabled); - setSelectedItems(new Map()); - // Deselect all checkboxes - setCheckboxState(new Map()); - }; - const onToggle = (isEnabled: any) => { - setCheckboxEnabled(isEnabled); - handleToggleChange(isEnabled); - }; - - // Function to handle checkbox state - const handleCheckboxChange = (isChecked: boolean, key: string) => { - if (isChecked) { - if (selectedItems.size >= 3) { - alert("You can only select up to 3 for comparison."); - return; - } - const selectionsComplete = isSelectionComplete.get(key); - - if (selectionsComplete) { - setSelectedItems((prevMap) => - new Map(prevMap).set(key, selectedSection) - ); - } else { - // Set the value as null if selections are incomplete - setSelectedItems((prevMap) => new Map(prevMap).set(key, null)); - } - } else { - // Remove the key from the map when unchecked - setSelectedItems((prevMap) => { - const newMap = new Map(prevMap); - newMap.delete(key); - return newMap; - }); + const [openAccordionIndex, setOpenAccordionIndex] = useState(null); + const [checkboxEnabled, setCheckboxEnabled] = useState(false); + + // State to store the checkbox states + const [checkboxState, setCheckboxState] = useState>(new Map()); + + // State to track if selections are complete for each professor/course + const [isSelectionComplete, setIsSelectionComplete] = useState>(new Map()); + + // Handle toggle switch change + const handleToggleChange = (enabled: boolean) => { + setCheckboxEnabled(enabled); + setSelectedItems(new Map()); + setCheckboxState(new Map()); + }; + + // Function to handle checkbox state + const handleCheckboxChange = (isChecked: boolean, key: string) => { + if (isChecked) { + if (selectedItems.size >= 3) { + alert("You can only select up to 3 for comparison."); + return; } - - // Store the checkbox state - setCheckboxState((prevMap) => new Map(prevMap).set(key, isChecked)); - }; - - // Effect to update when year, semester, or section changes for a particular professor/course - useEffect(() => { - if (selectedProfessor) { - const allSelectionsMade = - selectedYear && selectedSemester && selectedSection; - - setIsSelectionComplete((prevMap) => - new Map(prevMap).set(selectedProfessor, allSelectionsMade) - ); - - if (checkboxState.get(selectedProfessor) && allSelectionsMade) { - setSelectedItems((prevMap) => - new Map(prevMap).set(selectedProfessor, selectedSection) - ); - } + const selectionsComplete = isSelectionComplete.get(key) || false; + setSelectedItems((prevMap) => new Map(prevMap).set(key, selectionsComplete ? selectedSection : null)); + } else { + setSelectedItems((prevMap) => { + const newMap = new Map(prevMap); + newMap.delete(key); + return newMap; + }); + } + setCheckboxState((prevMap) => new Map(prevMap).set(key, isChecked)); + }; + + // Effect to update selection completeness + useEffect(() => { + const key = routeType === "course" ? selectedProfessor : selectedCourse; + if (key) { + const allSelectionsMade = !!selectedYear && !!selectedSemester && !!selectedSection; + setIsSelectionComplete((prevMap) => new Map(prevMap).set(key, allSelectionsMade)); + + if (checkboxState.get(key) && allSelectionsMade) { + setSelectedItems((prevMap) => new Map(prevMap).set(key, selectedSection)); } - - if (selectedCourse) { - const allSelectionsMade = - selectedYear && selectedSemester && selectedSection; - - setIsSelectionComplete((prevMap) => - new Map(prevMap).set(selectedCourse, allSelectionsMade) - ); - - // If all selections are made and the course is checked, update the selectedItems map - if (checkboxState.get(selectedCourse) && allSelectionsMade) { - setSelectedItems((prevMap) => - new Map(prevMap).set(selectedCourse, selectedSection) + } + }, [ + selectedYear, + selectedSemester, + selectedSection, + selectedProfessor, + selectedCourse, + checkboxState, + routeType, + setSelectedItems, + ]); + + const toggleAccordion = (index: number, key: string) => { + setOpenAccordionIndex(openAccordionIndex === index ? null : index); + if (routeType === "course") { + setSelectedProfessor(key); + } else { + setSelectedCourse(key); + } + }; + + return ( +
+ {routeType === "course" && ( +
+ Compare professors + +
+ )} + + {routeType === "course" ? ( +
    + {professors.map((professor, index) => { + const isOpen = openAccordionIndex === index; + return ( +
  • +
    + {checkboxEnabled && ( + handleCheckboxChange(e.target.checked, professor)} + /> + )} +
    toggleAccordion(index, professor)} + > +

    {professor}

    + {isOpen ? "-" : "+"} +
    +
    + + {isOpen && ( +
    e.stopPropagation()} + > + +
    + )} +
  • ); - } - } - }, [ - selectedYear, - selectedSemester, - selectedSection, - selectedProfessor, - selectedCourse, - checkboxState, - ]); - - const toggleProfessorAccordion = (index: number, professor: string) => { - setOpenProfessorAccordion( - openProfessorAccordion === index ? null : index - ); - setSelectedProfessor(professor); - }; - - const toggleCourseAccordion = (index: number, course: string) => { - setOpenCourseAccordion(openCourseAccordion === index ? null : index); - setSelectedCourse(course); - }; - - return ( -
    - {routeType === "course" && ( -
    - Compare professors - -
    - )} - - {routeType === "course" ? ( -
      - {professors.map((professor, index) => ( -
    • - toggleProfessorAccordion(index, professor) - } + })} +
    + ) : routeType === "professor" ? ( +
      + {coursesToDisplay.map((course, index) => { + const courseKey = `${course.subject_id} ${course.course_number}`; + const isOpen = openAccordionIndex === index; + return ( +
    • +
      +
      toggleAccordion(index, courseKey)} > -
      - {checkboxEnabled && ( - { - handleCheckboxChange( - e.target.checked, - professor - ); - toggleProfessorAccordion(index, professor); - }} - /> - )} -
      -

      - {professor} -

      - - {openProfessorAccordion === index ? "-" : "+"} - -
      -
      - - {openProfessorAccordion === index && ( -
      event.stopPropagation()} // Ignore onClick event from course box - > - -
      - )} -
    • - ))} -
    - ) : routeType === "professor" ? ( -
      - {coursesToDisplay.map((course, index) => ( -
    • - toggleCourseAccordion( - index, - `${course.subject_id} ${course.course_number}` - ) - } +

      {courseKey}

      + {isOpen ? "-" : "+"} +
    +
+ + {isOpen && ( +
e.stopPropagation()} > -
-
-

- {`${course.subject_id} ${course.course_number}`} -

- - {openCourseAccordion === index ? "-" : "+"} - -
-
- - {openCourseAccordion === index && ( -
event.stopPropagation()} // Ignore onClick event from professor box - > - -
- )} - - ))} - - ) : null} -
- ); + +
+ )} + + ); + })} + + ) : null} +
+ ); }; export default SideBar; From f0ac30dd3c4f74168a211067b9986b918e5df46f Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:08:31 +0000 Subject: [PATCH 8/9] Update StatsCard.tsx --- src/app/components/StatsCard.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/components/StatsCard.tsx b/src/app/components/StatsCard.tsx index 5dce693..5ad5f37 100644 --- a/src/app/components/StatsCard.tsx +++ b/src/app/components/StatsCard.tsx @@ -99,9 +99,10 @@ const StatsCard = ({ selectedItems }: { selectedItems: Map }) => {
)}

- {aggregatedData[0]?.subject_id && aggregatedData[0]?.course_number - ? `${aggregatedData[0].subject_id} ${aggregatedData[0].course_number} ${aggregatedData[0].course_title ? aggregatedData[0].course_title : ''}` - : "Course Information"} + {aggregatedData[0]?.subject_id && + aggregatedData[0]?.course_number + ? `${aggregatedData[0].subject_id} ${aggregatedData[0].course_number}` + : "Course Information"}

{" "} {/* Render differently depending on the number of items */} From 2fcd05176e6aeea27626f302a9e854972164f2eb Mon Sep 17 00:00:00 2001 From: xchar08 <108827236+xchar08@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:08:40 +0000 Subject: [PATCH 9/9] Update ToggleSwitch.tsx --- src/app/components/ToggleSwitch.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app/components/ToggleSwitch.tsx b/src/app/components/ToggleSwitch.tsx index b72e6f5..06d8864 100644 --- a/src/app/components/ToggleSwitch.tsx +++ b/src/app/components/ToggleSwitch.tsx @@ -3,23 +3,29 @@ import React from "react"; interface ToggleSwitchProps { isEnabled: boolean; onToggle: (value: boolean) => void; + label?: string; } -const ToggleSwitch: React.FC = ({ isEnabled, onToggle }) => { +const ToggleSwitch: React.FC = ({ isEnabled, onToggle, label }) => { return ( -
onToggle(!isEnabled)} + className={`lg:mt-1 relative inline-flex items-center ${ + isEnabled ? "bg-green-500" : "bg-gray-300" + } rounded-full w-11 h-6 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500`} > + {label || "Toggle"} -
+ ); }; export default ToggleSwitch; +