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 c86f0a5..ce80e39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@testing-library/jest-dom": "^5.17.0", "@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", @@ -5569,6 +5571,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", @@ -7312,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": { @@ -14875,6 +14906,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", @@ -15368,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 411a892..6206772 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "@testing-library/jest-dom": "^5.17.0", "@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", diff --git a/src/components/Courses.jsx b/src/components/Courses.jsx index b7c2854..2c4fe53 100644 --- a/src/components/Courses.jsx +++ b/src/components/Courses.jsx @@ -1,16 +1,9 @@ -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`; + } + return `${minCredit} - ${maxCredit} Credits`; }; return ( @@ -27,19 +20,37 @@ const Courses = ({ courses }) => { >

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

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

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

{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
+ )} +
)} @@ -87,9 +98,27 @@ const styles = { }, CourseBlurb: { marginLeft: '2em', + marginRight: '2em', marginTop: '0.5em', + marginBottom: '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 01edf16..894ea93 100644 --- a/src/components/SearchCourse.jsx +++ b/src/components/SearchCourse.jsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; +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'; @@ -17,7 +17,7 @@ const styles = { backgroundColor: 'rgb(254, 226, 226)', borderRadius: '0.75rem', fontFamily: 'Single Day, cursive', - height: '32em', + height: '656px', overflow: 'hidden', }, coursesContainer: { @@ -25,7 +25,7 @@ const styles = { borderRadius: '0.75rem', overflowY: 'auto', paddingBottom: '10px', - maxHeight: 'calc(100% - 90px)', + maxHeight: 'calc(100% - 100px)', }, heading: { textAlign: 'left', @@ -71,41 +71,14 @@ const styles = { fontSize: '15px', color: '#8D8D8D', marginLeft: '10px', - marginBottom: '-10px', + marginBottom: '-5px', }, }; -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, + display: 'flex', backgroundColor: 'none', border: 'none', lineHeight: '20px', @@ -128,13 +101,101 @@ const customStyles = { }), menu: (base) => ({ ...base, - width: '400px', }), 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 HOST = process.env.REACT_APP_API_HOST; + const SearchCourse = ({ isToolboxExpanded }) => { 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([]); + 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); + } + }; + + 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( + `${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 (
{ type="text" style={styles.input} placeholder="Find Courses Here..." + value={searchPrompt} + onChange={(e) => setSearchPrompt(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} />
{ {!showFilterOptions && closed icon} {showFilterOptions && closed icon}
+
{showFilterOptions && (
@@ -172,6 +237,10 @@ const SearchCourse = ({ isToolboxExpanded }) => { name="subject" options={subjectOptions} styles={customStyles} + value={selectedSubjects} + onChange={setSelectedSubjects} + menuPosition={'fixed'} + isMulti />
@@ -184,6 +253,10 @@ const SearchCourse = ({ isToolboxExpanded }) => { name="attribute" options={attributeOptions} styles={customStyles} + value={selectedAttributes} + onChange={setSelectedAttributes} + menuPosition={'fixed'} + isMulti />
@@ -196,12 +269,20 @@ const SearchCourse = ({ isToolboxExpanded }) => { name="semester" options={semesterOptions} styles={customStyles} + value={selectedSemesters} + onChange={setSelectedSemesters} + menuPosition={'fixed'} + isMulti />
)}
- +
);