From 20ecaeed481b0456d8558f912bcb5c36e09659f7 Mon Sep 17 00:00:00 2001 From: Gavin Liu <85533492+liug88@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:32:13 -0500 Subject: [PATCH 01/10] Initial API Changes First API Testing! --- src/components/SearchCourse.jsx | 118 ++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index fd5ddc8..af9635f 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -1,4 +1,5 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import axios from 'axios'; import Select from 'react-select'; import Courses from '../components/Courses'; @@ -77,34 +78,6 @@ const styles = { }, }; -const courseData = Array.from({ length: 2000 }, (_, index) => ({ - id: `Test ${index + 1}`, - name: `CourseName ${index + 1}`, - credits: 4, - description: `This is a brief description of Course ${index + 1}.`, -})); - -const subjectOptions = [ - { value: '', label: 'All Subjects' }, - { value: 'programming', label: 'Programming' }, - { value: 'math', label: 'Math' }, - { value: 'science', label: 'Science' }, -]; - -const attributeOptions = [ - { value: '', label: 'All Attributes' }, - { value: 'programming', label: 'Programming' }, - { value: 'math', label: 'Math' }, - { value: 'science', label: 'Science' }, -]; - -const semesterOptions = [ - { value: '', label: 'All Semesters' }, - { value: 'Fall', label: 'Fall' }, - { value: 'Spring', label: 'Spring' }, - { value: 'Summer', label: 'Summer' }, -]; - const customStyles = { control: (base) => ({ ...base, @@ -135,8 +108,84 @@ const customStyles = { indicatorSeparator: () => ({ display: 'none' }), }; +const subjectOptions = [ + { value: '', label: 'All Subjects' }, + { value: 'programming', label: 'Programming' }, + { value: 'math', label: 'Math' }, + { value: 'science', label: 'Science' }, + // Add more subjects as needed +]; + +const attributeOptions = [ + { value: '', label: 'All Attributes' }, + { value: 'online', label: 'Online' }, + { value: 'in-person', label: 'In-Person' }, + // Add more attributes as needed +]; + +const semesterOptions = [ + { value: '', label: 'All Semesters' }, + { value: 'Fall', label: 'Fall' }, + { value: 'Spring', label: 'Spring' }, + { value: 'Summer', label: 'Summer' }, + // Add more semesters as needed +]; + const SearchCourse = () => { const [showFilterOptions, setShowFilterOptions] = useState(false); + const [courses, setCourses] = useState([]); + const [searchPrompt, setSearchPrompt] = useState(''); + const [selectedSubjects, setSelectedSubjects] = useState([]); + const [selectedAttributes, setSelectedAttributes] = useState([]); + const [selectedSemesters, setSelectedSemesters] = useState([]); + + useEffect(() => { + const fetchCourses = async () => { + try { + const response = await axios.get('/api/v1/course/all'); + setCourses(response.data); + } catch (error) { + console.error('Error fetching courses:', error); + } + }; + + fetchCourses(); + }, []); + + const handleSearch = async () => { + const params = new URLSearchParams(); + + if (searchPrompt) { + params.append('searchPrompt', searchPrompt); + } + if (selectedSubjects.length > 0) { + params.append( + 'deptFilters', + selectedSubjects.map((s) => s.value).join(','), + ); + } + if (selectedAttributes.length > 0) { + params.append( + 'attrFilters', + selectedAttributes.map((a) => a.value).join(','), + ); + } + if (selectedSemesters.length > 0) { + params.append( + 'semFilters', + selectedSemesters.map((s) => s.value).join(','), + ); + } + + try { + const response = await axios.get( + `/api/v1/course/search?${params.toString()}`, + ); + setCourses(response.data); + } catch (error) { + console.error('Error searching courses:', error); + } + }; return (
@@ -146,6 +195,8 @@ const SearchCourse = () => { type="text" style={styles.input} placeholder="Find Courses Here..." + value={searchPrompt} + onChange={(e) => setSearchPrompt(e.target.value)} />
{ {!showFilterOptions && closed icon} {showFilterOptions && closed icon}
+
{showFilterOptions && (
@@ -169,6 +221,8 @@ const SearchCourse = () => { name="subject" options={subjectOptions} styles={customStyles} + onChange={setSelectedSubjects} + isMulti />
@@ -181,6 +235,8 @@ const SearchCourse = () => { name="attribute" options={attributeOptions} styles={customStyles} + onChange={setSelectedAttributes} + isMulti />
@@ -193,12 +249,14 @@ const SearchCourse = () => { name="semester" options={semesterOptions} styles={customStyles} + onChange={setSelectedSemesters} + isMulti />
)}
- +
); From de5715f9d55e444e1dfef30f7cf7d1993ac03e46 Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:42:54 -0500 Subject: [PATCH 02/10] Included AXIOS package Added AXIOS to package --- package-lock.json | 32 ++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 33 insertions(+) diff --git a/package-lock.json b/package-lock.json index c86f0a5..f1a5074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", "framer-motion": "^11.9.0", "react": "^18.3.1", "react-dnd": "^16.0.1", @@ -5569,6 +5570,31 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -14875,6 +14901,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index 411a892..9dc27e3 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.7", "framer-motion": "^11.9.0", "react": "^18.3.1", "react-dnd": "^16.0.1", From 0b8a9e72e5a0459e4a2da83cc71b6354c3fb7e0c Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:44:27 -0500 Subject: [PATCH 03/10] env dependency and env example env --- .gitignore | 1 + examples.env | 1 + package-lock.json | 22 ++++++++++++++++++---- package.json | 1 + 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 examples.env diff --git a/.gitignore b/.gitignore index fc81720..1248287 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # testing /coverage +.env # production /build diff --git a/examples.env b/examples.env new file mode 100644 index 0000000..6a2b176 --- /dev/null +++ b/examples.env @@ -0,0 +1 @@ +REACT_APP_API_HOST=YOURHOSTHERE \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f1a5074..ce80e39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.7", + "dotenv": "^16.4.5", "framer-motion": "^11.9.0", "react": "^18.3.1", "react-dnd": "^16.0.1", @@ -7338,11 +7339,15 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -15400,6 +15405,15 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, "node_modules/react-select": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.2.tgz", diff --git a/package.json b/package.json index 9dc27e3..6206772 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.7", + "dotenv": "^16.4.5", "framer-motion": "^11.9.0", "react": "^18.3.1", "react-dnd": "^16.0.1", From e02e64484c5cdef6d041c92dcc24a8581856d0f0 Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:49:23 -0500 Subject: [PATCH 04/10] Display response from API Display course ID and Code, course name. Display credits as range if range exist. --- src/components/Courses.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/Courses.jsx b/src/components/Courses.jsx index b7c2854..d259708 100644 --- a/src/components/Courses.jsx +++ b/src/components/Courses.jsx @@ -13,6 +13,13 @@ const Courses = ({ courses }) => { }); }; + const displayCredit = (minCredit, maxCredit) => { + if (minCredit === maxCredit) { + return `${minCredit} Credits`; + } + return `${minCredit} - ${maxCredit} Credits`; + }; + return (
{ >

- {course.id} + {course.department}-{course.code}

- {course.name} + {course.title}

- {course.credits} Credits + {displayCredit(course.creditMin, course.creditMax)}

{openCourses[index] && ( From d6b58142928b326a899d12e4a6bc14ad4e5ca33c Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:50:40 -0500 Subject: [PATCH 05/10] Fix search - Fixed fetching from API - Search on enter --- src/components/SearchCourse.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index af9635f..357f23e 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -131,6 +131,8 @@ const semesterOptions = [ // Add more semesters as needed ]; +const HOST = process.env.REACT_APP_API_HOST; + const SearchCourse = () => { const [showFilterOptions, setShowFilterOptions] = useState(false); const [courses, setCourses] = useState([]); @@ -142,7 +144,7 @@ const SearchCourse = () => { useEffect(() => { const fetchCourses = async () => { try { - const response = await axios.get('/api/v1/course/all'); + const response = await axios.get(`${HOST}/api/v1/course/all`); setCourses(response.data); } catch (error) { console.error('Error fetching courses:', error); @@ -179,7 +181,7 @@ const SearchCourse = () => { try { const response = await axios.get( - `/api/v1/course/search?${params.toString()}`, + `${HOST}/api/v1/course/search?${params.toString()}`, ); setCourses(response.data); } catch (error) { @@ -197,6 +199,7 @@ const SearchCourse = () => { placeholder="Find Courses Here..." value={searchPrompt} onChange={(e) => setSearchPrompt(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} />
Date: Tue, 19 Nov 2024 17:38:22 -0500 Subject: [PATCH 06/10] Course Search Changes Background darker for clarity Course Attributes and Semesters offered are now underneath the course description --- src/components/Courses.jsx | 52 +++++++++++++++++++++++---------- src/components/SearchCourse.jsx | 28 +++++++++++++----- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/components/Courses.jsx b/src/components/Courses.jsx index d259708..f16ac7a 100644 --- a/src/components/Courses.jsx +++ b/src/components/Courses.jsx @@ -1,18 +1,4 @@ -import { useState } from 'react'; - -const Courses = ({ courses }) => { - const [openCourses, setOpenCourses] = useState( - Array(courses.length).fill(false), - ); - - const toggleDropdown = (index) => { - setOpenCourses((prev) => { - const newOpenCourses = [...prev]; - newOpenCourses[index] = !newOpenCourses[index]; - return newOpenCourses; - }); - }; - +const Courses = ({ courses, openCourses, toggleDropdown }) => { const displayCredit = (minCredit, maxCredit) => { if (minCredit === maxCredit) { return `${minCredit} Credits`; @@ -46,7 +32,25 @@ const Courses = ({ courses }) => {
{openCourses[index] && (
-

{course.description}

+

{course.description || 'No description'}

+
+ {course.semesterList.length > 0 ? ( +
+ Offered: {course.semesterList.join(', ')} +
+ ) : ( +
No semesters offered
+ )} +
+
+ {course.attributeList.length > 0 ? ( +
+ Attributes: {course.attributeList.join(', ')} +
+ ) : ( +
No attributes
+ )} +
)} @@ -97,6 +101,22 @@ const styles = { marginTop: '0.5em', fontSize: '1em', color: '#555', + backgroundColor: '#e0e0e0', // Slightly darker background color + borderRadius: '8px', + padding: '1em', + }, + bubbleContainer: { + display: 'flex', + flexWrap: 'wrap', + gap: '0.5em', + marginTop: '0.5em', + }, + bubble: { + backgroundColor: '#d0d0d0', // Slightly darker background color for bubbles + borderRadius: '15px', + padding: '0.5em 1em', + fontSize: '0.9em', + color: '#333', }, }; diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index 357f23e..fff3f63 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -1,7 +1,6 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; import Select from 'react-select'; - import Courses from '../components/Courses'; import filterIcon from '../assets/images/filter.svg'; import expandedIcon from '../assets/images/expanded.svg'; @@ -18,15 +17,15 @@ const styles = { backgroundColor: 'rgb(254, 226, 226)', borderRadius: '0.75rem', fontFamily: 'Single Day, cursive', - height: '656px', // Set a fixed height - overflow: 'hidden', // Hide overflow to prevent height shift + height: '656px', + overflow: 'hidden', }, coursesContainer: { flexGrow: 1, borderRadius: '0.75rem', overflowY: 'auto', paddingBottom: '10px', - maxHeight: 'calc(100% - 100px)', // Adjust max height to account for filter options + maxHeight: 'calc(100% - 100px)', }, heading: { textAlign: 'left', @@ -67,8 +66,8 @@ const styles = { backgroundColor: '#f3f4f6', boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)', borderRadius: '0.5rem', - overflowY: 'auto', // Enable vertical scrolling for filter options - maxHeight: '150px', // Set a max height for filter options + overflowY: 'auto', + maxHeight: '150px', }, attribute: { fontSize: '15px', @@ -140,12 +139,14 @@ const SearchCourse = () => { const [selectedSubjects, setSelectedSubjects] = useState([]); const [selectedAttributes, setSelectedAttributes] = useState([]); const [selectedSemesters, setSelectedSemesters] = useState([]); + const [openCourses, setOpenCourses] = useState([]); useEffect(() => { const fetchCourses = async () => { try { const response = await axios.get(`${HOST}/api/v1/course/all`); setCourses(response.data); + setOpenCourses(Array(response.data.length).fill(false)); } catch (error) { console.error('Error fetching courses:', error); } @@ -184,11 +185,20 @@ const SearchCourse = () => { `${HOST}/api/v1/course/search?${params.toString()}`, ); setCourses(response.data); + setOpenCourses(Array(response.data.length).fill(false)); } catch (error) { console.error('Error searching courses:', error); } }; + const toggleDropdown = (index) => { + setOpenCourses((prev) => { + const newOpenCourses = [...prev]; + newOpenCourses[index] = !newOpenCourses[index]; + return newOpenCourses; + }); + }; + return (

Search Course

@@ -259,7 +269,11 @@ const SearchCourse = () => {
)}
- +
); From 5f599233eb6074bc4cf0e0e85df5d3f96a61cbe7 Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:13:26 -0500 Subject: [PATCH 07/10] Show Select Attribute Menu on top - Shows select attribute menu on top - Edited width of menu --- src/components/SearchCourse.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index fff3f63..1b71759 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -66,20 +66,19 @@ const styles = { backgroundColor: '#f3f4f6', boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)', borderRadius: '0.5rem', - overflowY: 'auto', - maxHeight: '150px', }, attribute: { fontSize: '15px', color: '#8D8D8D', marginLeft: '10px', - marginBottom: '-10px', + marginBottom: '-5px', }, }; const customStyles = { control: (base) => ({ ...base, + display: 'flex', backgroundColor: 'none', border: 'none', lineHeight: '20px', @@ -102,7 +101,6 @@ const customStyles = { }), menu: (base) => ({ ...base, - width: '400px', }), indicatorSeparator: () => ({ display: 'none' }), }; @@ -235,6 +233,7 @@ const SearchCourse = () => { options={subjectOptions} styles={customStyles} onChange={setSelectedSubjects} + menuPosition={'fixed'} isMulti /> @@ -249,6 +248,7 @@ const SearchCourse = () => { options={attributeOptions} styles={customStyles} onChange={setSelectedAttributes} + menuPosition={'fixed'} isMulti /> @@ -263,6 +263,7 @@ const SearchCourse = () => { options={semesterOptions} styles={customStyles} onChange={setSelectedSemesters} + menuPosition={'fixed'} isMulti /> From 0c884355f704c07446f28e7c39540e1413afbd5e Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:24:26 -0500 Subject: [PATCH 08/10] Updated Blurb Margins Updated Blurb Margins --- src/components/Courses.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Courses.jsx b/src/components/Courses.jsx index f16ac7a..2c4fe53 100644 --- a/src/components/Courses.jsx +++ b/src/components/Courses.jsx @@ -98,7 +98,9 @@ const styles = { }, CourseBlurb: { marginLeft: '2em', + marginRight: '2em', marginTop: '0.5em', + marginBottom: '0.5em', fontSize: '1em', color: '#555', backgroundColor: '#e0e0e0', // Slightly darker background color From 3c5b12e56bc0664d037ca402719061e4d2a77b75 Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:25:09 -0500 Subject: [PATCH 09/10] Save selected attributes on opening on closing filters Filters are saved saved when it is opened and closed. --- src/components/SearchCourse.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index 1b71759..8cee344 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -232,6 +232,7 @@ const SearchCourse = () => { name="subject" options={subjectOptions} styles={customStyles} + value={selectedSubjects} onChange={setSelectedSubjects} menuPosition={'fixed'} isMulti @@ -247,6 +248,7 @@ const SearchCourse = () => { name="attribute" options={attributeOptions} styles={customStyles} + value={selectedAttributes} onChange={setSelectedAttributes} menuPosition={'fixed'} isMulti @@ -262,6 +264,7 @@ const SearchCourse = () => { name="semester" options={semesterOptions} styles={customStyles} + value={selectedSemesters} onChange={setSelectedSemesters} menuPosition={'fixed'} isMulti From 11e047c418d572b904452b412087e53cdb85b831 Mon Sep 17 00:00:00 2001 From: Edwin Zhao <99381274+venxer@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:28:52 -0500 Subject: [PATCH 10/10] Fixed merged conflict Fixed merged conflict with toolbox --- src/components/SearchCourse.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SearchCourse.jsx b/src/components/SearchCourse.jsx index e544974..894ea93 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -130,7 +130,7 @@ const semesterOptions = [ const HOST = process.env.REACT_APP_API_HOST; -const SearchCourse = () => { +const SearchCourse = ({ isToolboxExpanded }) => { const [showFilterOptions, setShowFilterOptions] = useState(false); const [courses, setCourses] = useState([]); const [searchPrompt, setSearchPrompt] = useState('');