diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index 9d6f45d6a..84387818a 100644 --- a/frontend/src/Berkeleytime.tsx +++ b/frontend/src/Berkeleytime.tsx @@ -22,6 +22,7 @@ const Berkeleytime = () => { }); useEffect(() => { + observe(document.getElementById('root')); // Fetch enrollment context early on for catalog and enrollment page. dispatch(fetchEnrollContext()); @@ -41,15 +42,9 @@ const Berkeleytime = () => { if (localStorage.getItem(key) === null) { localStorage.setItem(key, key); } - }, [dispatch]); - - return ( -
- - - -
- ); + }, [dispatch, observe]); + + return ; }; export default memo(Berkeleytime); diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss index b9f198436..3d9c3a67b 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss +++ b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss @@ -103,9 +103,13 @@ } } -.grade { +.gradeWrapper { display: flex; - justify-content: center; + flex-direction: column; + + span { + line-height: 20px; + } } .A { diff --git a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx index f5370a199..583329872 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx @@ -55,7 +55,7 @@ const CatalogListItem = ({ style, data }: CatalogListItemProps) => {
{`${course.abbreviation} ${course.courseNumber}`}

{course.title}

-
+
{user && (
{ {isSaved ? : }
)} - + {course.letterAverage !== '' ? course.letterAverage : ''}
- {formatEnrollment(course.enrolledPercentage)} + {formatEnrollment(course.enrolledPercentage)} enrolled • {course.units ? formatUnits(course.units) : 'N/A'}
diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index bb576282e..751d10755 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -23,7 +23,7 @@ flex-direction: column; padding: 30px; gap: 15px; - overflow-y: auto; + overflow-y: overlay; overflow-x: hidden; -webkit-animation: fadeIn 0.1s; animation: fadeIn 0.1s; @@ -37,10 +37,6 @@ color: $bt-base-text; font-weight: 700; font-size: 18px; - - span { - color: $bt-blue; - } } h6 { @@ -210,29 +206,59 @@ .sectionItem { display: flex; flex-direction: row; - // background: #F8F8F8; - padding: 12px 24px; - border-radius: 12px !important; + padding: 12px; + border-radius: 16px; border: 1.5px solid #eaeaea; - gap: 20px; - h5 { + gap: 30px; + + h6 { font-size: 16px; + min-height: 21px; margin: 0; + font-weight: bold; + color: $bt-base-text; } +} + +.sectionInfo { + display: flex; + flex-direction: column; + flex: 1; h6 { - font-size: 14px; - span { - text-transform: capitalize; - } + display: flex; + align-items: center; + min-height: 24px; } } -.sectionInfo { +.instructor { display: flex; gap: 5px; + align-items: center; + font-size: 14px; + color: $bt-light-grey; + text-transform: capitalize; + gap: 4px; + min-height: 24px; + color: $bt-light-text; +} + +.sectionContent { + display: flex; flex-direction: column; - flex: 1; + justify-content: flex-start; + font-size: 14px; + + span { + display: inline-flex; + justify-content: flex-end; + align-items: center; + line-height: 20px; + gap: 4px; + color: $bt-light-text; + text-align: right; + } } .sectionStats { @@ -241,14 +267,14 @@ flex-wrap: wrap; gap: 5px; color: $bt-light-text; - margin-top: 5px; + margin-top: 12px; font-size: 14px; text-transform: none !important; } .enrolled { display: flex; - justify-content: center; + justify-content: flex-end; align-items: center; font-size: 14px; font-weight: 600; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index de4f1c8a7..f2362c5a2 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -44,7 +44,7 @@ const CatalogView = (props: CatalogViewProps) => { )?.id ?? null ); - const [getCourse, { data, loading }] = useGetCourseForNameLazyQuery({ + const [getCourse, { data }] = useGetCourseForNameLazyQuery({ onCompleted: (data) => { const course = data.allCourses.edges[0].node; if (course) { @@ -89,10 +89,15 @@ const CatalogView = (props: CatalogViewProps) => { const [playlists, sections] = useMemo(() => { let playlists = null; let sections = null; + // let semesters = null; if (course?.playlistSet) { const { edges } = course.playlistSet; playlists = catalogService.sortPills(edges.map((e) => e.node as PlaylistType)); + + // semesters = catalogService.sortSemestersByLatest( + // edges.map((e) => e.node).filter((n) => n.category === 'semester') + // ); } if (course?.sectionSet) { @@ -100,6 +105,7 @@ const CatalogView = (props: CatalogViewProps) => { sections = sortSections(edges.map((e) => e.node)); } + // return [playlists ?? skeleton, sections ?? [], semesters]; return [playlists ?? skeleton, sections ?? null]; }, [course]); @@ -132,19 +138,19 @@ const CatalogView = (props: CatalogViewProps) => { return (
- {course && ( <> +

{course.abbreviation} {course.courseNumber}

@@ -211,16 +217,7 @@ const CatalogView = (props: CatalogViewProps) => {
Class Times - {semester ?? ''}
- {sections && sections.length > 0 ? ( - - ) : !loading ? ( - There are no class times for the selected course. - ) : null} - - {/* - Redesigned catalog sections - - */} + {/* Good feature whenever we want...
Past Offerings
diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index f3b70bf21..326dcd811 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -1,6 +1,8 @@ import { SectionFragment } from 'graphql'; import { CSSProperties } from 'react'; -import { Table } from 'react-bootstrap'; +import { formatSectionTime } from 'utils/sections/section'; +import catalogService from '../service'; +import Skeleton from 'react-loading-skeleton'; import denero from 'assets/img/eggs/denero.png'; import hug from 'assets/img/eggs/hug.png'; @@ -9,9 +11,13 @@ import sahai from 'assets/img/eggs/sahai.png'; import scott from 'assets/img/eggs/scott.png'; import kubi from 'assets/img/eggs/kubi.png'; import garcia from 'assets/img/eggs/garcia.png'; -import { formatSectionTime } from 'utils/sections/section'; +import { Clock, Group, PinAlt, User } from 'iconoir-react'; + +import styles from './CatalogView.module.scss'; -const easterEggImages = new Map([ +const { colorEnrollment } = catalogService; + +const easterEggImages = new Map([ ['DENERO J', denero], ['HUG J', hug], ['SAHAI A', sahai], @@ -35,52 +41,66 @@ function findInstructor(instr: string | null): CSSProperties { return {}; } -type Props = { +interface Props { sections: SectionFragment[] | null; -}; +} const SectionTable = ({ sections }: Props) => { + if (!sections) { + return ( + + ); + } + return ( -
-
- - - - - - - - - - - - - - {sections?.map((section) => { - return ( - - - - - {section.startTime && section.endTime ? ( - - ) : ( - - )} - - - - - ); - })} - -
TypeCCNInstructorTimeLocationEnrolledWaitlist
{section.kind}{section.ccn}{section.instructor} - {section.wordDays} {formatSectionTime(section)} - {section.locationName} +
+ {sections.length > 0 ? ( + sections.map((section) => { + const color = colorEnrollment(section.enrolled / section.enrolledMax); + + return ( +
+
+
{section.kind}
+ + + {section.instructor?.toLowerCase() || 'unknown'} + +
+
+ {section.enrolled}/{section.enrolledMax} -
{section.waitlisted}
-
-
+
+
• {section.waitlisted} Waitlisted
+ {/* • CCN {section.ccn} */} + + +
+ + + {section.locationName || 'Unknown'} + + + + {section.wordDays} {formatSectionTime(section)} + +
+ + ); + }) + ) : ( +
There are no class sections for this course.
+ )} + ); }; diff --git a/frontend/src/app/Catalog/CatalogView/__new_SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/__new_SectionTable.tsx deleted file mode 100644 index a33b596c1..000000000 --- a/frontend/src/app/Catalog/CatalogView/__new_SectionTable.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { SectionFragment } from 'graphql'; -import { CSSProperties } from 'react'; -import { formatSectionTime } from 'utils/sections/section'; -import catalogService from '../service'; -import Skeleton from 'react-loading-skeleton'; - -import people from 'assets/svg/catalog/people.svg'; -import denero from 'assets/img/eggs/denero.png'; -import hug from 'assets/img/eggs/hug.png'; -import hilf from 'assets/img/eggs/hilf.png'; -import sahai from 'assets/img/eggs/sahai.png'; -import scott from 'assets/img/eggs/scott.png'; -import kubi from 'assets/img/eggs/kubi.png'; -import garcia from 'assets/img/eggs/garcia.png'; - -import styles from './CatalogView.module.scss'; - -const { colorEnrollment, formatEnrollment } = catalogService; - -const easterEggImages = new Map([ - ['DENERO J', denero], - ['HUG J', hug], - ['SAHAI A', sahai], - ['HILFINGER P', hilf], - ['SHENKER S', scott], - ['KUBIATOWICZ J', kubi], - ['GARCIA D', garcia] -]); - -function findInstructor(instr: string | null): CSSProperties { - if (instr === null) return {}; - - for (const [name, eggUrl] of easterEggImages) { - if (instr.includes(name)) { - return { - cursor: `url("${eggUrl}"), pointer` - } as CSSProperties; - } - } - - return {}; -} - -type Props = { - sections: SectionFragment[] | null; -}; - -const CatalogViewSections = ({ sections }: Props) => { - if (!sections) { - return ( - - ); - } - - return ( -
- {sections.length > 0 ? ( - sections.map((section) => ( -
-
-
- {section.kind} -{' '} - {section.locationName ? section.locationName : 'Unknown Location'} -
-
- {section?.instructor?.toLowerCase() ?? 'instructor'},{' '} - {section.wordDays} {formatSectionTime(section)} -
- - - {formatEnrollment(section.enrolled / section.enrolledMax)} - - • {section.waitlisted} waitlisted - • CCN: {section.ccn} - -
-
- - {section.enrolled}/{section.enrolledMax} -
-
- )) - ) : ( -
There are no class sections for this course.
- )} -
- ); -}; - -export default CatalogViewSections; diff --git a/frontend/src/app/Catalog/service.ts b/frontend/src/app/Catalog/service.ts index cf410484e..1fd66d5c4 100644 --- a/frontend/src/app/Catalog/service.ts +++ b/frontend/src/app/Catalog/service.ts @@ -272,8 +272,12 @@ export const sortByName = (arr: T) => arr.sort((a, b) => a.name.localeCompare(b.name)); function formatEnrollment(percentage: number) { - if (percentage === -1) return 'N/A'; - return `${Math.floor(percentage * 100)}% enrolled`; + if (percentage === -1 || isNaN(percentage)) return 'N/A'; + /* + Take the min of the percentage and 100 to prevent + absurd percentages from being displayed (e.g. 1000% enrolled) + */ + return `${Math.min(Math.floor(percentage * 100), 100)}%`; } function colorEnrollment(percentage: number) { diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 401078b17..404bff4fd 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,7 @@ import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { ApolloProvider } from '@apollo/client'; +import { IconoirProvider } from 'iconoir-react'; import Berkeleytime from './Berkeleytime'; import store from './redux/store'; diff --git a/frontend/src/utils/sections/section.ts b/frontend/src/utils/sections/section.ts index 15f5743b7..40b69c63c 100644 --- a/frontend/src/utils/sections/section.ts +++ b/frontend/src/utils/sections/section.ts @@ -56,7 +56,7 @@ export const formatSectionTime = (section: SectionFragment, showNoTime = true): section.startTime && section.endTime ? `${formatTime(section.startTime)} \u{2013} ${formatTime(section.endTime)}` : showNoTime - ? `no time` + ? `Unknown time` : ''; export const formatSectionEnrollment = (section: SectionFragment): ReactNode =>