-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resolves #103 currently the course search bar is fetching data from backend running on localhost, remember to change to Production API URL when merging to prod branch
- Loading branch information
Showing
1 changed file
with
159 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import { TextField, Autocomplete, CircularProgress } from '@mui/material'; | ||
|
||
interface CourseInfo { | ||
id: string; | ||
courseName: string; | ||
courseNumber: string; | ||
preReqs: string; | ||
coReqs: string; | ||
units: string; | ||
} | ||
|
||
const CourseSearchBar: React.FC = () => { | ||
const [open, setOpen] = useState(false); | ||
const [searchText, setSearchText] = useState(''); | ||
const [options, setOptions] = useState<CourseInfo[]>([]); | ||
const [selectedCourse, setSelectedCourse] = useState<CourseInfo | null>(null); | ||
const [loading, setLoading] = useState(false); | ||
|
||
useEffect(() => { | ||
const delayDebounce = setTimeout(() => { | ||
if (searchText === '') { | ||
// setOptions([]); | ||
return; | ||
} | ||
|
||
setLoading(true); | ||
fetch( | ||
// change to prod api url when the backend endpoints are updated | ||
// `https://api.cppbroncodirect.me/courses?key=${encodeURIComponent(searchText))}` | ||
`http://localhost:3000/courses?key=${encodeURIComponent(searchText)}` | ||
) | ||
.then(async (response) => await response.json()) | ||
.then((data) => { | ||
setOptions(data); | ||
}) | ||
.catch((error) => { | ||
console.error(`Error fetching courses:`, error); | ||
}) | ||
.finally(() => { | ||
setLoading(false); | ||
}); | ||
}, 2000); // 2-second delay | ||
|
||
return () => clearTimeout(delayDebounce); | ||
}, [searchText]); | ||
|
||
const fetchCourseDetails = async (courseNumber: string): Promise<void> => { | ||
try { | ||
const response = await fetch( | ||
// change to prod api url when the backend endpoints are updated | ||
// `https://api.cppbroncodirect.me/courses/${courseNumber}` | ||
`http://localhost:3000/courses/${courseNumber}` | ||
); | ||
if (!response.ok) { | ||
throw new Error('Network response was not ok'); | ||
} | ||
const data: CourseInfo = await response.json(); | ||
setSelectedCourse(data); | ||
} catch (error) { | ||
console.error('Error fetching course details:', error); | ||
setSelectedCourse(null); | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Autocomplete | ||
id="course-search" | ||
sx={{ | ||
width: '100%', | ||
}} | ||
open={open} | ||
onOpen={() => { | ||
setOpen(true); | ||
}} | ||
onClose={() => { | ||
setOpen(false); | ||
}} | ||
getOptionLabel={(option) => | ||
`${option.courseNumber}: ${option.courseName}` | ||
} | ||
options={options} | ||
loading={loading} | ||
onChange={(event, newValue: CourseInfo | null) => { | ||
if (newValue) { | ||
fetchCourseDetails(newValue.courseNumber).catch((e) => {}); | ||
} | ||
}} | ||
renderInput={(params) => ( | ||
<TextField | ||
{...params} | ||
onChange={(e) => setSearchText(e.target.value)} | ||
placeholder="Search for a course" | ||
variant="outlined" | ||
InputProps={{ | ||
...params.InputProps, | ||
endAdornment: ( | ||
<> | ||
{loading && <CircularProgress color="inherit" size={20} />} | ||
{params.InputProps.endAdornment} | ||
</> | ||
), | ||
}} | ||
/> | ||
)} | ||
/> | ||
{selectedCourse && ( | ||
<div | ||
style={{ | ||
marginTop: 10, | ||
padding: 5, | ||
}} | ||
> | ||
<h2>{selectedCourse.courseName}</h2> | ||
<span style={{ fontSize: 14 }}> | ||
{( | ||
[ | ||
{ name: 'Course ID', value: selectedCourse.courseNumber }, | ||
{ name: 'Units', value: selectedCourse.units }, | ||
{ | ||
name: 'Prerequisites', | ||
value: selectedCourse.preReqs, | ||
hidden: !selectedCourse.preReqs, | ||
}, | ||
{ | ||
name: 'Corequisites', | ||
value: selectedCourse.coReqs, | ||
hidden: !selectedCourse.coReqs, | ||
}, | ||
] as CourseFieldProps[] | ||
).map((field, index) => ( | ||
<CourseField key={index} {...field} /> | ||
))} | ||
</span> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CourseSearchBar; | ||
|
||
interface CourseFieldProps { | ||
name: string; | ||
value: string; | ||
hidden?: boolean; | ||
} | ||
const CourseField: React.FC<CourseFieldProps> = ({ | ||
name, | ||
value, | ||
hidden = false, | ||
}) => { | ||
return hidden ? null : ( | ||
<p> | ||
<strong>{name}:</strong> {value} | ||
</p> | ||
); | ||
}; |