From 1dd8adbd399c565f4f1e8952e2dc02f85dedb06d Mon Sep 17 00:00:00 2001 From: Jaden Date: Wed, 15 Mar 2023 22:59:11 -0700 Subject: [PATCH 01/42] init section table redesign --- .../CatalogView/CatalogView.module.scss | 19 ++-- .../app/Catalog/CatalogView/CatalogView.tsx | 13 +-- .../app/Catalog/CatalogView/SectionTable.tsx | 106 ++++++++++-------- .../CatalogView/__new_SectionTable.tsx | 103 ----------------- 4 files changed, 71 insertions(+), 170 deletions(-) delete mode 100644 frontend/src/app/Catalog/CatalogView/__new_SectionTable.tsx diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index b599930fb..6c4b38e52 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -37,10 +37,6 @@ color: $bt-base-text; font-weight: 700; font-size: 18px; - - span { - color: $bt-blue; - } } h6 { @@ -210,10 +206,8 @@ .sectionItem { display: flex; flex-direction: row; - // background: #F8F8F8; - padding: 12px 24px; - border-radius: 12px !important; - border: 1.5px solid #eaeaea; + padding: 6px 12px; + border-top: 1.5px solid #eaeaea; gap: 20px; h5 { font-size: 16px; @@ -222,9 +216,6 @@ h6 { font-size: 14px; - span { - text-transform: capitalize; - } } } @@ -233,6 +224,12 @@ gap: 5px; flex-direction: column; flex: 1; + + div { + font-size: 14px; + color: $bt-light-text; + text-transform: capitalize; + } } .sectionStats { diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index d370ddbca..a092b6192 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) { @@ -217,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 65d39eec4..5ba056fdf 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -1,7 +1,10 @@ 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 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'; @@ -9,7 +12,10 @@ 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 styles from './CatalogView.module.scss'; + +const { colorEnrollment, formatEnrollment } = catalogService; const easterEggImages = new Map([ ['DENERO J', denero], @@ -27,7 +33,7 @@ function findInstructor(instr: string | null): CSSProperties { for (const [name, eggUrl] of easterEggImages) { if (instr.includes(name)) { return { - '--section-cursor': `url("${eggUrl}")` + cursor: `url("${eggUrl}"), pointer` } as CSSProperties; } } @@ -39,49 +45,59 @@ type Props = { sections: SectionFragment[] | null; }; -const SectionTable = ({ sections }: Props) => { +const CatalogViewSections = ({ 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} - {section.enrolled}/{section.enrolledMax} - {section.waitlisted}
-
-
+
+ {sections.length > 0 ? ( + sections.map((section) => ( +
+
+
+ {section.kind} •{' '} + {section.locationName ? section.locationName : 'Unknown Location'} +
+
+ {section.wordDays} {formatSectionTime(section)} +
+
{section?.instructor?.toLowerCase() ?? 'No Instructor'}
+ + {/* + {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 SectionTable; +export default CatalogViewSections; 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; From edac4437f72e6d3c2ae953d1cc0e956d91a9c531 Mon Sep 17 00:00:00 2001 From: Jaden Date: Wed, 22 Mar 2023 16:53:59 -0700 Subject: [PATCH 02/42] revert design change --- .../Catalog/CatalogView/CatalogView.module.scss | 17 ++++++++++++----- .../app/Catalog/CatalogView/SectionTable.tsx | 8 ++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 6c4b38e52..a20845566 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -37,6 +37,10 @@ color: $bt-base-text; font-weight: 700; font-size: 18px; + + span { + color: $bt-blue; + } } h6 { @@ -206,8 +210,10 @@ .sectionItem { display: flex; flex-direction: row; - padding: 6px 12px; - border-top: 1.5px solid #eaeaea; + // background: #F8F8F8; + padding: 12px 24px; + border-radius: 12px !important; + border: 1.5px solid #eaeaea; gap: 20px; h5 { font-size: 16px; @@ -216,6 +222,9 @@ h6 { font-size: 14px; + span { + text-transform: capitalize; + } } } @@ -225,9 +234,7 @@ flex-direction: column; flex: 1; - div { - font-size: 14px; - color: $bt-light-text; + h6 { text-transform: capitalize; } } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 5ba056fdf..4d2d7d62f 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -72,15 +72,15 @@ const CatalogViewSections = ({ sections }: Props) => { {section.locationName ? section.locationName : 'Unknown Location'}
- {section.wordDays} {formatSectionTime(section)} + {section.wordDays} {formatSectionTime(section)},{' '} + {section?.instructor?.toLowerCase() ?? 'No Instructor'}
-
{section?.instructor?.toLowerCase() ?? 'No Instructor'}
{/* {formatEnrollment(section.enrolled / section.enrolledMax)} */} - {section.waitlisted} Waitlisted - • CCN: {section.ccn} + {section.ccn} + • {section.waitlisted} Waitlisted
Date: Tue, 4 Apr 2023 09:26:30 -0700 Subject: [PATCH 03/42] improve section design --- .../CatalogView/CatalogView.module.scss | 22 +++++++++++--- .../app/Catalog/CatalogView/SectionTable.tsx | 29 ++++++++++--------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index a20845566..bfda84d57 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; @@ -214,9 +214,10 @@ padding: 12px 24px; border-radius: 12px !important; border: 1.5px solid #eaeaea; - gap: 20px; + gap: 30px; h5 { font-size: 16px; + min-height: 21px; margin: 0; } @@ -230,7 +231,7 @@ .sectionInfo { display: flex; - gap: 5px; + gap: 6px; flex-direction: column; flex: 1; @@ -239,6 +240,19 @@ } } +.sectionContent { + display: flex; + flex-direction: column; + justify-content: flex-start; + font-size: 14px; + gap: 6px; + + span { + color: $bt-light-text; + text-align: right; + } +} + .sectionStats { display: flex; flex-direction: row; @@ -252,7 +266,7 @@ .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/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 4d2d7d62f..c471f0fee 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -72,24 +72,27 @@ const CatalogViewSections = ({ sections }: Props) => { {section.locationName ? section.locationName : 'Unknown Location'}
- {section.wordDays} {formatSectionTime(section)},{' '} - {section?.instructor?.toLowerCase() ?? 'No Instructor'} + {section.instructor ? section.instructor.toLowerCase() : 'Instructor not specified.'}
- {/* + {formatEnrollment(section.enrolled / section.enrolledMax)} - */} - {section.ccn} - • {section.waitlisted} Waitlisted + + • {section.waitlisted} WaitlistedCCN {section.ccn}
-
- - {section.enrolled}/{section.enrolledMax} +
+
+ + {section.enrolled}/{section.enrolledMax} +
+ + {section.wordDays} {formatSectionTime(section)} +
)) From ef2bd6f0f9904c9a16bd74be6115db7b9a01ecf8 Mon Sep 17 00:00:00 2001 From: Jaden Date: Wed, 5 Apr 2023 17:44:44 -0700 Subject: [PATCH 04/42] remove blue color --- frontend/src/app/Catalog/CatalogView/CatalogView.module.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index bfda84d57..139164f3c 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -37,10 +37,6 @@ color: $bt-base-text; font-weight: 700; font-size: 18px; - - span { - color: $bt-blue; - } } h6 { From c3582b520fbe1531e5ae404681a60712a55f5765 Mon Sep 17 00:00:00 2001 From: Jaden Date: Thu, 6 Apr 2023 11:30:23 -0700 Subject: [PATCH 05/42] simplify app structure --- frontend/src/Berkeleytime.tsx | 9 +++------ frontend/src/app/Catalog/Catalog.module.scss | 3 +-- .../CatalogList/CatalogList.module.scss | 2 +- frontend/src/index.tsx | 19 ++++++++++++------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index 1abe30cd2..da2396376 100644 --- a/frontend/src/Berkeleytime.tsx +++ b/frontend/src/Berkeleytime.tsx @@ -21,6 +21,7 @@ const Berkeleytime = () => { }); useEffect(() => { + observe(document.getElementById('root')); // Fetch enrollment context early on for catalog and enrollment page. dispatch(fetchEnrollContext()); @@ -40,13 +41,9 @@ const Berkeleytime = () => { if (localStorage.getItem(key) === null) { localStorage.setItem(key, key); } - }, [dispatch]); + }, [dispatch, observe]); - return ( -
- -
- ); + return ; }; export default memo(Berkeleytime); diff --git a/frontend/src/app/Catalog/Catalog.module.scss b/frontend/src/app/Catalog/Catalog.module.scss index e5c0ea5a7..676cb1576 100644 --- a/frontend/src/app/Catalog/Catalog.module.scss +++ b/frontend/src/app/Catalog/Catalog.module.scss @@ -4,8 +4,7 @@ grid-template-rows: 1fr; grid-template-columns: 0.5fr 0.5fr 1fr; position: relative; - padding-top: 57.11px; - height: 100vh; + height: calc(100vh - 57.5px); align-items: stretch; overflow: hidden; diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss index 538f1592b..7b0c58886 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss +++ b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss @@ -94,7 +94,7 @@ .grade { display: flex; - justify-content: center; + justify-content: flex-start; } .A { diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index eb7b012b1..7621714ca 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -2,12 +2,14 @@ import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { ApolloProvider } from '@apollo/client'; +import { IconoirProvider } from 'iconoir-react'; import ScrollToTop from './components/Common/ScrollToTop'; import LogPageView from './components/Common/LogPageView'; import Berkeleytime from './Berkeleytime'; import store from './redux/store'; import client from './graphql/client'; +import Banner from 'components/Common/Banner'; import 'assets/scss/berkeleytime.scss'; import 'react-loading-skeleton/dist/skeleton.css'; @@ -16,12 +18,15 @@ const root = createRoot(document.getElementById('root') as HTMLElement); root.render( - - - - - - - + + + + + + + + + + ); From 4f335a835509c5fd8c65cd1d1235be4faea41b3d Mon Sep 17 00:00:00 2001 From: Jaden Date: Thu, 6 Apr 2023 12:02:58 -0700 Subject: [PATCH 06/42] clean up mark-up --- .../CatalogList/CatalogList.module.scss | 9 ++- .../Catalog/CatalogList/CatalogListItem.tsx | 4 +- .../CatalogView/CatalogView.module.scss | 12 +-- .../app/Catalog/CatalogView/SectionTable.tsx | 78 ++++++++++--------- frontend/src/index.tsx | 2 +- 5 files changed, 56 insertions(+), 49 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss index 7b0c58886..fa323d2d7 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss +++ b/frontend/src/app/Catalog/CatalogList/CatalogList.module.scss @@ -92,9 +92,14 @@ } } -.grade { +.gradeWrapper { display: flex; - justify-content: flex-start; + flex-direction: column; + font-weight: 700; + + 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..882de3ba3 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 : ''}
diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index dd62c93bc..6f1397deb 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -227,13 +227,14 @@ .sectionInfo { display: flex; - gap: 6px; flex-direction: column; flex: 1; +} - h6 { - text-transform: capitalize; - } +.instructor { + font-size: 14px; + color: $bt-light-grey; + text-transform: capitalize; } .sectionContent { @@ -241,7 +242,6 @@ flex-direction: column; justify-content: flex-start; font-size: 14px; - gap: 6px; span { color: $bt-light-text; @@ -255,7 +255,7 @@ flex-wrap: wrap; gap: 5px; color: $bt-light-text; - margin-top: 5px; + margin-top: 12px; font-size: 14px; text-transform: none !important; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index c471f0fee..f94c4ffa6 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -17,7 +17,7 @@ import styles from './CatalogView.module.scss'; const { colorEnrollment, formatEnrollment } = catalogService; -const easterEggImages = new Map([ +const easterEggImages = new Map([ ['DENERO J', denero], ['HUG J', hug], ['SAHAI A', sahai], @@ -41,11 +41,11 @@ function findInstructor(instr: string | null): CSSProperties { return {}; } -type Props = { +interface Props { sections: SectionFragment[] | null; -}; +} -const CatalogViewSections = ({ sections }: Props) => { +const SectionTable = ({ sections }: Props) => { if (!sections) { return ( { return (
{sections.length > 0 ? ( - sections.map((section) => ( -
-
-
- {section.kind} •{' '} - {section.locationName ? section.locationName : 'Unknown Location'} -
-
- {section.instructor ? section.instructor.toLowerCase() : 'Instructor not specified.'} -
- - - {formatEnrollment(section.enrolled / section.enrolledMax)} + sections.map((section) => { + const colorStyle = colorEnrollment(section.enrolled / section.enrolledMax); + + return ( +
+
+
+ {section.kind} • {section.locationName ?? 'Unknown Location'} +
+ + {section.instructor + ? section.instructor.toLowerCase() + : 'Instructor not specified.'} + +
+ + {formatEnrollment(section.enrolled / section.enrolledMax)} + + • {section.waitlisted} Waitlisted + • CCN {section.ccn} +
+
+
+
+ + {section.enrolled}/{section.enrolledMax} +
+ + {section.wordDays} {formatSectionTime(section)} - • {section.waitlisted} WaitlistedCCN {section.ccn} - -
-
-
- - {section.enrolled}/{section.enrolledMax}
- - {section.wordDays} {formatSectionTime(section)} -
-
- )) + ); + }) ) : (
There are no class sections for this course.
)} @@ -103,4 +105,4 @@ const CatalogViewSections = ({ sections }: Props) => { ); }; -export default CatalogViewSections; +export default SectionTable; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 7621714ca..de86593b3 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -10,7 +10,7 @@ import Berkeleytime from './Berkeleytime'; import store from './redux/store'; import client from './graphql/client'; import Banner from 'components/Common/Banner'; - + import 'assets/scss/berkeleytime.scss'; import 'react-loading-skeleton/dist/skeleton.css'; From 569eb5a2c4001d7366847bf05231445fe359f710 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 14 Apr 2023 00:23:03 -0700 Subject: [PATCH 07/42] add icons --- .../CatalogView/CatalogView.module.scss | 11 ++++++++++- .../app/Catalog/CatalogView/SectionTable.tsx | 19 ++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 6f1397deb..a78f2ae2e 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -208,7 +208,7 @@ flex-direction: row; // background: #F8F8F8; padding: 12px 24px; - border-radius: 12px !important; + border-radius: 16px !important; border: 1.5px solid #eaeaea; gap: 30px; h5 { @@ -235,6 +235,10 @@ font-size: 14px; color: $bt-light-grey; text-transform: capitalize; + align-items: flex-end; + line-height: 20px; + gap: 4px; + color: $bt-light-text; } .sectionContent { @@ -244,6 +248,11 @@ font-size: 14px; span { + display: inline-flex; + justify-content: flex-end; + align-items: flex-end; + line-height: 20px; + gap: 4px; color: $bt-light-text; text-align: right; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index f94c4ffa6..65bd61f72 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -14,6 +14,7 @@ import kubi from 'assets/img/eggs/kubi.png'; import garcia from 'assets/img/eggs/garcia.png'; import styles from './CatalogView.module.scss'; +import { Clock, Group, PinAlt, User } from 'iconoir-react'; const { colorEnrollment, formatEnrollment } = catalogService; @@ -70,10 +71,9 @@ const SectionTable = ({ sections }: Props) => { key={section.ccn} >
-
- {section.kind} • {section.locationName ?? 'Unknown Location'} -
+
{section.kind}
+ {section.instructor ? section.instructor.toLowerCase() : 'Instructor not specified.'} @@ -87,13 +87,18 @@ const SectionTable = ({ sections }: Props) => {
-
- - {section.enrolled}/{section.enrolledMax} -
+ + {section.locationName ?? 'Unknown Location'} + + + {section.wordDays} {formatSectionTime(section)} +
+ + {section.enrolled}/{section.enrolledMax} +
); From 9bd1ef3c41d64be833a807f4485e126eb63587ad Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 14 Apr 2023 12:45:45 -0700 Subject: [PATCH 08/42] move enrollment info up --- .../app/Catalog/CatalogView/CatalogView.module.scss | 6 ++++-- frontend/src/app/Catalog/CatalogView/SectionTable.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index a78f2ae2e..1df590116 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -229,14 +229,16 @@ display: flex; flex-direction: column; flex: 1; + + h5 { + line-height: 24px; + } } .instructor { font-size: 14px; color: $bt-light-grey; text-transform: capitalize; - align-items: flex-end; - line-height: 20px; gap: 4px; color: $bt-light-text; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 65bd61f72..7f96fb280 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -73,7 +73,7 @@ const SectionTable = ({ sections }: Props) => {
{section.kind}
- + {section.instructor ? section.instructor.toLowerCase() : 'Instructor not specified.'} @@ -87,6 +87,10 @@ const SectionTable = ({ sections }: Props) => {
+
+ + {section.enrolled}/{section.enrolledMax} +
{section.locationName ?? 'Unknown Location'} @@ -95,10 +99,6 @@ const SectionTable = ({ sections }: Props) => { {section.wordDays} {formatSectionTime(section)} -
- - {section.enrolled}/{section.enrolledMax} -
); From 28be78e4f2b130752d9e6b6d8350e70eb38ffe2e Mon Sep 17 00:00:00 2001 From: Jaden Date: Sat, 13 May 2023 13:49:02 -0700 Subject: [PATCH 09/42] init catalog hook refactor --- frontend/src/app/Catalog/Catalog.tsx | 47 +---- .../Catalog/CatalogFilters/CatalogFilters.tsx | 120 ++++-------- .../app/Catalog/CatalogList/CatalogList.tsx | 85 +++------ .../Catalog/CatalogList/CatalogListItem.tsx | 4 +- .../app/Catalog/CatalogView/CatalogView.tsx | 89 +++------ .../CatalogView/__new_SectionTable.tsx | 4 +- frontend/src/app/Catalog/service.ts | 94 +++++----- frontend/src/app/Catalog/types.ts | 2 +- frontend/src/app/Catalog/useCatalog.tsx | 176 ++++++++++++++++++ frontend/src/lib/courses/sorting.ts | 2 +- 10 files changed, 318 insertions(+), 305 deletions(-) create mode 100644 frontend/src/app/Catalog/useCatalog.tsx diff --git a/frontend/src/app/Catalog/Catalog.tsx b/frontend/src/app/Catalog/Catalog.tsx index 476129367..809c8137a 100644 --- a/frontend/src/app/Catalog/Catalog.tsx +++ b/frontend/src/app/Catalog/Catalog.tsx @@ -1,53 +1,18 @@ -import { useEffect, useState } from 'react'; -import { CurrentFilters, SortOption } from './types'; -import catalogService from './service'; import styles from './Catalog.module.scss'; import CatalogFilters from './CatalogFilters'; import CatalogList from './CatalogList'; import CatalogView from './CatalogView'; -import { CourseFragment } from 'graphql'; -import { useLocation } from 'react-router'; - -const { SORT_OPTIONS, INITIAL_FILTERS } = catalogService; +import { CatalogProvider } from './useCatalog'; const Catalog = () => { - const [currentFilters, setCurrentFilters] = useState(INITIAL_FILTERS); - const [currentCourse, setCurrentCourse] = useState(null); - const [sortQuery, setSortQuery] = useState(SORT_OPTIONS[0]); - const [searchQuery, setSearchQuery] = useState(''); - const location = useLocation(); - const [sortDir, setDir] = useState(false); - - useEffect(() => { - const params = new URLSearchParams(location.search); - if (params.has('q')) setSearchQuery(params.get('q') ?? ''); - }, [location.search]); return (
- - - + + + + +
); }; diff --git a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx index fdc70bc3b..218a8b166 100644 --- a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx +++ b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx @@ -1,77 +1,49 @@ -import { - Dispatch, - memo, - SetStateAction, - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { ActionMeta } from 'react-select'; import BTSelect from 'components/Custom/Select'; - -import catalogService from '../service'; import { ReactComponent as SearchIcon } from 'assets/svg/common/search.svg'; import { ReactComponent as FilterIcon } from 'assets/svg/catalog/filter.svg'; import BTInput from 'components/Custom/Input'; -import { CurrentFilters, FilterOption, SortOption, CatalogFilterKeys, CatalogSlug } from '../types'; - +import { FilterOption, SortOption, CatalogFilterKeys, CatalogSlug, FilterTemplate } from '../types'; import { useGetFiltersQuery } from 'graphql'; import BTLoader from 'components/Common/BTLoader'; -import { useHistory, useParams } from 'react-router'; - +import { useHistory, useLocation, useParams } from 'react-router'; import styles from './CatalogFilters.module.scss'; import { SortDown, SortUp } from 'iconoir-react'; +import useCatalog from '../useCatalog'; +import { FILTER_TEMPLATE, SORT_OPTIONS, putFilterOptions } from '../service'; -type CatalogFilterProps = { - currentFilters: CurrentFilters; - sortQuery: SortOption; - searchQuery: string; - setCurrentFilters: Dispatch>; - setSortQuery: Dispatch>; - setSearchQuery: Dispatch>; - setDir: Dispatch>; - sortDir: boolean; -}; +const CatalogFilters = () => { + const [template, setTemplate] = useState(null); + const { loading, error } = useGetFiltersQuery({ + onCompleted: (data) => setTemplate(putFilterOptions(FILTER_TEMPLATE, data)) + }); -const { SORT_OPTIONS, FILTER_TEMPLATE, INITIAL_FILTERS } = catalogService; - -const CatalogFilters = (props: CatalogFilterProps) => { - const { - currentFilters, - setCurrentFilters, - sortQuery, - searchQuery, - setSortQuery, - setSearchQuery, - setDir, - sortDir - } = props; - - const { data, loading, error } = useGetFiltersQuery(); const [isOpen, setOpen] = useState(false); - const filters = useMemo(() => catalogService.processFilterData(data), [data]); const history = useHistory(); + const location = useLocation(); const slug = useParams(); const modalRef = useRef(null); + const [{ sortDir, sortQuery, searchQuery, filters }, dispatch] = useCatalog(); - const filterList = useMemo( - () => catalogService.putFilterOptions(FILTER_TEMPLATE, filters), - [filters] - ); + useEffect(() => { + const params = new URLSearchParams(location.search); + if (params.has('q')) dispatch({ type: 'search', query: params.get('q') ?? '' }); + }, [dispatch, location.search]); useEffect(() => { - if (filterList?.semester) { - const options = filterList.semester.options as FilterOption[]; + if (template?.semester) { + const options = template.semester.options as FilterOption[]; const semester = options.find(({ label }) => label === slug?.semester) ?? null; - setCurrentFilters((prev) => ({ - ...prev, - semester: semester ?? options[0] - })); + dispatch({ + type: 'filter', + filters: { + semester: semester ?? options[0] + } + }); } - }, [filterList, setCurrentFilters, slug?.semester]); + }, [template, slug?.semester, dispatch]); useEffect(() => { const ref = modalRef; @@ -88,33 +60,21 @@ const CatalogFilters = (props: CatalogFilterProps) => { }; }, [isOpen, modalRef, setOpen]); - const handleFilterReset = useCallback(() => { - setSortQuery(SORT_OPTIONS[0]); - setSearchQuery(''); - - if (filterList) { - const semester = filterList.semester.options[0] as FilterOption; - setCurrentFilters({ - ...INITIAL_FILTERS, - semester - }); - + const handleFilterReset = () => { + if (template) { + const semester = template.semester.options[0] as FilterOption; + dispatch({ type: 'reset', filters: { semester } }); history.push({ pathname: `/catalog/${semester.value.name}` }); } - }, [filterList, history, setCurrentFilters, setSearchQuery, setSortQuery]); + }; const handleFilterChange = ( newValue: FilterOption | readonly FilterOption[] | null, meta: ActionMeta ) => { - const key = meta.name as CatalogFilterKeys; - setCurrentFilters((prev) => ({ - ...prev, - [key]: newValue - })); - + dispatch({ type: 'filter', filters: { [meta.name as CatalogFilterKeys]: newValue } }); // Update the url slug if semester filter changes. - if (key === 'semester') { + if (meta.name === 'semester') { history.push({ pathname: `/catalog/${(newValue as FilterOption)?.value?.name}` .concat(slug?.abbreviation ? `/${slug.abbreviation}` : '') @@ -132,7 +92,7 @@ const CatalogFilters = (props: CatalogFilterProps) => { value={searchQuery} onChange={(e) => { history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - setSearchQuery(e.target.value); + dispatch({ type: 'search', query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -154,7 +114,7 @@ const CatalogFilters = (props: CatalogFilterProps) => { value={searchQuery} onChange={(e) => { history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - setSearchQuery(e.target.value); + dispatch({ type: 'search', query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -168,20 +128,20 @@ const CatalogFilters = (props: CatalogFilterProps) => { isClearable={false} options={SORT_OPTIONS} isSearchable={false} - onChange={(newValue) => setSortQuery(newValue as SortOption)} + onChange={(newValue) => dispatch({ type: 'sort', query: newValue as SortOption })} /> - - {filterList && - Object.entries(filterList).map(([key, filter]) => ( + {template && + Object.entries(template).map(([key, filter]) => (

{filter.name}

>; - selectedId: string | null; - searchQuery: string; - sortQuery: SortOption; - sortDir: boolean; -}; - -type Skeleton = { __typename: 'Skeleton'; id: number }; - -/** - * Component for course list - */ -const CatalogList = (props: CatalogListProps) => { - const { currentFilters, setCurrentCourse, selectedId, searchQuery, sortQuery, sortDir } = props; - const { observe, height } = useDimensions(); - const [fetchCatalogList, { data, loading, called }] = useGetCoursesForFilterLazyQuery({}); +const CatalogList = () => { const history = useHistory(); + const { observe, height } = useDimensions(); + const [{ course, courses, filters }, dispatch] = useCatalog(); - const courses = useMemo(() => { - if (!data) - return [...Array(20).keys()].map( - (key) => - ({ - __typename: 'Skeleton', - id: key - } as Skeleton) - ); - - let courseResults = CatalogService.searchCatalog( - data.allCourses.edges.map((edge) => edge.node), - searchQuery - ).sort(sortByAttribute(sortQuery.value)); - - //TODO: Very big problem to inspect - server is returning duplicate entries of same courses. - // Here we filter the duplicates to ensure catalog list consistency. - courseResults = courseResults.filter((v, i, a) => a.findIndex((t) => t.id === v.id) === i); - - // Inspect one case of duplication: - // console.log(courses.filter((v, i, a) => v.id === 'Q291cnNlVHlwZTo0NDc1')); - - return sortDir ? CatalogService.descending(courseResults, sortQuery) : courseResults; - }, [data, searchQuery, sortDir, sortQuery]); + const [fetchCatalogList, { loading, called }] = useGetCoursesForFilterLazyQuery({ + onCompleted: (data) => + dispatch({ type: 'setCourses', courses: data.allCourses.edges.map((edge) => edge.node) }) + }); useEffect(() => { - const playlistString = Object.values(currentFilters ?? {}) - .filter((val) => val !== null) - .map((item) => (Array.isArray(item) ? item.map((v) => v.value.id) : item?.value.id)) - .flat() - .join(','); - - if (playlistString) fetchCatalogList({ variables: { playlists: playlistString } }); - }, [fetchCatalogList, currentFilters]); + const playlists = buildPlaylist(filters); + if (playlists) fetchCatalogList({ variables: { playlists } }); + }, [fetchCatalogList, filters]); const handleCourseSelect = (course: CourseFragment) => { - setCurrentCourse(course); - history.push({ - pathname: `/catalog/${(currentFilters.semester as FilterOption)?.value?.name}/${ - course.abbreviation - }/${course.courseNumber}`, - search: location.search - }); + dispatch({ type: 'setCourse', course }); + if (filters.semester) { + const { name } = filters.semester.value; + const { abbreviation, courseNumber } = course; + history.push({ + pathname: `/catalog/${name}/${abbreviation}/${courseNumber}`, + search: location.search + }); + } }; return ( @@ -93,7 +54,7 @@ const CatalogList = (props: CatalogListProps) => { data={{ course: courses[index] as CourseFragment, handleCourseSelect, - isSelected: selectedId === courses[index].id + isSelected: course?.id === courses[index].id }} style={style} /> diff --git a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx index f5370a199..d3689f457 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx @@ -5,12 +5,10 @@ import { CSSProperties, memo } from 'react'; import { areEqual } from 'react-window'; import { ReactComponent as BookmarkSaved } from 'assets/svg/catalog/bookmark-saved.svg'; import { ReactComponent as BookmarkUnsaved } from 'assets/svg/catalog/bookmark-unsaved.svg'; -import catalogService from '../service'; - -const { colorEnrollment, formatEnrollment } = catalogService; import styles from './CatalogList.module.scss'; import Skeleton from 'react-loading-skeleton'; +import { colorEnrollment, formatEnrollment } from '../service'; function formatUnits(units: string) { return `${units} Unit${units === '1.0' || units === '1' ? '' : 's'}` diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index db8131b33..a21c191cf 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -1,13 +1,11 @@ -import { Dispatch, memo, SetStateAction, useEffect, useMemo, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import people from 'assets/svg/catalog/people.svg'; import chart from 'assets/svg/catalog/chart.svg'; import book from 'assets/svg/catalog/book.svg'; import launch from 'assets/svg/catalog/launch.svg'; import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow.svg'; -import catalogService from '../service'; import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils'; -import { CourseFragment, PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; -import { CurrentFilters } from 'app/Catalog/types'; +import { PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; import { useHistory, useParams } from 'react-router'; import { sortSections } from 'utils/sections/sort'; import Skeleton from 'react-loading-skeleton'; @@ -16,27 +14,21 @@ import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useSelector } from 'react-redux'; import SectionTable from './SectionTable'; - -interface CatalogViewProps { - coursePreview: CourseFragment | null; - setCurrentCourse: Dispatch>; - setCurrentFilters: Dispatch>; -} +import useCatalog from '../useCatalog'; +import { sortPills } from '../service'; const skeleton = [...Array(8).keys()]; -const CatalogView = (props: CatalogViewProps) => { - const { coursePreview, setCurrentFilters, setCurrentCourse } = props; +const CatalogView = () => { + const [{ course }, dispatch] = useCatalog(); + const [isOpen, setOpen] = useState(false); + const history = useHistory(); const { abbreviation, courseNumber, semester } = useParams<{ abbreviation: string; courseNumber: string; semester: string; }>(); - const [course, setCourse] = useState(coursePreview); - const [isOpen, setOpen] = useState(false); - const history = useHistory(); - const legacyId = useSelector( (state: any) => state.enrollment?.context?.courses?.find( @@ -48,21 +40,17 @@ const CatalogView = (props: CatalogViewProps) => { onCompleted: (data) => { const course = data.allCourses.edges[0].node; if (course) { - setCourse(course); + dispatch({ type: 'setCourse', course }); setOpen(true); } } }); - useEffect(() => { - if (!abbreviation || !courseNumber) { - setOpen(false); - } - }, [abbreviation, courseNumber]); - useEffect(() => { const [sem, year] = semester?.split(' ') ?? [null, null]; + if (!abbreviation || !courseNumber) setOpen(false); + const variables = { abbreviation: abbreviation ?? null, courseNumber: courseNumber ?? null, @@ -75,55 +63,29 @@ const CatalogView = (props: CatalogViewProps) => { }, [getCourse, abbreviation, courseNumber, semester]); useEffect(() => { - const course = data?.allCourses.edges[0].node; + const newCourse = data?.allCourses.edges[0].node; - if (course && course?.id === coursePreview?.id) { - setCourse(course); + if (newCourse && newCourse?.id === course?.id) { + dispatch({ type: 'setCourse', course: newCourse }); setOpen(true); - } else if (coursePreview) { - setCourse(coursePreview); + } else if (course) { + dispatch({ type: 'setCourse', course }); setOpen(true); } - }, [coursePreview, data]); + }, [course, data, dispatch]); const [playlists, sections] = useMemo(() => { let playlists = null; let sections = null; - if (course?.playlistSet) { - const { edges } = course.playlistSet; - playlists = catalogService.sortPills(edges.map((e) => e.node as PlaylistType)); - } + if (course?.playlistSet) + playlists = sortPills(course.playlistSet.edges.map((e) => e.node as PlaylistType)); - if (course?.sectionSet) { - const { edges } = course.sectionSet; - sections = sortSections(edges.map((e) => e.node)); - } + if (course?.sectionSet) sections = sortSections(course.sectionSet.edges.map((e) => e.node)); - return [playlists ?? skeleton, sections ?? null]; + return [playlists ?? skeleton, sections]; }, [course]); - const handlePill = (pillItem: PlaylistType) => { - setCurrentFilters((prev) => { - if (['haas', 'ls', 'engineering', 'university'].includes(pillItem.category)) { - const reqs = prev.requirements; - if (reqs?.find((el) => el.label === pillItem.name)) { - return prev; - } - - return { - ...prev, - requirements: [...(reqs ?? []), { label: pillItem.name, value: pillItem }] - }; - } - - return { - ...prev, - [pillItem.category]: { label: pillItem.name, value: pillItem } - }; - }); - }; - const enrollPath = legacyId ? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all` : `/enrollment`; @@ -135,8 +97,7 @@ const CatalogView = (props: CatalogViewProps) => {
diff --git a/frontend/src/app/Catalog/useCatalog.tsx b/frontend/src/app/Catalog/useCatalog.tsx index 2097a8113..b5cc5930e 100644 --- a/frontend/src/app/Catalog/useCatalog.tsx +++ b/frontend/src/app/Catalog/useCatalog.tsx @@ -1,4 +1,4 @@ -import { useReducer, createContext, Dispatch, useContext, useEffect } from 'react'; +import { useReducer, createContext, Dispatch, useContext } from 'react'; import { DEFAULT_FILTERS, SORT_OPTIONS, buildCourseIndex } from './service'; import { CatalogFilters, CourseInfo, SortOption } from './types'; import { CourseFragment, CourseOverviewFragment, PlaylistType } from 'graphql'; @@ -46,10 +46,6 @@ export const CatalogDispatch = createContext>( export const CatalogProvider = ({ children }: { children: React.ReactNode }) => { const [catalog, dispatch] = useReducer(catalogReducer, initialCatalog); - useEffect(() => { - dispatch({ type: 'search' }); - }, [catalog.allCourses, catalog.filters]); - return ( {children} @@ -58,7 +54,7 @@ export const CatalogProvider = ({ children }: { children: React.ReactNode }) => }; function catalogReducer(catalog: CatalogContext, action: CatalogAction): CatalogContext { - const { allCourses, courses, sortQuery, sortDir, searchQuery } = catalog; + const { courses, sortQuery, sortDir, searchQuery } = catalog; switch (action.type) { case 'sortDir': { @@ -80,10 +76,7 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog case 'search': { const query = action.query ?? searchQuery; - const { value } = sortQuery; - const courses = searchCatalog(catalog.courseIndex, query, allCourses).sort( - byAttribute(value) - ); + const courses = setSearch(catalog, query); return { ...catalog, @@ -115,11 +108,17 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog const newCourses = action.courses.filter( (v, i, a) => a.findIndex((t) => t.id === v.id) === i ); - return { + + const payload = { ...catalog, allCourses: newCourses, courseIndex: buildCourseIndex(newCourses) }; + + return { + ...payload, + courses: setSearch(payload) + }; } case 'reset': { @@ -173,4 +172,11 @@ export function useCatalogDispatch(): Dispatch { return useContext(CatalogDispatch); } +const setSearch = (catalog: CatalogContext, query: string | null = null) => { + const { courseIndex, searchQuery, allCourses, sortQuery } = catalog; + return searchCatalog(courseIndex, query ?? searchQuery, allCourses).sort( + byAttribute(sortQuery.value) + ); +}; + export default useCatalog; From 14a2d865323884ef81cc0a289ec090c81ebb0434 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 23 Jun 2023 01:05:32 -0700 Subject: [PATCH 11/42] remove skeleton in ListItem --- .../app/Catalog/CatalogList/CatalogListItem.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx index d3689f457..69fdc83e1 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx @@ -7,7 +7,6 @@ import { ReactComponent as BookmarkSaved } from 'assets/svg/catalog/bookmark-sav import { ReactComponent as BookmarkUnsaved } from 'assets/svg/catalog/bookmark-unsaved.svg'; import styles from './CatalogList.module.scss'; -import Skeleton from 'react-loading-skeleton'; import { colorEnrollment, formatEnrollment } from '../service'; function formatUnits(units: string) { @@ -19,7 +18,7 @@ function formatUnits(units: string) { type CatalogListItemProps = { data: { - course: CourseFragment | { __typename: 'Skeleton'; id: number }; + course: CourseFragment; handleCourseSelect: (course: CourseFragment) => void; isSelected: boolean; }; @@ -35,17 +34,7 @@ const CatalogListItem = ({ style, data }: CatalogListItemProps) => { const isSaved = user?.savedClasses?.some((savedCourse) => savedCourse?.id === course.id); - return course.__typename === 'Skeleton' ? ( -
-
- -
-
- ) : ( + return (
handleCourseSelect(course)}>
From c7f08340d2cea79ccea98cc5651cc38691ccff1f Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 25 Aug 2023 02:43:09 -0700 Subject: [PATCH 12/42] refactor toward key enum --- .../Catalog/CatalogFilters/CatalogFilters.tsx | 18 +-- .../app/Catalog/CatalogList/CatalogList.tsx | 6 +- .../app/Catalog/CatalogView/CatalogView.tsx | 24 ++-- .../CatalogView/__new_SectionTable.tsx | 4 +- frontend/src/app/Catalog/service.ts | 4 +- frontend/src/app/Catalog/useCatalog.tsx | 105 +++++++++--------- 6 files changed, 80 insertions(+), 81 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx index f3ec866ed..da41a3a4f 100644 --- a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx +++ b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx @@ -10,7 +10,7 @@ import BTLoader from 'components/Common/BTLoader'; import { useHistory, useLocation, useParams } from 'react-router'; import styles from './CatalogFilters.module.scss'; import { SortDown, SortUp } from 'iconoir-react'; -import useCatalog from '../useCatalog'; +import useCatalog, { CatalogActions } from '../useCatalog'; import { FILTER_TEMPLATE, SORT_OPTIONS, putFilterOptions } from '../service'; const CatalogFilters = () => { @@ -28,7 +28,7 @@ const CatalogFilters = () => { useEffect(() => { const params = new URLSearchParams(location.search); - dispatch({ type: 'search', query: params.get('q') ?? '' }); + dispatch({ type: CatalogActions.Search, query: params.get('q') ?? '' }); }, [dispatch, location.search]); useEffect(() => { @@ -37,7 +37,7 @@ const CatalogFilters = () => { const semester = options.find(({ label }) => label === slug?.semester) ?? null; dispatch({ - type: 'filter', + type: CatalogActions.Filter, filters: { semester: semester ?? options[0] } @@ -63,7 +63,7 @@ const CatalogFilters = () => { const handleFilterReset = () => { if (template) { const semester = template.semester.options[0] as FilterOption; - dispatch({ type: 'reset', filters: { semester } }); + dispatch({ type: CatalogActions.Reset, filters: { semester } }); history.push({ pathname: `/catalog/${semester.value.name}` }); } }; @@ -72,7 +72,7 @@ const CatalogFilters = () => { newValue: FilterOption | readonly FilterOption[] | null, meta: ActionMeta ) => { - dispatch({ type: 'filter', filters: { [meta.name as CatalogFilterKeys]: newValue } }); + dispatch({ type: CatalogActions.Filter, filters: { [meta.name as CatalogFilterKeys]: newValue } }); // Update the url slug if semester filter changes. if (meta.name === 'semester') { history.push({ @@ -92,7 +92,7 @@ const CatalogFilters = () => { value={searchQuery} onChange={(e) => { history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - dispatch({ type: 'search', query: e.target.value }); + dispatch({ type: CatalogActions.Search, query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -114,7 +114,7 @@ const CatalogFilters = () => { value={searchQuery} onChange={(e) => { history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - dispatch({ type: 'search', query: e.target.value }); + dispatch({ type: CatalogActions.Search, query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -128,9 +128,9 @@ const CatalogFilters = () => { isClearable={false} options={SORT_OPTIONS} isSearchable={false} - onChange={(newValue) => dispatch({ type: 'sort', query: newValue as SortOption })} + onChange={(newValue) => dispatch({ type: CatalogActions.Sort, query: newValue as SortOption })} /> -
diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.tsx b/frontend/src/app/Catalog/CatalogList/CatalogList.tsx index 1f593efad..59d89c5b5 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogList.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogList.tsx @@ -5,7 +5,7 @@ import { memo, useEffect } from 'react'; import useDimensions from 'react-cool-dimensions'; import styles from './CatalogList.module.scss'; import { useHistory } from 'react-router'; -import useCatalog from '../useCatalog'; +import useCatalog, { CatalogActions } from '../useCatalog'; import { buildPlaylist } from '../service'; import Skeleton from 'react-loading-skeleton'; @@ -16,7 +16,7 @@ const CatalogList = () => { const [fetchCatalogList, { loading, called }] = useGetCoursesForFilterLazyQuery({ onCompleted: (data) => - dispatch({ type: 'setCourses', courses: data.allCourses.edges.map((edge) => edge.node) }) + dispatch({ type: CatalogActions.SetCourseList, allCourses: data.allCourses.edges.map((edge) => edge.node) }) }); useEffect(() => { @@ -25,7 +25,7 @@ const CatalogList = () => { }, [fetchCatalogList, filters]); const handleCourseSelect = (course: CourseFragment) => { - dispatch({ type: 'setCourse', course }); + dispatch({ type: CatalogActions.SetCourse, course }); if (filters.semester) { const { name } = filters.semester.value; const { abbreviation, courseNumber } = course; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index a21c191cf..0e67fcc5c 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { memo, useEffect, useMemo, useState } from 'react'; import people from 'assets/svg/catalog/people.svg'; import chart from 'assets/svg/catalog/chart.svg'; @@ -14,8 +15,9 @@ import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useSelector } from 'react-redux'; import SectionTable from './SectionTable'; -import useCatalog from '../useCatalog'; +import useCatalog, { CatalogActions } from '../useCatalog'; import { sortPills } from '../service'; +import CatalogViewSections from './__new_SectionTable'; const skeleton = [...Array(8).keys()]; @@ -40,7 +42,7 @@ const CatalogView = () => { onCompleted: (data) => { const course = data.allCourses.edges[0].node; if (course) { - dispatch({ type: 'setCourse', course }); + dispatch({ type: CatalogActions.SetCourse, course }); setOpen(true); } } @@ -66,10 +68,10 @@ const CatalogView = () => { const newCourse = data?.allCourses.edges[0].node; if (newCourse && newCourse?.id === course?.id) { - dispatch({ type: 'setCourse', course: newCourse }); + dispatch({ type: CatalogActions.SetCourse, course: newCourse }); setOpen(true); } else if (course) { - dispatch({ type: 'setCourse', course }); + dispatch({ type: CatalogActions.SetCourse, course }); setOpen(true); } }, [course, data, dispatch]); @@ -97,7 +99,7 @@ const CatalogView = () => {
@@ -95,6 +93,7 @@ const SectionTable = ({ sections }: Props) => { {section.enrolled}/{section.enrolledMax} ( {formatEnrollment(section.enrolled / section.enrolledMax)})
+ {section.waitlisted} Waitlisted
); diff --git a/frontend/src/utils/sections/section.ts b/frontend/src/utils/sections/section.ts index 15f5743b7..3421b97c9 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` : ''; export const formatSectionEnrollment = (section: SectionFragment): ReactNode => From 62b27aa21a88c970fec92a0072d4a703539693aa Mon Sep 17 00:00:00 2001 From: Jaden Date: Sat, 23 Sep 2023 20:12:05 -0700 Subject: [PATCH 16/42] add details to unknown strings --- frontend/src/app/Catalog/CatalogView/SectionTable.tsx | 6 +++--- frontend/src/utils/sections/section.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 42d3caa55..df40446c9 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -73,12 +73,12 @@ const SectionTable = ({ sections }: Props) => {
{section.kind}
- {section.instructor?.toLowerCase() || 'unknown'} + {section.instructor?.toLowerCase() || 'Unknown instructor'}
- {section.locationName || 'Unknown'} + {section.locationName || 'Unknown location'} {' • '}
@@ -91,7 +91,7 @@ const SectionTable = ({ sections }: Props) => {
{section.enrolled}/{section.enrolledMax} ( - {formatEnrollment(section.enrolled / section.enrolledMax)}) + {formatEnrollment(section.enrolled / section.enrolledMax)}) enrolled
{section.waitlisted} Waitlisted
diff --git a/frontend/src/utils/sections/section.ts b/frontend/src/utils/sections/section.ts index 3421b97c9..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 - ? `Unknown` + ? `Unknown time` : ''; export const formatSectionEnrollment = (section: SectionFragment): ReactNode => From 634c21e7e3ea20b953ce641e23d33cb66e29d1eb Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 25 Sep 2023 19:05:32 -0700 Subject: [PATCH 17/42] bolden enrollment info, remove percentage --- frontend/src/app/Catalog/CatalogView/CatalogView.module.scss | 4 +++- frontend/src/app/Catalog/CatalogView/SectionTable.tsx | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 5a1b02689..88615e39c 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -201,6 +201,7 @@ display: flex; flex-direction: column; gap: 10px; + align-items: center; } .sectionItem { @@ -210,6 +211,7 @@ border-radius: 16px; border: 1.5px solid #eaeaea; gap: 30px; + width: 100%; h6 { font-size: 16px; @@ -278,7 +280,7 @@ align-items: center; font-size: 14px; gap: 5px; - + font-weight: bold; img { width: 14px; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index df40446c9..16f62f907 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -90,8 +90,9 @@ const SectionTable = ({ sections }: Props) => {
- {section.enrolled}/{section.enrolledMax} ( - {formatEnrollment(section.enrolled / section.enrolledMax)}) enrolled + {section.enrolled}/{section.enrolledMax} {' '} + {/* ({formatEnrollment(section.enrolled / section.enrolledMax)}) */} + enrolled
{section.waitlisted} Waitlisted
From edd3cc3657561d68380b72ae08e29fcb73d4a30b Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 2 Oct 2023 19:31:07 -0700 Subject: [PATCH 18/42] revert positions --- .../CatalogView/CatalogView.module.scss | 14 ++------- .../app/Catalog/CatalogView/CatalogView.tsx | 28 ++++++++++------- .../app/Catalog/CatalogView/SectionTable.tsx | 30 +++++++++---------- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 88615e39c..0c9a4f9c9 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -201,7 +201,6 @@ display: flex; flex-direction: column; gap: 10px; - align-items: center; } .sectionItem { @@ -211,7 +210,6 @@ border-radius: 16px; border: 1.5px solid #eaeaea; gap: 30px; - width: 100%; h6 { font-size: 16px; @@ -231,15 +229,12 @@ .instructor { display: flex; gap: 5px; + align-items: center; font-size: 14px; color: $bt-light-grey; text-transform: capitalize; gap: 4px; color: $bt-light-text; - - svg { - overflow: visible; - } } .sectionContent { @@ -268,10 +263,6 @@ margin-top: 12px; font-size: 14px; text-transform: none !important; - div { - display: flex; - gap: 4px; - } } .enrolled { @@ -279,8 +270,9 @@ justify-content: flex-end; align-items: center; font-size: 14px; + font-weight: 600; gap: 5px; - font-weight: bold; + img { width: 14px; } diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 27b72366e..b067d4f92 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -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}

diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 16f62f907..50317ee32 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -73,28 +73,26 @@ const SectionTable = ({ sections }: Props) => {
{section.kind}
- {section.instructor?.toLowerCase() || 'Unknown instructor'} + {section.instructor?.toLowerCase() || 'unknown'}
-
- - {section.locationName || 'Unknown location'} - {' • '} -
-
- - {section.wordDays} {formatSectionTime(section)} +
+ + {section.enrolled}/{section.enrolledMax}
+
• {section.waitlisted} Waitlisted
+ {/* • CCN {section.ccn} */}
-
- - {section.enrolled}/{section.enrolledMax} {' '} - {/* ({formatEnrollment(section.enrolled / section.enrolledMax)}) */} - enrolled -
- {section.waitlisted} Waitlisted + + + {section.locationName || 'Unknown'} + + + + {section.wordDays} {formatSectionTime(section)} +
); From ed5e7c99b39e82bac6c891953dd540bd8553603f Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 2 Oct 2023 19:46:43 -0700 Subject: [PATCH 19/42] align text --- .../src/app/Catalog/CatalogView/CatalogView.module.scss | 9 ++++++++- frontend/src/app/Catalog/CatalogView/SectionTable.tsx | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 0c9a4f9c9..751d10755 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -224,6 +224,12 @@ display: flex; flex-direction: column; flex: 1; + + h6 { + display: flex; + align-items: center; + min-height: 24px; + } } .instructor { @@ -234,6 +240,7 @@ color: $bt-light-grey; text-transform: capitalize; gap: 4px; + min-height: 24px; color: $bt-light-text; } @@ -246,7 +253,7 @@ span { display: inline-flex; justify-content: flex-end; - align-items: flex-end; + align-items: center; line-height: 20px; gap: 4px; color: $bt-light-text; diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 50317ee32..326dcd811 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -15,7 +15,7 @@ import { Clock, Group, PinAlt, User } from 'iconoir-react'; import styles from './CatalogView.module.scss'; -const { colorEnrollment, formatEnrollment } = catalogService; +const { colorEnrollment } = catalogService; const easterEggImages = new Map([ ['DENERO J', denero], From 55a2f416a9834737ecccf81acc33669229297e01 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 13 Oct 2023 12:35:05 -0700 Subject: [PATCH 20/42] re-apply navigation --- .../Catalog/CatalogFilters/CatalogFilters.tsx | 58 +++++++++++-------- .../app/Catalog/CatalogList/CatalogList.tsx | 13 +++-- .../app/Catalog/CatalogView/CatalogView.tsx | 24 +++++--- frontend/src/app/Catalog/service.ts | 4 +- frontend/src/app/Catalog/types.ts | 6 +- 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx index da41a3a4f..7c67c8d90 100644 --- a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx +++ b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx @@ -4,13 +4,13 @@ import BTSelect from 'components/Custom/Select'; import { ReactComponent as SearchIcon } from 'assets/svg/common/search.svg'; import { ReactComponent as FilterIcon } from 'assets/svg/catalog/filter.svg'; import BTInput from 'components/Custom/Input'; -import { FilterOption, SortOption, CatalogFilterKeys, CatalogSlug, FilterTemplate } from '../types'; +import { FilterOption, SortOption, CatalogFilterKey, CatalogSlug, FilterTemplate } from '../types'; import { useGetFiltersQuery } from 'graphql'; import BTLoader from 'components/Common/BTLoader'; -import { useHistory, useLocation, useParams } from 'react-router'; +import { useNavigate, useLocation, useParams } from 'react-router'; import styles from './CatalogFilters.module.scss'; import { SortDown, SortUp } from 'iconoir-react'; -import useCatalog, { CatalogActions } from '../useCatalog'; +import useCatalog, { CatalogActions as Actions } from '../useCatalog'; import { FILTER_TEMPLATE, SORT_OPTIONS, putFilterOptions } from '../service'; const CatalogFilters = () => { @@ -19,7 +19,7 @@ const CatalogFilters = () => { onCompleted: (data) => setTemplate(putFilterOptions(FILTER_TEMPLATE, data)) }); - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const slug = useParams(); const [isOpen, setOpen] = useState(false); @@ -28,7 +28,7 @@ const CatalogFilters = () => { useEffect(() => { const params = new URLSearchParams(location.search); - dispatch({ type: CatalogActions.Search, query: params.get('q') ?? '' }); + dispatch({ type: Actions.Search, query: params.get('q') ?? '' }); }, [dispatch, location.search]); useEffect(() => { @@ -37,10 +37,8 @@ const CatalogFilters = () => { const semester = options.find(({ label }) => label === slug?.semester) ?? null; dispatch({ - type: CatalogActions.Filter, - filters: { - semester: semester ?? options[0] - } + type: Actions.Filter, + filters: { semester: semester ?? options[0] } }); } }, [template, slug?.semester, dispatch]); @@ -63,8 +61,8 @@ const CatalogFilters = () => { const handleFilterReset = () => { if (template) { const semester = template.semester.options[0] as FilterOption; - dispatch({ type: CatalogActions.Reset, filters: { semester } }); - history.push({ pathname: `/catalog/${semester.value.name}` }); + dispatch({ type: Actions.Reset, filters: { semester } }); + navigate({ pathname: `/catalog/${semester.value.name}` }); } }; @@ -72,13 +70,19 @@ const CatalogFilters = () => { newValue: FilterOption | readonly FilterOption[] | null, meta: ActionMeta ) => { - dispatch({ type: CatalogActions.Filter, filters: { [meta.name as CatalogFilterKeys]: newValue } }); + dispatch({ + type: Actions.Filter, + filters: { [meta.name as CatalogFilterKey]: newValue } + }); // Update the url slug if semester filter changes. if (meta.name === 'semester') { - history.push({ - pathname: `/catalog/${(newValue as FilterOption)?.value?.name}` - .concat(slug?.abbreviation ? `/${slug.abbreviation}` : '') - .concat(slug?.courseNumber ? `/${slug.courseNumber}` : ''), + navigate({ + pathname: + `/catalog/${(newValue as FilterOption)?.value?.name}` + slug?.abbreviation + ? `/${slug.abbreviation}` + : '' + slug?.courseNumber + ? `/${slug.courseNumber}` + : '', search: location.search }); } @@ -91,8 +95,11 @@ const CatalogFilters = () => { style={{ border: 'none', width: '100%' }} value={searchQuery} onChange={(e) => { - history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - dispatch({ type: CatalogActions.Search, query: e.target.value }); + navigate( + { pathname: location.pathname, search: `q=${e.target.value}` }, + { replace: true } + ); + dispatch({ type: Actions.Search, query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -113,8 +120,11 @@ const CatalogFilters = () => { { - history.replace({ pathname: location.pathname, search: `q=${e.target.value}` }); - dispatch({ type: CatalogActions.Search, query: e.target.value }); + navigate( + { pathname: location.pathname, search: `q=${e.target.value}` }, + { replace: true } + ); + dispatch({ type: Actions.Search, query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -128,9 +138,11 @@ const CatalogFilters = () => { isClearable={false} options={SORT_OPTIONS} isSearchable={false} - onChange={(newValue) => dispatch({ type: CatalogActions.Sort, query: newValue as SortOption })} + onChange={(newValue) => + dispatch({ type: Actions.Sort, query: newValue as SortOption }) + } /> -
@@ -141,7 +153,7 @@ const CatalogFilters = () => {

{filter.name}

{ - const history = useHistory(); + const navigate = useNavigate(); const { observe, height } = useDimensions(); const [{ course, courses, filters }, dispatch] = useCatalog(); const [fetchCatalogList, { loading, called }] = useGetCoursesForFilterLazyQuery({ onCompleted: (data) => - dispatch({ type: CatalogActions.SetCourseList, allCourses: data.allCourses.edges.map((edge) => edge.node) }) + dispatch({ + type: CatalogActions.SetCourseList, + allCourses: data.allCourses.edges.map((edge) => edge.node) + }) }); useEffect(() => { @@ -29,7 +32,7 @@ const CatalogList = () => { if (filters.semester) { const { name } = filters.semester.value; const { abbreviation, courseNumber } = course; - history.push({ + navigate({ pathname: `/catalog/${name}/${abbreviation}/${courseNumber}`, search: location.search }); @@ -65,7 +68,7 @@ const CatalogList = () => { {loading && [...Array(20).keys()].map((key) => (
-
+
diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 8dc84ddbc..2ff36365e 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -7,14 +7,14 @@ import launch from 'assets/svg/catalog/launch.svg'; import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow.svg'; import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils'; import { PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; -import { useHistory, useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router'; import { sortSections } from 'utils/sections/sort'; import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useSelector } from 'react-redux'; -import SectionTable from './SectionTable'; +// import SectionTable from './SectionTable'; import useCatalog, { CatalogActions } from '../useCatalog'; import { sortPills } from '../service'; import CatalogViewSections from './__new_SectionTable'; @@ -24,7 +24,7 @@ const skeleton = [...Array(8).keys()]; const CatalogView = () => { const [{ course }, dispatch] = useCatalog(); const [isOpen, setOpen] = useState(false); - const history = useHistory(); + const navigate = useNavigate(); const { abbreviation, courseNumber, semester } = useParams<{ abbreviation: string; courseNumber: string; @@ -38,7 +38,7 @@ const CatalogView = () => { )?.id ?? null ); - const [getCourse, { data, loading }] = useGetCourseForNameLazyQuery({ + const [getCourse, { data }] = useGetCourseForNameLazyQuery({ onCompleted: (data) => { const course = data.allCourses.edges[0].node; if (course) { @@ -61,7 +61,15 @@ const CatalogView = () => { }; // Only fetch the course if every parameter has a value. - if (Object.values(variables).every((value) => value !== null)) getCourse({ variables }); + if (Object.values(variables).every((value) => value !== null)) + getCourse({ + variables: variables as { + abbreviation: string; + courseNumber: string; + semester: string; + year: string; + } + }); }, [getCourse, abbreviation, courseNumber, semester]); useEffect(() => { @@ -89,7 +97,7 @@ const CatalogView = () => { }, [course]); const enrollPath = legacyId - ? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all` + ? `/enrollment/0-${legacyId}-${semester?.replace(' ', '-')}-all` : `/enrollment`; const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; @@ -100,7 +108,7 @@ const CatalogView = () => { className={styles.modalButton} onClick={() => { dispatch({ type: CatalogActions.SetCourse, course: null }); - history.replace(`/catalog/${semester}`); + navigate(`/catalog/${semester}`, { replace: true }); }} > @@ -184,11 +192,9 @@ const CatalogView = () => { 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/service.ts b/frontend/src/app/Catalog/service.ts index 2256979ce..41e1d9ff5 100644 --- a/frontend/src/app/Catalog/service.ts +++ b/frontend/src/app/Catalog/service.ts @@ -5,7 +5,7 @@ import { courseToName } from 'lib/courses/course'; import { CatalogCategoryKeys, FilterTemplate, - CatalogFilterKeys, + CatalogFilterKey, FilterOptions, SortOption, CourseInfo, @@ -156,7 +156,7 @@ export const putFilterOptions = (filterItems: FilterTemplate, data?: GetFiltersQ Object.entries(result) .filter(([key]) => key !== 'requirements') .map(([k]) => { - const key = k as CatalogFilterKeys; + const key = k as CatalogFilterKey; result[key].options = filters[key].map((filter) => ({ label: filter.name, value: filter diff --git a/frontend/src/app/Catalog/types.ts b/frontend/src/app/Catalog/types.ts index 397cd07d6..28211a589 100644 --- a/frontend/src/app/Catalog/types.ts +++ b/frontend/src/app/Catalog/types.ts @@ -18,7 +18,7 @@ export type CatalogCategoryKeys = | 'university' | 'requirements'; -export type CatalogFilterKeys = Exclude< +export type CatalogFilterKey = Exclude< CatalogCategoryKeys, 'haas' | 'ls' | 'engineering' | 'university' >; @@ -46,7 +46,7 @@ export type SortOption = { value: CatalogSortKeys; label: string }; export type FilterOption = { value: FilterFragment; label: string }; export type FilterTemplate = { - [key in CatalogFilterKeys]: { + [key in CatalogFilterKey]: { name: string; isClearable: boolean; isMulti: boolean; @@ -63,4 +63,4 @@ export type CourseInfo = { courseNumber: string; fullCourseCode: string; abbreviations: string[]; -}; \ No newline at end of file +}; From 0c98611f849874039ee4982c49cc37f96cdaae44 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 13 Oct 2023 13:18:05 -0700 Subject: [PATCH 21/42] start tab work --- frontend/package.json | 2 + .../CatalogView/CatalogView.module.scss | 33 ++++++++++++ .../app/Catalog/CatalogView/CatalogView.tsx | 19 +++---- .../app/Catalog/CatalogView/CourseTabs.tsx | 54 +++++++++++++++++++ 4 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/Catalog/CatalogView/CourseTabs.tsx diff --git a/frontend/package.json b/frontend/package.json index e6cb986bd..4bfa2dcba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,8 +19,10 @@ }, "dependencies": { "@apollo/client": "^3.2.5", + "@radix-ui/react-tabs": "^1.0.4", "axios": "^1.2.6", "bootstrap": "^4.5.2", + "clsx": "^2.0.0", "color": "^3.1.3", "date-fns": "^2.21.1", "fuse.js": "^6.6.2", diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index bb576282e..ae0234741 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -258,3 +258,36 @@ width: 14px; } } + +.tabList { + display: flex; + flex-direction: row; + gap: 30px; + border-bottom: 1.5px solid #eaeaea; + margin: 0 -30px; + padding: 0 30px; +} + +.tabContent { + display: flex; + flex-direction: column; + padding-top: 30px; +} + +.tab { + display: flex; + align-items: center; + outline: none; + border: none; + background: transparent; + padding: 5px 0; + font-size: 16px; + margin-bottom: -1.5px; + color: $bt-light-text; + border-bottom: 3px solid transparent; +} + +.active { + border-bottom: 3px solid #2f80ed; + color: $bt-base-text; +} diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 2ff36365e..5688b3bfa 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -8,16 +8,14 @@ import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils'; import { PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; import { useNavigate, useParams } from 'react-router'; -import { sortSections } from 'utils/sections/sort'; import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; - -import styles from './CatalogView.module.scss'; import { useSelector } from 'react-redux'; -// import SectionTable from './SectionTable'; import useCatalog, { CatalogActions } from '../useCatalog'; import { sortPills } from '../service'; -import CatalogViewSections from './__new_SectionTable'; +import CourseTabs from './CourseTabs'; + +import styles from './CatalogView.module.scss'; const skeleton = [...Array(8).keys()]; @@ -84,16 +82,13 @@ const CatalogView = () => { } }, [course, data, dispatch]); - const [playlists, sections] = useMemo(() => { + const playlists = useMemo(() => { let playlists = null; - let sections = null; if (course?.playlistSet) playlists = sortPills(course.playlistSet.edges.map((e) => e.node as PlaylistType)); - if (course?.sectionSet) sections = sortSections(course.sectionSet.edges.map((e) => e.node)); - - return [playlists ?? skeleton, sections]; + return playlists ?? skeleton; }, [course]); const enrollPath = legacyId @@ -185,7 +180,7 @@ const CatalogView = () => {

-
Class Times - {semester ?? ''}
+ {/* {sections && sections.length > 0 ? ( ) : !loading ? ( @@ -193,7 +188,7 @@ const CatalogView = () => { ) : null} */} {/* Redesigned catalog sections */} - + {/* */} {/* Good feature whenever we want...
Past Offerings
diff --git a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx new file mode 100644 index 000000000..1a593310e --- /dev/null +++ b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx @@ -0,0 +1,54 @@ +import * as Tabs from '@radix-ui/react-tabs'; +import useCatalog from '../useCatalog'; +import CatalogViewSections from './__new_SectionTable'; +import { useMemo, useState } from 'react'; +import clsx from 'clsx'; +import { sortSections } from 'utils/sections/sort'; + +import styles from './CatalogView.module.scss'; +// import GradesGraph from 'components/Graphs/GradesGraph'; + +type TabKey = 'times' | 'grades' | 'enrollment'; + +const CourseTabs = () => { + const [value, setValue] = useState('times'); + const [{ course }] = useCatalog(); + + const sections = useMemo( + () => (course?.sectionSet ? sortSections(course.sectionSet.edges.map((e) => e.node)) : []), + [course] + ); + + return ( + setValue(key as TabKey)} value={value}> + + + Class Times + + + Grades + + + Enrollment + + + + + + + hi + + + ); +}; + +export default CourseTabs; From 41def7f2cf4467ab7c5629d1e86972ef1e223c55 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 13 Oct 2023 15:40:58 -0700 Subject: [PATCH 22/42] catalog grades --- frontend/src/Berkeleytime.tsx | 3 +- .../CatalogView/CatalogView.module.scss | 4 ++ .../app/Catalog/CatalogView/CourseTabs.tsx | 40 ++++++++++-- .../src/assets/scss/bt/grades/_graph.scss | 3 - .../components/GraphCard/GradesGraphCard.jsx | 4 +- .../components/GraphCard/Graphs.module.scss | 17 +++++ .../src/components/Graphs/GradesGraph.jsx | 62 ++++++++++++------- frontend/src/redux/actions.js | 32 +++++++++- 8 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 frontend/src/components/GraphCard/Graphs.module.scss diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index 9d6f45d6a..cb026efb2 100644 --- a/frontend/src/Berkeleytime.tsx +++ b/frontend/src/Berkeleytime.tsx @@ -4,7 +4,7 @@ import { openBanner, enterMobile, exitMobile, openLandingModal } from './redux/c import useDimensions from 'react-cool-dimensions'; import easterEgg from 'utils/easterEgg'; import Routes from './Routes'; -import { fetchEnrollContext } from 'redux/actions'; +import { fetchEnrollContext, fetchGradeContext } from 'redux/actions'; import { IconoirProvider } from 'iconoir-react'; const Berkeleytime = () => { @@ -24,6 +24,7 @@ const Berkeleytime = () => { useEffect(() => { // Fetch enrollment context early on for catalog and enrollment page. dispatch(fetchEnrollContext()); + dispatch(fetchGradeContext()); const bannerType = 'fa23recruitment'; // should match value in ./redux/common/reducer.ts if (localStorage.getItem('bt-hide-banner') !== bannerType) { diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index ae0234741..641f9ab0d 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -274,6 +274,10 @@ padding-top: 30px; } +.tabGraph { + margin: 0 -20px 0 -25px; +} + .tab { display: flex; align-items: center; diff --git a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx index 1a593310e..7a34e885d 100644 --- a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx @@ -1,24 +1,50 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import * as Tabs from '@radix-ui/react-tabs'; import useCatalog from '../useCatalog'; import CatalogViewSections from './__new_SectionTable'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import clsx from 'clsx'; import { sortSections } from 'utils/sections/sort'; - import styles from './CatalogView.module.scss'; -// import GradesGraph from 'components/Graphs/GradesGraph'; +import GradesGraph from 'components/Graphs/GradesGraph'; +import { fetchCatalogGrades, fetchLegacyGradeObjects } from 'redux/actions'; +import { useSelector } from 'react-redux'; type TabKey = 'times' | 'grades' | 'enrollment'; const CourseTabs = () => { const [value, setValue] = useState('times'); const [{ course }] = useCatalog(); + const [graphData, setGraphData] = useState([]); const sections = useMemo( () => (course?.sectionSet ? sortSections(course.sectionSet.edges.map((e) => e.node)) : []), [course] ); + const legacyId = useSelector( + (state: any) => + state.grade?.context?.courses?.find((c: any) => { + return c.course_number === course?.courseNumber; + })?.id || null + ); + + useEffect(() => { + if (!course || !legacyId) return; + + const fetchGraph = async () => { + const gradeObjects = await fetchLegacyGradeObjects(legacyId); + + const res = await fetchCatalogGrades([ + { ...course, sections: gradeObjects.map((s: any) => s.grade_id) } + ]); + + return setGraphData(res); + }; + + fetchGraph(); + }, [course, legacyId]); + return ( setValue(key as TabKey)} value={value}> @@ -45,7 +71,13 @@ const CourseTabs = () => { - hi +
+ +
); diff --git a/frontend/src/assets/scss/bt/grades/_graph.scss b/frontend/src/assets/scss/bt/grades/_graph.scss index d8e8bc353..d05c7fa9a 100644 --- a/frontend/src/assets/scss/bt/grades/_graph.scss +++ b/frontend/src/assets/scss/bt/grades/_graph.scss @@ -1,7 +1,4 @@ .grades-graph { - margin-top: 20px; - max-width: 90vw; - g.recharts-layer.recharts-cartesian-axis.recharts-xAxis.xAxis, g.recharts-layer.recharts-cartesian-axis.recharts-yAxis.yAxis { opacity: 0.5; diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx index 5ce7e5f05..dbc4b1f1e 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -11,7 +11,7 @@ import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; import { fetchGradeData } from '../../redux/actions'; export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { - const { gradesData, graphData, selectedCourses } = useSelector((state) => state.grade); + const { gradesData, selectedCourses } = useSelector((state) => state.grade); const [hoveredClass, setHoveredClass] = useState(false); const [updateMobileHover, setUpdateMobileHover] = useState(true); const dispatch = useDispatch(); @@ -106,7 +106,6 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { > {isMobile &&
Grade Distribution
} diff --git a/frontend/src/components/GraphCard/Graphs.module.scss b/frontend/src/components/GraphCard/Graphs.module.scss new file mode 100644 index 000000000..d2827824d --- /dev/null +++ b/frontend/src/components/GraphCard/Graphs.module.scss @@ -0,0 +1,17 @@ +.graphView { + display: grid; + width: 100%; + grid-template-columns: 2fr 1fr; + grid-template-rows: 1fr; + @include media(tablet) { + grid-template-columns: 1fr; + grid-template-rows: min-content 1fr; + } +} + +.simpleGraphView { + display: grid; + width: 100%; + grid-template-columns: 1fr; + grid-template-rows: 1fr; +} diff --git a/frontend/src/components/Graphs/GradesGraph.jsx b/frontend/src/components/Graphs/GradesGraph.jsx index eb9d6c8db..869281eda 100644 --- a/frontend/src/components/Graphs/GradesGraph.jsx +++ b/frontend/src/components/Graphs/GradesGraph.jsx @@ -1,5 +1,6 @@ import { BarChart, Bar, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { percentileToString } from '../../utils/utils'; +import { useMemo } from 'react'; import vars from '../../utils/variables'; import emptyImage from '../../assets/img/images/graphs/empty.svg'; @@ -55,26 +56,43 @@ const PercentageLabel = (props) => { ); }; -export default function GradesGraph({ - graphData, - gradesData, - updateBarHover, - updateGraphHover, - course, - semester, - instructor, - selectedPercentiles, - denominator, - color, - isMobile, - graphEmpty -}) { +export default function GradesGraph(props) { + const { + gradesData, + updateBarHover, + updateGraphHover, + course, + semester, + instructor, + selectedPercentiles, + denominator, + color, + isMobile + } = props; + let numClasses = gradesData.length; + const graphData = useMemo( + () => + ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'].map( + (letterGrade) => { + const ret = { + name: letterGrade + }; + for (const grade of gradesData) { + ret[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; + } + return ret; + } + ), + [gradesData] + ); + + const graphEmpty = gradesData.length === 0; + return ( -
+
{!isMobile ? ( - // desktop or wide viewport - {!graphEmpty ? ( - + {graphEmpty ? ( + ) : ( - + )} @@ -107,15 +125,11 @@ export default function GradesGraph({ ) : ( - // mobile or narrow viewport {!graphEmpty ? ( diff --git a/frontend/src/redux/actions.js b/frontend/src/redux/actions.js index 574ff99c7..bf941bef5 100644 --- a/frontend/src/redux/actions.js +++ b/frontend/src/redux/actions.js @@ -16,7 +16,9 @@ import { UPDATE_ENROLL_SELECTED } from './actionTypes'; -axios.defaults.baseURL = import.meta.env.PROD ? axios.defaults.baseURL : 'https://staging.berkeleytime.com'; +axios.defaults.baseURL = import.meta.env.PROD + ? axios.defaults.baseURL + : 'https://staging.berkeleytime.com'; // update grade list const updateGradeContext = (data) => ({ @@ -159,6 +161,28 @@ export function fetchGradeData(classData) { ); } +export async function fetchCatalogGrades(classData) { + const promises = []; + for (const course of classData) { + const { sections } = course; + const url = `/api/grades/sections/${sections.join('&')}/`; + promises.push(axios.get(url)); + } + + const result = await axios.all(promises); + + const test = result.map((res, i) => { + let gradesData = res.data; + gradesData['id'] = classData[i].id; + gradesData['instructor'] = classData[i].instructor = 'All Instructors'; + gradesData['semester'] = classData[i].semester = 'All Semesters'; + gradesData['colorId'] = classData[i].colorId; + return gradesData; + }); + + return test; +} + export function fetchGradeSelected(updatedClass) { const url = `/api/grades/course_grades/${updatedClass.value}/`; return (dispatch) => @@ -174,6 +198,12 @@ export function fetchGradeSelected(updatedClass) { ); } +export async function fetchLegacyGradeObjects(id) { + const url = `/api/grades/course_grades/${id}/`; + const res = await axios.get(url); + return res.data; +} + export function fetchGradeFromUrl(url, navigate) { const toUrlForm = (s) => s.replace('/', '_').toLowerCase().split(' ').join('-'); const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1); From a624717901013a26e1d3275195644ad1018fa3a5 Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 13 Oct 2023 16:11:06 -0700 Subject: [PATCH 23/42] omit internal fields from type --- frontend/src/app/Catalog/useCatalog.tsx | 44 ++++++++----------- .../src/assets/scss/bt/grades/_graph.scss | 3 ++ 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/Catalog/useCatalog.tsx b/frontend/src/app/Catalog/useCatalog.tsx index 2552f7e9f..da0ce5e00 100644 --- a/frontend/src/app/Catalog/useCatalog.tsx +++ b/frontend/src/app/Catalog/useCatalog.tsx @@ -67,49 +67,42 @@ export const CatalogProvider = ({ children }: { children: React.ReactNode }) => }; function catalogReducer(catalog: CatalogContext, action: CatalogAction): CatalogContext { - const { courses, sortQuery, sortDir, searchQuery } = catalog; + const { courses, sortQuery, sortDir, searchQuery, filters } = catalog; switch (action.type) { - case CatalogActions.SortDir: { - const newDir = sortDir === 'ASC' ? 'DESC' : 'ASC'; - + case CatalogActions.SortDir: return { ...catalog, - sortDir: newDir, + sortDir: sortDir === 'ASC' ? 'DESC' : 'ASC', courses: flipCourseList(courses, sortQuery) }; - } - case CatalogActions.SetCourse: { + case CatalogActions.SetCourse: return { ...catalog, course: action.course }; - } case CatalogActions.Search: { - const query = action.query ?? searchQuery; - const courses = setSearch(catalog, query); + const newQuery = action.query ?? searchQuery; return { ...catalog, - searchQuery: query, - courses + searchQuery: newQuery, + courses: setSearch(catalog, newQuery) }; } - case CatalogActions.Sort: { + case CatalogActions.Sort: return { ...catalog, sortQuery: action.query, courses: courses.sort(byAttribute(action.query.value)) }; - } - case CatalogActions.Filter: { + case CatalogActions.Filter: return { ...catalog, filters: { - ...catalog.filters, + ...filters, ...action.filters } }; - } case CatalogActions.SetCourseList: { // Here we filter to ensure there are no duplicate course entries. const allCourses = action.allCourses.filter( @@ -135,25 +128,24 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog }; } case CatalogActions.SetPill: { - const { filters } = catalog; const { pillItem } = action; const newItem = { label: pillItem.name, value: pillItem }; - let result: Partial = {}; + let newFilter: Partial = {}; const requirements = ['haas', 'ls', 'engineering', 'university']; if (requirements.includes(pillItem.category)) { if (filters.requirements?.find((value) => value.label === pillItem.name)) return catalog; - result = { requirements: [...(filters.requirements ?? []), newItem] }; + newFilter = { requirements: [...(filters.requirements ?? []), newItem] }; } else { - result = { [pillItem.category]: newItem }; + newFilter = { [pillItem.category]: newItem }; } return { ...catalog, filters: { ...filters, - ...result + ...newFilter } }; } @@ -162,10 +154,10 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog } } -const useCatalog = (): [CatalogContext, Dispatch] => [ - useContext(Context), - useContext(CatalogDispatch) -]; +const useCatalog = (): [ + Omit, + Dispatch +] => [useContext(Context), useContext(CatalogDispatch)]; export const useCatalogDispatch = (): Dispatch => useContext(CatalogDispatch); diff --git a/frontend/src/assets/scss/bt/grades/_graph.scss b/frontend/src/assets/scss/bt/grades/_graph.scss index d05c7fa9a..d8e8bc353 100644 --- a/frontend/src/assets/scss/bt/grades/_graph.scss +++ b/frontend/src/assets/scss/bt/grades/_graph.scss @@ -1,4 +1,7 @@ .grades-graph { + margin-top: 20px; + max-width: 90vw; + g.recharts-layer.recharts-cartesian-axis.recharts-xAxis.xAxis, g.recharts-layer.recharts-cartesian-axis.recharts-yAxis.yAxis { opacity: 0.5; From 7f6418417f39e55f85a90e3db71172c813a1b42d Mon Sep 17 00:00:00 2001 From: Jaden Date: Fri, 13 Oct 2023 16:34:42 -0700 Subject: [PATCH 24/42] move away from enum and infer key types --- .../Catalog/CatalogFilters/CatalogFilters.tsx | 18 +++---- .../app/Catalog/CatalogList/CatalogList.tsx | 6 +-- .../app/Catalog/CatalogView/CatalogView.tsx | 12 ++--- frontend/src/app/Catalog/index.ts | 3 -- .../app/Catalog/{Catalog.tsx => index.tsx} | 0 frontend/src/app/Catalog/types.ts | 27 ++++++++++ frontend/src/app/Catalog/useCatalog.tsx | 54 ++++--------------- 7 files changed, 54 insertions(+), 66 deletions(-) delete mode 100644 frontend/src/app/Catalog/index.ts rename frontend/src/app/Catalog/{Catalog.tsx => index.tsx} (100%) diff --git a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx index 7c67c8d90..132f4fbf9 100644 --- a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx +++ b/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx @@ -10,7 +10,7 @@ import BTLoader from 'components/Common/BTLoader'; import { useNavigate, useLocation, useParams } from 'react-router'; import styles from './CatalogFilters.module.scss'; import { SortDown, SortUp } from 'iconoir-react'; -import useCatalog, { CatalogActions as Actions } from '../useCatalog'; +import useCatalog from '../useCatalog'; import { FILTER_TEMPLATE, SORT_OPTIONS, putFilterOptions } from '../service'; const CatalogFilters = () => { @@ -28,7 +28,7 @@ const CatalogFilters = () => { useEffect(() => { const params = new URLSearchParams(location.search); - dispatch({ type: Actions.Search, query: params.get('q') ?? '' }); + dispatch({ type: 'search', query: params.get('q') ?? '' }); }, [dispatch, location.search]); useEffect(() => { @@ -37,7 +37,7 @@ const CatalogFilters = () => { const semester = options.find(({ label }) => label === slug?.semester) ?? null; dispatch({ - type: Actions.Filter, + type: 'filter', filters: { semester: semester ?? options[0] } }); } @@ -61,7 +61,7 @@ const CatalogFilters = () => { const handleFilterReset = () => { if (template) { const semester = template.semester.options[0] as FilterOption; - dispatch({ type: Actions.Reset, filters: { semester } }); + dispatch({ type: 'reset', filters: { semester } }); navigate({ pathname: `/catalog/${semester.value.name}` }); } }; @@ -71,7 +71,7 @@ const CatalogFilters = () => { meta: ActionMeta ) => { dispatch({ - type: Actions.Filter, + type: 'filter', filters: { [meta.name as CatalogFilterKey]: newValue } }); // Update the url slug if semester filter changes. @@ -99,7 +99,7 @@ const CatalogFilters = () => { { pathname: location.pathname, search: `q=${e.target.value}` }, { replace: true } ); - dispatch({ type: Actions.Search, query: e.target.value }); + dispatch({ type: 'search', query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -124,7 +124,7 @@ const CatalogFilters = () => { { pathname: location.pathname, search: `q=${e.target.value}` }, { replace: true } ); - dispatch({ type: Actions.Search, query: e.target.value }); + dispatch({ type: 'search', query: e.target.value }); }} type="search" placeholder="Search for a class..." @@ -139,10 +139,10 @@ const CatalogFilters = () => { options={SORT_OPTIONS} isSearchable={false} onChange={(newValue) => - dispatch({ type: Actions.Sort, query: newValue as SortOption }) + dispatch({ type: 'sort', query: newValue as SortOption }) } /> -
diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.tsx b/frontend/src/app/Catalog/CatalogList/CatalogList.tsx index ce55418a2..41305c362 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogList.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogList.tsx @@ -5,7 +5,7 @@ import { memo, useEffect } from 'react'; import useDimensions from 'react-cool-dimensions'; import styles from './CatalogList.module.scss'; import { useNavigate } from 'react-router'; -import useCatalog, { CatalogActions } from '../useCatalog'; +import useCatalog from '../useCatalog'; import { buildPlaylist } from '../service'; import Skeleton from 'react-loading-skeleton'; @@ -17,7 +17,7 @@ const CatalogList = () => { const [fetchCatalogList, { loading, called }] = useGetCoursesForFilterLazyQuery({ onCompleted: (data) => dispatch({ - type: CatalogActions.SetCourseList, + type: 'setCourseList', allCourses: data.allCourses.edges.map((edge) => edge.node) }) }); @@ -28,7 +28,7 @@ const CatalogList = () => { }, [fetchCatalogList, filters]); const handleCourseSelect = (course: CourseFragment) => { - dispatch({ type: CatalogActions.SetCourse, course }); + dispatch({ type: 'setCourse', course }); if (filters.semester) { const { name } = filters.semester.value; const { abbreviation, courseNumber } = course; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 5688b3bfa..f8ac665aa 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -11,7 +11,7 @@ import { useNavigate, useParams } from 'react-router'; import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; import { useSelector } from 'react-redux'; -import useCatalog, { CatalogActions } from '../useCatalog'; +import useCatalog from '../useCatalog'; import { sortPills } from '../service'; import CourseTabs from './CourseTabs'; @@ -40,7 +40,7 @@ const CatalogView = () => { onCompleted: (data) => { const course = data.allCourses.edges[0].node; if (course) { - dispatch({ type: CatalogActions.SetCourse, course }); + dispatch({ type: 'setCourse', course }); setOpen(true); } } @@ -74,10 +74,10 @@ const CatalogView = () => { const newCourse = data?.allCourses.edges[0].node; if (newCourse && newCourse?.id === course?.id) { - dispatch({ type: CatalogActions.SetCourse, course: newCourse }); + dispatch({ type: 'setCourse', course: newCourse }); setOpen(true); } else if (course) { - dispatch({ type: CatalogActions.SetCourse, course }); + dispatch({ type: 'setCourse', course }); setOpen(true); } }, [course, data, dispatch]); @@ -102,7 +102,7 @@ const CatalogView = () => { - )) - ) : ( - - )} -
*/} )}
diff --git a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx index b9800512b..7836ca9d3 100644 --- a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as Tabs from '@radix-ui/react-tabs'; import useCatalog from '../useCatalog'; -import CatalogViewSections from './__new_SectionTable'; +import CatalogViewSections from './SectionTable'; import { useEffect, useMemo, useState } from 'react'; import clsx from 'clsx'; import { sortSections } from 'utils/sections/sort'; diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 326dcd811..e9e7fe28a 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -1,7 +1,7 @@ import { SectionFragment } from 'graphql'; import { CSSProperties } from 'react'; import { formatSectionTime } from 'utils/sections/section'; -import catalogService from '../service'; +import { colorEnrollment, formatEnrollment } from '../service'; import Skeleton from 'react-loading-skeleton'; import denero from 'assets/img/eggs/denero.png'; @@ -14,8 +14,7 @@ import garcia from 'assets/img/eggs/garcia.png'; import { Clock, Group, PinAlt, User } from 'iconoir-react'; import styles from './CatalogView.module.scss'; - -const { colorEnrollment } = catalogService; +import clsx from 'clsx'; const easterEggImages = new Map([ ['DENERO J', denero], @@ -62,37 +61,46 @@ const SectionTable = ({ sections }: Props) => { {sections.length > 0 ? ( sections.map((section) => { const color = colorEnrollment(section.enrolled / section.enrolledMax); - + const enrolledPercent = formatEnrollment(section.enrolled / section.enrolledMax); return (
-
-
{section.kind}
- - - {section.instructor?.toLowerCase() || 'unknown'} - -
-
+
+
+
{section.kind}
+ + + + + {section.instructor?.toLowerCase() || 'unknown'} + +
+ +
+ {section.enrolled}/{section.enrolledMax} -
-
• {section.waitlisted} Waitlisted
- {/* • CCN {section.ccn} */} + + + + + {section.locationName || 'Unknown'} + + + + + {section.wordDays} {formatSectionTime(section)} +
-
- - - {section.locationName || 'Unknown'} - - - - {section.wordDays} {formatSectionTime(section)} - + +
+
+
{enrolledPercent} Enrolled
+
• {section.waitlisted} Waitlisted
); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 853b31236..8866ae2f2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -11,10 +11,7 @@ export default defineConfig({ server: { host: true, port: 3000, - open: true, - watch: { - usePolling: true - } + open: true }, publicDir: path.resolve(__dirname, 'public'), plugins: [ From d55a19361af6be705ab51585816026f340332242 Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 16 Oct 2023 19:19:15 -0700 Subject: [PATCH 30/42] fix tab border on hover --- .../src/app/Catalog/CatalogView/CatalogView.module.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index e26610f0b..1a8ded4a5 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -327,10 +327,10 @@ background: transparent; padding: 5px 0; font-size: 16px; - margin-bottom: -1.5px; + margin-bottom: -1px; color: $bt-light-text; padding: 5px 15px; - border-bottom: 3px solid transparent; + border-bottom: 1.5px solid transparent; transition-duration: 0.2s; border-radius: 8px 8px 0px 0px ; } @@ -339,9 +339,10 @@ cursor: pointer; color: $bt-base-text; background: #F8F8F8; + border-bottom: 1.5px solid #eaeaea; } .active { - border-bottom: 3px solid #2f80ed; + border-bottom: 1.5px solid #2f80ed !important; color: $bt-base-text; } From 02890e8352ca917e6e4c2837fd5d921ea3b7be54 Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 16 Oct 2023 20:11:27 -0700 Subject: [PATCH 31/42] new section table --- .../CatalogView/CatalogView.module.scss | 36 ++++++--- .../app/Catalog/CatalogView/SectionTable.tsx | 81 ++++++++++++++++--- 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 1a8ded4a5..16f23005f 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -210,7 +210,7 @@ padding: 12px 16px; border-radius: 16px; border: 1.5px solid #eaeaea; - gap: 10px; + gap: 40px; h6 { font-size: 16px; @@ -223,14 +223,14 @@ .sectionContainer { display: flex; - gap: 10px; - flex-direction: row; + flex-direction: column; } .separator { height: 1.5px; - width: calc(100% + 32px); - margin: 0px -16px; + // width: calc(100% + 32px); + // margin: 0px -16px; + width: 100%; background: #eaeaea; } @@ -266,15 +266,27 @@ } .sectionFooter { + // display: grid; + // grid-template-columns: 1fr 1fr 1fr; + // grid-template-rows: 1fr; display: flex; - flex-direction: row; - flex-wrap: wrap; + justify-content: first baseline; gap: 5px; color: $bt-light-text; font-size: 14px; text-transform: none !important; -} + span { + display: inline-flex; + justify-content: flex-start;; + align-items: center; + line-height: 20px; + gap: 4px; + color: $bt-light-text; + font-weight: 500; + text-align: right; + } +} .instructor { font-size: 14px; @@ -289,10 +301,10 @@ .enrolled { display: flex; - justify-content: flex-end; + justify-content: flex-start; align-items: center; font-size: 14px; - font-weight: 500; + // font-weight: 500; gap: 5px; img { @@ -332,13 +344,13 @@ padding: 5px 15px; border-bottom: 1.5px solid transparent; transition-duration: 0.2s; - border-radius: 8px 8px 0px 0px ; + border-radius: 8px 8px 0px 0px; } .tab:hover { cursor: pointer; color: $bt-base-text; - background: #F8F8F8; + background: #f8f8f8; border-bottom: 1.5px solid #eaeaea; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index e9e7fe28a..6ab9de95e 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -1,5 +1,5 @@ import { SectionFragment } from 'graphql'; -import { CSSProperties } from 'react'; +import { CSSProperties, useMemo } from 'react'; import { formatSectionTime } from 'utils/sections/section'; import { colorEnrollment, formatEnrollment } from '../service'; import Skeleton from 'react-loading-skeleton'; @@ -45,11 +45,21 @@ interface Props { } const SectionTable = ({ sections }: Props) => { - if (!sections) { + const [lectures, discussions, labs] = useMemo(() => { + if (!sections) return [null, null, null]; + + const lectures = sections.filter((section) => section.kind === 'Lecture'); + const discussions = sections.filter((section) => section.kind === 'Discussion'); + const labs = sections.filter((section) => section.kind === 'Laboratory'); + + return [lectures, discussions, labs]; + }, [sections]); + + if (!sections || sections.length === 0) { return ( @@ -58,7 +68,61 @@ const SectionTable = ({ sections }: Props) => { return (
- {sections.length > 0 ? ( +
+
Lectures
+ {lectures && + lectures.map((section) => { + const color = colorEnrollment(section.enrolled / section.enrolledMax); + + return ( +
+
{section.locationName || 'Unknown Location'}
+
+ + + {section.wordDays} {formatSectionTime(section)} + + • + + + {section.enrolled}/{section.enrolledMax} Enrolled + + •CCN: {section.ccn} +
+
+ ); + })} +
+
+
Discussions
+ {discussions && + discussions.map((section) => { + const color = colorEnrollment(section.enrolled / section.enrolledMax); + + return ( + <> +
+
{section.locationName || 'Unknown Location'}
+
+ + + {section.wordDays} {formatSectionTime(section)} + + • + + + {section.enrolled}/{section.enrolledMax} Enrolled + + •CCN: {section.ccn} +
+
+ {/*
*/} + + ); + })} +
+ + {/* {sections.length > 0 ? ( sections.map((section) => { const color = colorEnrollment(section.enrolled / section.enrolledMax); const enrolledPercent = formatEnrollment(section.enrolled / section.enrolledMax); @@ -77,6 +141,7 @@ const SectionTable = ({ sections }: Props) => { {section.instructor?.toLowerCase() || 'unknown'} + {section.waitlisted} Waitlisted
@@ -96,18 +161,12 @@ const SectionTable = ({ sections }: Props) => {
- -
-
-
{enrolledPercent} Enrolled
-
• {section.waitlisted} Waitlisted
-
); }) ) : (
There are no class sections for this course.
- )} + )} */}
); }; From cc1f0803166bc91111bef281eb61d4b2ee53c85b Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 6 Nov 2023 18:49:27 -0800 Subject: [PATCH 32/42] refactor to separate component --- .../CatalogView/CatalogView.module.scss | 50 +++--- .../app/Catalog/CatalogView/SectionTable.tsx | 152 +++--------------- .../Catalog/CatalogView/SectionTableItem.tsx | 68 ++++++++ 3 files changed, 108 insertions(+), 162 deletions(-) create mode 100644 frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 16f23005f..c86ea0fde 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -204,23 +204,6 @@ gap: 10px; } -.sectionItem { - display: flex; - flex-direction: column; - padding: 12px 16px; - border-radius: 16px; - border: 1.5px solid #eaeaea; - gap: 40px; - - h6 { - font-size: 16px; - min-height: 21px; - margin: 0; - font-weight: bold; - color: $bt-base-text; - } -} - .sectionContainer { display: flex; flex-direction: column; @@ -251,31 +234,35 @@ display: flex; flex-direction: column; justify-content: flex-start; - font-size: 14px; + font-size: 16px; +} - span { - display: inline-flex; - justify-content: flex-end; - align-items: center; - line-height: 20px; - gap: 4px; - color: $bt-light-text; +.sectionItem { + display: flex; + flex-direction: column; + padding: 12px 16px; + border-radius: 16px; + border: 1.5px solid #eaeaea; + gap: 30px; + + h6 { + font-size: 16px; + min-height: 21px; + margin: 0; font-weight: 500; - text-align: right; + color: $bt-base-text; } } .sectionFooter { - // display: grid; - // grid-template-columns: 1fr 1fr 1fr; - // grid-template-rows: 1fr; display: flex; justify-content: first baseline; gap: 5px; color: $bt-light-text; font-size: 14px; text-transform: none !important; - + border-bottom: 1.5px solid #eaeaea; + padding-bottom: 5px; span { display: inline-flex; justify-content: flex-start;; @@ -283,7 +270,8 @@ line-height: 20px; gap: 4px; color: $bt-light-text; - font-weight: 500; + font-weight: 400; + font-size: 14px; text-align: right; } } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 6ab9de95e..f3d2aba8f 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -1,44 +1,9 @@ import { SectionFragment } from 'graphql'; -import { CSSProperties, useMemo } from 'react'; -import { formatSectionTime } from 'utils/sections/section'; -import { colorEnrollment, formatEnrollment } from '../service'; +import { useMemo } from 'react'; import Skeleton from 'react-loading-skeleton'; -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 { Clock, Group, PinAlt, User } from 'iconoir-react'; - import styles from './CatalogView.module.scss'; -import clsx from 'clsx'; - -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 {}; -} +import SectionTableItem from './SectionTableItem'; interface Props { sections: SectionFragment[] | null; @@ -46,7 +11,7 @@ interface Props { const SectionTable = ({ sections }: Props) => { const [lectures, discussions, labs] = useMemo(() => { - if (!sections) return [null, null, null]; + if (!sections) return [[], [], []]; const lectures = sections.filter((section) => section.kind === 'Lecture'); const discussions = sections.filter((section) => section.kind === 'Discussion'); @@ -70,103 +35,28 @@ const SectionTable = ({ sections }: Props) => {
Lectures
- {lectures && - lectures.map((section) => { - const color = colorEnrollment(section.enrolled / section.enrolledMax); - - return ( -
-
{section.locationName || 'Unknown Location'}
-
- - - {section.wordDays} {formatSectionTime(section)} - - • - - - {section.enrolled}/{section.enrolledMax} Enrolled - - •CCN: {section.ccn} -
-
- ); - })} + {lectures && lectures.length > 0 ? ( + lectures.map((section) => ) + ) : ( +

No lectures available.

+ )}
Discussions
- {discussions && - discussions.map((section) => { - const color = colorEnrollment(section.enrolled / section.enrolledMax); - - return ( - <> -
-
{section.locationName || 'Unknown Location'}
-
- - - {section.wordDays} {formatSectionTime(section)} - - • - - - {section.enrolled}/{section.enrolledMax} Enrolled - - •CCN: {section.ccn} -
-
- {/*
*/} - - ); - })} + {discussions && discussions.length > 0 ? ( + discussions.map((section) => ) + ) : ( +

No discussions available.

+ )} +
+
+
Labs
+ {labs && labs.length > 0 ? ( + labs.map((section) => ) + ) : ( +

No labs available.

+ )}
- - {/* {sections.length > 0 ? ( - sections.map((section) => { - const color = colorEnrollment(section.enrolled / section.enrolledMax); - const enrolledPercent = formatEnrollment(section.enrolled / section.enrolledMax); - return ( -
-
-
-
{section.kind}
- - - - - {section.instructor?.toLowerCase() || 'unknown'} - - {section.waitlisted} Waitlisted -
- -
- - - {section.enrolled}/{section.enrolledMax} - - - - - {section.locationName || 'Unknown'} - - - - - {section.wordDays} {formatSectionTime(section)} - -
-
-
- ); - }) - ) : ( -
There are no class sections for this course.
- )} */}
); }; diff --git a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx new file mode 100644 index 000000000..0b6ad22f6 --- /dev/null +++ b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx @@ -0,0 +1,68 @@ +import { SectionFragment } from 'graphql'; +import { Clock, Group } from 'iconoir-react'; +import { colorEnrollment } from '../service'; +import { formatSectionTime } from 'utils/sections/section'; + + +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'; +import { CSSProperties } from 'react'; + +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 {}; +} + +interface SectionTableItem { + section: SectionFragment; +} + +const SectionTableItem = ({ section }: SectionTableItem) => { + const color = colorEnrollment(section.enrolled / section.enrolledMax); + + return ( +
+
{section.locationName || 'Unknown Location'}
+
+ + + {section.wordDays} {formatSectionTime(section)} + + • + + + {section.enrolled}/{section.enrolledMax} Enrolled + + •CCN: {section.ccn} +
+
+ ); +}; + +export default SectionTableItem; From f7e8d3d61667baf4b9de0650047ddf8e33951b97 Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 6 Nov 2023 19:12:01 -0800 Subject: [PATCH 33/42] revert ts conversion, clean up section style --- .../CatalogView/CatalogView.module.scss | 16 +- .../app/Catalog/CatalogView/CourseTabs.tsx | 2 +- .../Catalog/CatalogView/SectionTableItem.tsx | 17 +- .../components/GraphCard/GradesGraphCard.jsx | 2 +- .../src/components/Graphs/GradesGraph.jsx | 185 ++++++++++++++++++ .../src/components/Graphs/GradesGraph.tsx | 124 ------------ 6 files changed, 215 insertions(+), 131 deletions(-) create mode 100644 frontend/src/components/Graphs/GradesGraph.jsx delete mode 100644 frontend/src/components/Graphs/GradesGraph.tsx diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index c86ea0fde..73dede6b5 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -207,6 +207,18 @@ .sectionContainer { display: flex; flex-direction: column; + + & > p { + text-transform: capitalize; + font-size: 14px; + color: $bt-light-text; + } + + & > h6 { + display: flex; + gap: 4px; + align-items: flex-start; + } } .separator { @@ -263,9 +275,11 @@ text-transform: none !important; border-bottom: 1.5px solid #eaeaea; padding-bottom: 5px; + flex-wrap: wrap; + span { display: inline-flex; - justify-content: flex-start;; + justify-content: flex-start; align-items: center; line-height: 20px; gap: 4px; diff --git a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx index 7836ca9d3..f3af8b8a1 100644 --- a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx @@ -112,7 +112,7 @@ const CourseTabs = () => {
- +
diff --git a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx index 0b6ad22f6..1a8b37cd4 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx @@ -1,9 +1,8 @@ import { SectionFragment } from 'graphql'; -import { Clock, Group } from 'iconoir-react'; +import { Clock, Group, User } from 'iconoir-react'; import { colorEnrollment } from '../service'; import { formatSectionTime } from 'utils/sections/section'; - import denero from 'assets/img/eggs/denero.png'; import hug from 'assets/img/eggs/hug.png'; import hilf from 'assets/img/eggs/hilf.png'; @@ -47,8 +46,18 @@ const SectionTableItem = ({ section }: SectionTableItem) => { const color = colorEnrollment(section.enrolled / section.enrolledMax); return ( -
-
{section.locationName || 'Unknown Location'}
+
+
+ {/* */} + {section.locationName || 'Unknown Location'} +
+

+ {section.instructor?.toLowerCase() || 'unknown instructor'} +

diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx index 1db690871..dbc4b1f1e 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -106,7 +106,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { > {isMobile &&
Grade Distribution
} { + return ( +
+
+ empty state +

+ You have not added any
classes yet. +

+
+
+ ); +}; + +const MobileTooltip = (props) => { + const { active, payload, label } = props; + if (active && payload) { + const denominator = props.denominator; + const percentile = props.selectedPercentiles; + const numerator = percentile ? props.selectedPercentiles.numerator : 0; + const percentileLow = percentile ? percentileToString(percentile.percentile_low) : 0; + const percentileHigh = percentile ? percentileToString(percentile.percentile_high) : 0; + + return ( +
+
Grade: {label}
+

+ {props.course} • {props.semester} • {props.instructor} +

+

+ {percentileLow} - {percentileHigh} percentile +

+

+ {numerator}/{denominator} +

+
+ ); + } + return null; +}; + +const PercentageLabel = (props) => { + //todo: change text color + const { x, y, width, value } = props; + let percentage = value === 0 ? '' : value < 1 ? '<1%' : Math.round(value * 10) / 10 + '%'; + return ( + + {percentage} + + ); +}; + +export default function GradesGraph(props) { + const { + gradesData, + updateBarHover, + updateGraphHover, + course, + semester, + instructor, + selectedPercentiles, + denominator, + color, + isMobile + } = props; + + let numClasses = gradesData?.length || 0; + + const graphData = useMemo( + () => + ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'].map( + (letterGrade) => { + const ret = { + name: letterGrade + }; + for (const grade of gradesData) { + ret[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; + } + return ret; + } + ), + [gradesData] + ); + + const graphEmpty = gradesData?.length === 0; + + return ( +
+ {!isMobile ? ( + + + + {graphEmpty ? ( + + ) : ( + + )} + + [`${Math.round(value * 10) / 10}%`, name]} + cursor={graphEmpty ? { fill: '#fff' } : { fill: '#EAEAEA' }} + /> + + {!graphEmpty && + gradesData.map((item, i) => ( + + ))} + + + ) : ( + + + {!graphEmpty ? ( + + ) : ( + + )} + + {!graphEmpty ? ( + + } + /> + ) : null} + {gradesData.map((item, i) => ( + } + radius={[0, 4, 4, 0]} + /> + ))} + + + + )} + + {graphEmpty && } +
+ ); +} diff --git a/frontend/src/components/Graphs/GradesGraph.tsx b/frontend/src/components/Graphs/GradesGraph.tsx deleted file mode 100644 index bb67bf3c4..000000000 --- a/frontend/src/components/Graphs/GradesGraph.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { BarChart, Bar, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts'; -import { percentileToString } from '../../utils/utils'; -import { useMemo } from 'react'; - -import vars from '../../utils/variables'; -import { CourseFragment } from 'graphql'; -import { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent'; -import { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalChart'; - -const MobileTooltip = (props) => { - const { active, payload, label } = props; - if (active && payload) { - const denominator = props.denominator; - const percentile = props.selectedPercentiles; - const numerator = percentile ? props.selectedPercentiles.numerator : 0; - const percentileLow = percentile ? percentileToString(percentile.percentile_low) : 0; - const percentileHigh = percentile ? percentileToString(percentile.percentile_high) : 0; - - return ( -
-
Grade: {label}
-

- {props.course} • {props.semester} • {props.instructor} -

-

- {percentileLow} - {percentileHigh} percentile -

-

- {numerator}/{denominator} -

-
- ); - } - return null; -}; - -const PercentageLabel = (props) => { - //todo: change text color - const { x, y, width, value } = props; - const percentage = value === 0 ? '' : value < 1 ? '<1%' : Math.round(value * 10) / 10 + '%'; - return ( - - {percentage} - - ); -}; - -const grades = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'] as const; -type LetterGrade = (typeof grades)[number]; - -type legacyGradeData = { - [key in LetterGrade]: { - numerator: number; - denominator: number; - }; -}; - -type legacyGradeObject = { - colorId?: number; - id: string; - title: string; - semester: string; - instructor: string; - denominator: number; -} & legacyGradeData; - -interface GradesGraphProps { - gradeData: legacyGradeObject[] | null; - course: CourseFragment | null; - color?: string; - updateBarHover: CategoricalChartFunc; -} - -export default function GradesGraph(props: GradesGraphProps) { - const { gradeData, updateBarHover, color } = props; - - const graphData = useMemo(() => { - if (!gradeData) return null; - - return grades.map((letterGrade) => { - const result: Record = { name: letterGrade }; - - gradeData.forEach(({ denominator, id, ...grade }) => { - if (denominator === 0) return; - const percent = 100 * (grade[letterGrade].numerator / denominator); - result[id] = percent; - }); - - return result; - }); - }, [gradeData]); - - const formatTooltip = (value: ValueType, name: NameType) => [ - `${Math.round((value as number) * 10) / 10}%`, - name - ]; - - return ( -
- - - - - - {gradeData && - gradeData.map((item, i) => ( - - ))} - - -
- ); -} From a9e0620c28aa6a8b95690c163b480c9287188fc2 Mon Sep 17 00:00:00 2001 From: Jaden Date: Mon, 6 Nov 2023 19:18:06 -0800 Subject: [PATCH 34/42] fix crash if you enter the graph before the data has loaded --- .../src/components/Graphs/GradesGraph.jsx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/Graphs/GradesGraph.jsx b/frontend/src/components/Graphs/GradesGraph.jsx index 9b6b151d7..3bbe5d13d 100644 --- a/frontend/src/components/Graphs/GradesGraph.jsx +++ b/frontend/src/components/Graphs/GradesGraph.jsx @@ -72,23 +72,23 @@ export default function GradesGraph(props) { let numClasses = gradesData?.length || 0; - const graphData = useMemo( - () => - ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'].map( - (letterGrade) => { - const ret = { - name: letterGrade - }; - for (const grade of gradesData) { - ret[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; - } - return ret; + const graphData = useMemo(() => { + if (!gradesData) return null; + + return ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'].map( + (letterGrade) => { + const ret = { + name: letterGrade + }; + for (const grade of gradesData) { + ret[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; } - ), - [gradesData] - ); + return ret; + } + ); + }, [gradesData]); - const graphEmpty = gradesData?.length === 0; + const graphEmpty = gradesData && gradesData.length === 0; return (
@@ -112,6 +112,7 @@ export default function GradesGraph(props) { /> {!graphEmpty && + gradesData && gradesData.map((item, i) => ( ) : null} - {gradesData.map((item, i) => ( - } - radius={[0, 4, 4, 0]} - /> - ))} + {gradesData && + gradesData.map((item, i) => ( + } + radius={[0, 4, 4, 0]} + /> + ))} Date: Sat, 25 Nov 2023 11:35:47 -0800 Subject: [PATCH 35/42] rework section card design again --- .../CatalogView/CatalogView.module.scss | 89 +++++++++++-------- .../app/Catalog/CatalogView/SectionTable.tsx | 12 +-- .../Catalog/CatalogView/SectionTableItem.tsx | 26 +++--- .../src/components/Graphs/EnrollmentGraph.jsx | 2 +- .../src/components/Graphs/GradesGraph.jsx | 26 +++--- frontend/src/react-app-env.d.ts | 4 - frontend/tsconfig.json | 1 - 7 files changed, 85 insertions(+), 75 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 73dede6b5..cfb5a7617 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -198,71 +198,86 @@ } } -.sectionRoot { +.sections { display: flex; flex-direction: column; + border-radius: 16px; gap: 10px; -} -.sectionContainer { - display: flex; - flex-direction: column; - - & > p { - text-transform: capitalize; - font-size: 14px; - color: $bt-light-text; + h6 { + font-size: 16px; + min-height: 21px; + margin: 0; + font-weight: 500; + color: $bt-base-text; } - & > h6 { - display: flex; - gap: 4px; - align-items: flex-start; + p { + margin: 0; + font-size: 16px; + font-weight: 500; + color: $bt-base-text; } } -.separator { - height: 1.5px; - // width: calc(100% + 32px); - // margin: 0px -16px; - width: 100%; - background: #eaeaea; +.sectionRoot { + display: flex; + flex-direction: column; + gap: 20px; +} + +.sectionItem { + display: flex; + flex-direction: row; + border: 1.5px solid #eaeaea; + padding: 12px 16px; + border-radius: 12px; } .sectionLeft { display: flex; flex-direction: column; flex: 1; - font-weight: 500; h6 { display: flex; align-items: center; + font-weight: 700; min-height: 24px; } + + span { + display: flex; + align-items: center; + justify-content: flex-start; + text-transform: capitalize; + color: $bt-light-text; + font-weight: 400; + font-size: 14px; + text-align: right; + } } .sectionRight { display: flex; flex-direction: column; - justify-content: flex-start; + justify-content: flex-end; font-size: 16px; -} -.sectionItem { - display: flex; - flex-direction: column; - padding: 12px 16px; - border-radius: 16px; - border: 1.5px solid #eaeaea; - gap: 30px; + h6, + span { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + font-size: 14px; + color: $bt-light-text; + font-weight: 400; + text-align: right; + } - h6 { - font-size: 16px; - min-height: 21px; - margin: 0; - font-weight: 500; - color: $bt-base-text; + & > h6 { + font-size: 14px; } } @@ -273,7 +288,6 @@ color: $bt-light-text; font-size: 14px; text-transform: none !important; - border-bottom: 1.5px solid #eaeaea; padding-bottom: 5px; flex-wrap: wrap; @@ -359,4 +373,5 @@ .active { border-bottom: 1.5px solid #2f80ed !important; color: $bt-base-text; + background: #f8f8f8; } diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index f3d2aba8f..70051f10a 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -33,28 +33,28 @@ const SectionTable = ({ sections }: Props) => { return (
-
+
Lectures
{lectures && lectures.length > 0 ? ( lectures.map((section) => ) ) : ( -

No lectures available.

+ No lectures available. )}
-
+
Discussions
{discussions && discussions.length > 0 ? ( discussions.map((section) => ) ) : ( -

No discussions available.

+ No discussions available. )}
-
+
Labs
{labs && labs.length > 0 ? ( labs.map((section) => ) ) : ( -

No labs available.

+ No labs available. )}
diff --git a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx index 1a8b37cd4..0b57e49d1 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx @@ -47,28 +47,26 @@ const SectionTableItem = ({ section }: SectionTableItem) => { return (
-
- {/* */} - {section.locationName || 'Unknown Location'} -
-

- {section.instructor?.toLowerCase() || 'unknown instructor'} -

-
+
+
{section.locationName || 'Unknown Location'}
- - {section.wordDays} {formatSectionTime(section)} + {' '} + {section.instructor?.toLowerCase() || 'unknown instructor'} - • - +
+
+
{section.enrolled}/{section.enrolledMax} Enrolled +
+ + + {section.wordDays} {formatSectionTime(section)} - •CCN: {section.ccn}
); diff --git a/frontend/src/components/Graphs/EnrollmentGraph.jsx b/frontend/src/components/Graphs/EnrollmentGraph.jsx index d7b2986dd..8c842f7dd 100644 --- a/frontend/src/components/Graphs/EnrollmentGraph.jsx +++ b/frontend/src/components/Graphs/EnrollmentGraph.jsx @@ -119,7 +119,7 @@ export default function EnrollmentGraph(props) { name={`${item.title}`} type="monotone" dataKey={item.id} - stroke={vars.colors[item.colorId]} + stroke={color ? color : vars.colors[item.colorId]} strokeWidth={3} dot={false} activeDot={{ onMouseOver: (_, e) => updateLineHover(e.dataKey, e.payload.name) }} diff --git a/frontend/src/components/Graphs/GradesGraph.jsx b/frontend/src/components/Graphs/GradesGraph.jsx index 848929dd4..ac77cfa22 100644 --- a/frontend/src/components/Graphs/GradesGraph.jsx +++ b/frontend/src/components/Graphs/GradesGraph.jsx @@ -88,7 +88,7 @@ export default function GradesGraph(props) { ); }, [gradesData]); - const graphEmpty = gradesData && gradesData.length === 0; + const isLoaded = gradesData && graphData; return (
@@ -102,24 +102,26 @@ export default function GradesGraph(props) { margin={{ top: 0, right: 0, left: -15, bottom: 0 }} > - {!graphEmpty ? ( + {isLoaded ? ( ) : ( )} - [`${Math.round(value * 10) / 10}%`, name]} - cursor={graphEmpty ? { fill: '#fff' } : { fill: '#EAEAEA' }} - /> + {isLoaded && ( + [`${Math.round(value * 10) / 10}%`, name]} + cursor={{ fill: '#EAEAEA' }} + /> + )} - {!graphEmpty && + {isLoaded && gradesData.map((item, i) => ( @@ -130,7 +132,7 @@ export default function GradesGraph(props) { ) : ( // mobile or narrow viewport
- + - {!graphEmpty ? ( + {isLoaded ? ( ) : ( )} - {!graphEmpty ? ( + {isLoaded ? ( )} - {graphEmpty && } + {!isLoaded && }
); } diff --git a/frontend/src/react-app-env.d.ts b/frontend/src/react-app-env.d.ts index 15d0c8b42..ee9857a62 100644 --- a/frontend/src/react-app-env.d.ts +++ b/frontend/src/react-app-env.d.ts @@ -1,6 +1,2 @@ /// /// - -declare module 'react-ios-switch' { - export = Switch; -} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e4e81120e..c9ff20514 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -21,7 +21,6 @@ "types": ["vite/client"] }, "include": ["src"], - "types": ["vite/client"], "references": [ { "path": "./tsconfig.vite.json" From b52701d405638dcfe0d67a480784e743141ada94 Mon Sep 17 00:00:00 2001 From: Jaden Date: Sat, 25 Nov 2023 11:44:51 -0800 Subject: [PATCH 36/42] clean up styles and improve mobile xp --- .../CatalogView/CatalogView.module.scss | 67 +++++-------------- .../app/Catalog/CatalogView/CatalogView.tsx | 14 ++-- 2 files changed, 22 insertions(+), 59 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index cfb5a7617..b58cc5dd9 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -53,6 +53,8 @@ height: 100%; z-index: 300; width: 100vw; + padding: 20px; + padding-right: 10px; // Add size of fixed header. padding-top: 30px; &[data-modal='false'] { @@ -232,6 +234,20 @@ border: 1.5px solid #eaeaea; padding: 12px 16px; border-radius: 12px; + gap: 10px; +} + +@include media(mobile) { + .sectionItem { + flex-direction: column; + } + + .sectionRight { + & > h6, span { + justify-content: flex-start !important; + text-align: left !important; + } + } } .sectionLeft { @@ -261,7 +277,7 @@ .sectionRight { display: flex; flex-direction: column; - justify-content: flex-end; + justify-content: flex-start; font-size: 16px; h6, @@ -281,53 +297,6 @@ } } -.sectionFooter { - display: flex; - justify-content: first baseline; - gap: 5px; - color: $bt-light-text; - font-size: 14px; - text-transform: none !important; - padding-bottom: 5px; - flex-wrap: wrap; - - span { - display: inline-flex; - justify-content: flex-start; - align-items: center; - line-height: 20px; - gap: 4px; - color: $bt-light-text; - font-weight: 400; - font-size: 14px; - text-align: right; - } -} - -.instructor { - font-size: 14px; - text-transform: capitalize; - min-height: 24px; - color: $bt-light-text; - - span { - margin-right: 5px; - } -} - -.enrolled { - display: flex; - justify-content: flex-start; - align-items: center; - font-size: 14px; - // font-weight: 500; - gap: 5px; - - img { - width: 14px; - } -} - .tabList { display: flex; flex-direction: row; @@ -344,7 +313,7 @@ } .tabGraph { - margin: 0 -20px 0 -25px; + margin: 0 -10px 0 -15px; } .tab { diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 3180fd772..1f91125d5 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -98,7 +98,10 @@ const CatalogView = () => { return (
- + {course && ( <> -

{course.abbreviation} {course.courseNumber}

From 0129bf11ba105e67ebfb1bfb3d501a59fb98d3fc Mon Sep 17 00:00:00 2001 From: Jaden Date: Sat, 25 Nov 2023 11:59:09 -0800 Subject: [PATCH 37/42] restructure sectionView, unify modal state with url --- .../src/app/Catalog/CatalogFilters/index.ts | 3 -- .../{CatalogFilters.tsx => index.tsx} | 0 frontend/src/app/Catalog/CatalogList/index.ts | 3 -- .../{CatalogList.tsx => index.tsx} | 0 .../CatalogView/CatalogView.module.scss | 45 +++++++++---------- .../app/Catalog/CatalogView/CatalogView.tsx | 17 +++---- .../CatalogView/{ => Tabs}/SectionTable.tsx | 2 +- .../{ => Tabs}/SectionTableItem.tsx | 4 +- .../{CourseTabs.tsx => Tabs/index.tsx} | 6 +-- 9 files changed, 34 insertions(+), 46 deletions(-) delete mode 100644 frontend/src/app/Catalog/CatalogFilters/index.ts rename frontend/src/app/Catalog/CatalogFilters/{CatalogFilters.tsx => index.tsx} (100%) delete mode 100644 frontend/src/app/Catalog/CatalogList/index.ts rename frontend/src/app/Catalog/CatalogList/{CatalogList.tsx => index.tsx} (100%) rename frontend/src/app/Catalog/CatalogView/{ => Tabs}/SectionTable.tsx (97%) rename frontend/src/app/Catalog/CatalogView/{ => Tabs}/SectionTableItem.tsx (95%) rename frontend/src/app/Catalog/CatalogView/{CourseTabs.tsx => Tabs/index.tsx} (96%) diff --git a/frontend/src/app/Catalog/CatalogFilters/index.ts b/frontend/src/app/Catalog/CatalogFilters/index.ts deleted file mode 100644 index 978858c71..000000000 --- a/frontend/src/app/Catalog/CatalogFilters/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CatalogFilters from './CatalogFilters'; - -export default CatalogFilters; diff --git a/frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx b/frontend/src/app/Catalog/CatalogFilters/index.tsx similarity index 100% rename from frontend/src/app/Catalog/CatalogFilters/CatalogFilters.tsx rename to frontend/src/app/Catalog/CatalogFilters/index.tsx diff --git a/frontend/src/app/Catalog/CatalogList/index.ts b/frontend/src/app/Catalog/CatalogList/index.ts deleted file mode 100644 index 7e8e5cec0..000000000 --- a/frontend/src/app/Catalog/CatalogList/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CatalogList from './CatalogList'; - -export default CatalogList; diff --git a/frontend/src/app/Catalog/CatalogList/CatalogList.tsx b/frontend/src/app/Catalog/CatalogList/index.tsx similarity index 100% rename from frontend/src/app/Catalog/CatalogList/CatalogList.tsx rename to frontend/src/app/Catalog/CatalogList/index.tsx diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index b58cc5dd9..25178113e 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -57,34 +57,32 @@ padding-right: 10px; // Add size of fixed header. padding-top: 30px; - &[data-modal='false'] { - display: none; - } - [data-modal='true'] { - display: flex; - } + } + + &[data-modal='false'] { + display: none; + } + + &[data-modal='true'] { + display: flex; } } .modalButton { - display: none; - @include media(mobile) { - display: flex; - justify-content: flex-start; - align-items: center; - text-align: center; - border: none; - background: none; - line-height: 20px; - color: $bt-grey-text; - gap: 10px; - padding: 0; - margin-bottom: 15px; + display: flex; + justify-content: flex-start; + align-items: center; + text-align: center; + border: none; + background: none; + line-height: 20px; + color: $bt-grey-text; + gap: 10px; + padding: 0; - &:hover { - color: #2f80ed; - } + &:hover { + color: #2f80ed; } } @@ -243,7 +241,8 @@ } .sectionRight { - & > h6, span { + & > h6, + span { justify-content: flex-start !important; text-align: left !important; } diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 1f91125d5..7248e5ae2 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useMemo, useState } from 'react'; +import { memo, useEffect, useMemo } from 'react'; import people from 'assets/svg/catalog/people.svg'; import chart from 'assets/svg/catalog/chart.svg'; import book from 'assets/svg/catalog/book.svg'; @@ -13,7 +12,7 @@ import ReadMore from './ReadMore'; import { useSelector } from 'react-redux'; import useCatalog from '../useCatalog'; import { sortPills } from '../service'; -import CourseTabs from './CourseTabs'; +import CourseTabs from './Tabs'; import styles from './CatalogView.module.scss'; import { CatalogSlug } from '../types'; @@ -24,13 +23,14 @@ const skeleton = [...Array(8).keys()]; const CatalogView = () => { const [{ course }, dispatch] = useCatalog(); - const [isOpen, setOpen] = useState(false); const navigate = useNavigate(); const { abbreviation, courseNumber, semester } = useParams(); const legacyId = useSelector( + // eslint-disable-next-line @typescript-eslint/no-explicit-any (state: any) => state.enrollment?.context?.courses?.find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber )?.id ?? null ); @@ -40,7 +40,6 @@ const CatalogView = () => { const course = data.allCourses.edges[0].node; if (course) { dispatch({ type: 'setCourse', course }); - setOpen(true); } } }); @@ -48,8 +47,6 @@ const CatalogView = () => { useEffect(() => { const [sem, year] = semester?.split(' ') ?? [null, null]; - if (!abbreviation || !courseNumber) setOpen(false); - const variables = { abbreviation: abbreviation ?? null, courseNumber: courseNumber ?? null, @@ -74,10 +71,8 @@ const CatalogView = () => { if (newCourse && newCourse?.id === course?.id) { dispatch({ type: 'setCourse', course: newCourse }); - setOpen(true); } else if (course) { dispatch({ type: 'setCourse', course }); - setOpen(true); } }, [course, data, dispatch]); @@ -97,7 +92,7 @@ const CatalogView = () => { const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; return ( -
+
{ }} > - Back to Courses + Close {course && ( <> diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/Tabs/SectionTable.tsx similarity index 97% rename from frontend/src/app/Catalog/CatalogView/SectionTable.tsx rename to frontend/src/app/Catalog/CatalogView/Tabs/SectionTable.tsx index 70051f10a..80a4241fe 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/Tabs/SectionTable.tsx @@ -2,7 +2,7 @@ import { SectionFragment } from 'graphql'; import { useMemo } from 'react'; import Skeleton from 'react-loading-skeleton'; -import styles from './CatalogView.module.scss'; +import styles from '../CatalogView.module.scss'; import SectionTableItem from './SectionTableItem'; interface Props { diff --git a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx b/frontend/src/app/Catalog/CatalogView/Tabs/SectionTableItem.tsx similarity index 95% rename from frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx rename to frontend/src/app/Catalog/CatalogView/Tabs/SectionTableItem.tsx index 0b57e49d1..e9525b212 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTableItem.tsx +++ b/frontend/src/app/Catalog/CatalogView/Tabs/SectionTableItem.tsx @@ -1,6 +1,6 @@ import { SectionFragment } from 'graphql'; import { Clock, Group, User } from 'iconoir-react'; -import { colorEnrollment } from '../service'; +import { colorEnrollment } from '../../service'; import { formatSectionTime } from 'utils/sections/section'; import denero from 'assets/img/eggs/denero.png'; @@ -11,7 +11,7 @@ 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'; +import styles from '../CatalogView.module.scss'; import { CSSProperties } from 'react'; const easterEggImages = new Map([ diff --git a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx b/frontend/src/app/Catalog/CatalogView/Tabs/index.tsx similarity index 96% rename from frontend/src/app/Catalog/CatalogView/CourseTabs.tsx rename to frontend/src/app/Catalog/CatalogView/Tabs/index.tsx index f3af8b8a1..0cbf81b78 100644 --- a/frontend/src/app/Catalog/CatalogView/CourseTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/Tabs/index.tsx @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as Tabs from '@radix-ui/react-tabs'; -import useCatalog from '../useCatalog'; +import useCatalog from '../../useCatalog'; import CatalogViewSections from './SectionTable'; import { useEffect, useMemo, useState } from 'react'; import clsx from 'clsx'; import { sortSections } from 'utils/sections/sort'; -import styles from './CatalogView.module.scss'; +import styles from '../CatalogView.module.scss'; import GradesGraph from 'components/Graphs/GradesGraph'; import { fetchCatalogGrades, @@ -16,7 +16,7 @@ import { import { useSelector } from 'react-redux'; import EnrollmentGraph from 'components/Graphs/EnrollmentGraph'; import { useParams } from 'react-router-dom'; -import { CatalogSlug } from '../types'; +import { CatalogSlug } from '../../types'; type TabKey = 'times' | 'grades' | 'enrollment'; From 8121c5e72e23550f3583e54285970e492d7225f3 Mon Sep 17 00:00:00 2001 From: Jaden Date: Sun, 26 Nov 2023 15:14:19 -0800 Subject: [PATCH 38/42] saving courses --- .../Catalog/CatalogList/CatalogListItem.tsx | 31 ++-- .../CatalogView/CatalogView.module.scss | 59 +++++++ .../app/Catalog/CatalogView/CatalogView.tsx | 153 ++++++++++++++---- frontend/src/app/Catalog/types.ts | 5 +- frontend/src/app/Catalog/useCatalog.tsx | 42 ++++- 5 files changed, 245 insertions(+), 45 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx index 522babdda..cdedb0c48 100644 --- a/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx +++ b/frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx @@ -23,10 +23,11 @@ type CatalogListItemProps = { handleCourseSelect: (course: CourseFragment) => void; isSelected: boolean; }; - style: CSSProperties; + style?: CSSProperties; + simple?: boolean; }; -const CatalogListItem = ({ style, data }: CatalogListItemProps) => { +const CatalogListItem = ({ style, data, simple }: CatalogListItemProps) => { const { course, handleCourseSelect, isSelected } = data; const [{ course: currentCourse }] = useCatalog(); @@ -63,20 +64,28 @@ const CatalogListItem = ({ style, data }: CatalogListItemProps) => { {isSaved ? : }
)} - - {course.letterAverage !== '' ? course.letterAverage : ''} - + {!simple && ( + + {course.letterAverage !== '' ? course.letterAverage : ''} + + )}
-
- - {formatEnrollment(course.enrolledPercentage)} enrolled - - • {course.units ? formatUnits(course.units) : 'N/A'} -
+ {!simple && ( +
+ + {formatEnrollment(course.enrolledPercentage)} enrolled + + • {course.units ? formatUnits(course.units) : 'N/A'} +
+ )}
); }; +CatalogListItem.defaultProps = { + simple: false +}; + export default memo(CatalogListItem, areEqual); diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 25178113e..96fbf41b1 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -343,3 +343,62 @@ color: $bt-base-text; background: #f8f8f8; } + +.saveRoot { + display: flex; + flex-direction: row; + padding: 0 20px; + gap: 20px; +} + +.saveContainer { + display: flex; + flex-direction: column; + flex: 1; +} + +.header { + // margin-bottom: 20px; + display: flex; + justify-content: space-between; + flex-direction: row; + align-items: baseline; + gap: 30px; + padding: 6px 23px; + + span { + font-size: 20px; + font-weight: 600; + } + + border-bottom: 1.5px solid #eaeaea; + + button { + color: #2f80ed; + transition: 0.2s; + border: none; + background: none; + padding: 0 8px; + border-radius: 4px; + font-size: 14px; + &:hover { + background: hsla(0, 0%, 92.2%, 0.6196078431); + } + } +} + +.seperator { + display: flex; + height: 100%; + width: 1px; + background: #eaeaea; +} + +.emptyView { + display: flex; + align-items: center; + justify-content: center; + color: $bt-light-text; + padding: 20px 0; + text-align: center; +} diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 7248e5ae2..3d7debb21 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -5,7 +5,7 @@ import book from 'assets/svg/catalog/book.svg'; import launch from 'assets/svg/catalog/launch.svg'; import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow.svg'; import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils'; -import { PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; +import { CourseFragment, PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; import { useNavigate, useParams } from 'react-router'; import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; @@ -15,14 +15,36 @@ import { sortPills } from '../service'; import CourseTabs from './Tabs'; import styles from './CatalogView.module.scss'; -import { CatalogSlug } from '../types'; +import { CatalogSlug, FilterOption } from '../types'; import Meta from 'components/Common/Meta'; import { courseToName } from 'lib/courses/course'; +import { useUser } from 'graphql/hooks/user'; +import CatalogListItem from '../CatalogList/CatalogListItem'; const skeleton = [...Array(8).keys()]; +// Debug saved courses with dummy array since logging in doesn't work in DEV. +// const dummy = [ +// { +// id: 'Q291cnNlVHlwZToyMTcxOQ==', +// abbreviation: 'MATH', +// courseNumber: '56', +// description: +// 'This is a first course in Linear Algebra. Core topics include: algebra and geometry of vectors and matrices; systems of linear equations and Gaussian elimination; eigenvalues and eigenvectors; Gram-Schmidt and least squares; symmetric matrices and quadratic forms; singular value decomposition and other factorizations. Time permitting, additional topics may include: Markov chains and Perron-Frobenius, dimensionality reduction, or linear programming. This course differs from Math 54 in that it does not cover Differential Equations, but focuses on Linear Algebra motivated by first applications in Data Science and Statistics.', +// title: 'Linear Algebra ', +// gradeAverage: -1, +// letterAverage: 'A', +// openSeats: 0, +// enrolledPercentage: 1, +// enrolled: 292, +// enrolledMax: 292, +// units: '4.0', +// __typename: 'CourseType' +// } +// ]; + const CatalogView = () => { - const [{ course }, dispatch] = useCatalog(); + const [{ course, filters, recentCourses }, dispatch] = useCatalog(); const navigate = useNavigate(); const { abbreviation, courseNumber, semester } = useParams(); @@ -44,26 +66,29 @@ const CatalogView = () => { } }); + const { user } = useUser(); + const savedClasses = useMemo(() => user?.savedClasses ?? [], [user?.savedClasses]); + useEffect(() => { - const [sem, year] = semester?.split(' ') ?? [null, null]; + const [sem, year] = semester?.split(' ') ?? [undefined, undefined]; + + type coursePayload = { + abbreviation: string; + courseNumber: string; + semester: string; + year: string; + }; const variables = { - abbreviation: abbreviation ?? null, - courseNumber: courseNumber ?? null, - semester: sem?.toLowerCase() ?? null, + abbreviation: abbreviation, + courseNumber: courseNumber, + semester: sem?.toLowerCase(), year: year }; // Only fetch the course if every parameter has a value. - if (Object.values(variables).every((value) => value !== null)) - getCourse({ - variables: variables as { - abbreviation: string; - courseNumber: string; - semester: string; - year: string; - } - }); + if (Object.values(variables).every((value) => value !== undefined)) + getCourse({ variables: variables as coursePayload }); }, [getCourse, abbreviation, courseNumber, semester]); useEffect(() => { @@ -92,22 +117,23 @@ const CatalogView = () => { const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; return ( -
+ <> - - {course && ( - <> + {course ? ( +
+

{course.abbreviation} {course.courseNumber}

@@ -178,9 +204,76 @@ const CatalogView = () => { - +
+ ) : ( +
+
+
+ Recents + +
+ {recentCourses.length > 0 ? ( +
+ {recentCourses.map((course) => ( + { + dispatch({ type: 'setCourse', course }); + navigate({ + pathname: `/catalog/${(filters.semester as FilterOption)?.value?.name}/${ + course.abbreviation + }/${course.courseNumber}`, + search: location.search + }); + }, + isSelected: false + }} + /> + ))} +
+ ) : ( +
Recently viewed courses will appear here!
+ )} +
+
+
+
+ Saved +
+ {savedClasses.length > 0 ? ( +
+ {savedClasses.map((course) => ( + { + dispatch({ type: 'setCourse', course }); + navigate({ + pathname: `/catalog/${(filters.semester as FilterOption)?.value?.name}/${ + course.abbreviation + }/${course.courseNumber}`, + search: location.search + }); + }, + isSelected: false + }} + /> + ))} +
+ ) : ( +
+ Click on the bookmarks in the course list while signed-in to save courses! +
+ )} +
+
)} -
+ ); }; diff --git a/frontend/src/app/Catalog/types.ts b/frontend/src/app/Catalog/types.ts index 15827083d..bcd257dd1 100644 --- a/frontend/src/app/Catalog/types.ts +++ b/frontend/src/app/Catalog/types.ts @@ -78,6 +78,7 @@ export type CatalogContext = { courses: CourseOverviewFragment[]; course: CourseFragment | null; courseIndex: Fuse | null; + recentCourses: CourseFragment[]; }; export type CatalogAction = @@ -88,6 +89,8 @@ export type CatalogAction = | { type: 'filter'; filters: Partial } | { type: 'setCourseList'; allCourses: CatalogContext['courses'] } | { type: 'reset'; filters?: Partial } - | { type: 'setPill'; pillItem: PlaylistType }; + | { type: 'setPill'; pillItem: PlaylistType } + | { type: 'clearRecents' }; + export type CatalogActions = CatalogAction[keyof CatalogAction]; diff --git a/frontend/src/app/Catalog/useCatalog.tsx b/frontend/src/app/Catalog/useCatalog.tsx index 0f49c1700..85226966a 100644 --- a/frontend/src/app/Catalog/useCatalog.tsx +++ b/frontend/src/app/Catalog/useCatalog.tsx @@ -3,6 +3,12 @@ import { DEFAULT_FILTERS, SORT_OPTIONS, buildCourseIndex } from './service'; import { CatalogFilters, CatalogContext, CatalogAction } from './types'; import { searchCatalog, flipCourseList } from './service'; import { byAttribute } from 'lib/courses/sorting'; +import { CourseFragment } from 'graphql'; + +const getRecents = (): CourseFragment[] => { + const recents = localStorage.getItem('recentlyViewedCourses'); + return recents ? JSON.parse(recents) : []; +}; const initialCatalog: CatalogContext = { filters: DEFAULT_FILTERS, @@ -12,7 +18,8 @@ const initialCatalog: CatalogContext = { courses: [], allCourses: [], courseIndex: null, - course: null + course: null, + recentCourses: getRecents() }; const Context = createContext(initialCatalog); @@ -41,11 +48,15 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog sortDir: sortDir === 'ASC' ? 'DESC' : 'ASC', courses: flipCourseList(courses, sortQuery) }; - case 'setCourse': + case 'setCourse': { + const recentCourses = setRecentlyViewed(action.course); + return { ...catalog, - course: action.course + course: action.course, + recentCourses }; + } case 'search': { const newQuery = action.query ?? searchQuery; return { @@ -114,6 +125,13 @@ function catalogReducer(catalog: CatalogContext, action: CatalogAction): Catalog } }; } + case 'clearRecents': { + localStorage.removeItem('recentlyViewedCourses'); + return { + ...catalog, + recentCourses: [] + }; + } default: return catalog; } @@ -133,4 +151,22 @@ const setSearch = (catalog: CatalogContext, query: string | null = null) => { ); }; +const setRecentlyViewed = (course: CourseFragment | null): CourseFragment[] => { + let recents = getRecents(); + + if (!course) return recents; + + // If the course was already viewed, don't add it. + if (recents.find((c) => c.id === course.id)) return recents; + recents = [course, ...recents]; + + // Limit the size; + const maxSize = 25; + recents = recents.slice(-maxSize); + + localStorage.setItem('recentlyViewedCourses', JSON.stringify(recents)); + + return recents; +}; + export default useCatalog; From 67a28b36e03eea583e441260a56cb5689907e780 Mon Sep 17 00:00:00 2001 From: Jaden Date: Sun, 26 Nov 2023 15:24:15 -0800 Subject: [PATCH 39/42] no panel on mobile --- .../src/app/Catalog/CatalogView/CatalogView.module.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 96fbf41b1..148860eb8 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -351,6 +351,12 @@ gap: 20px; } +@include media(mobile) { + .saveRoot { + display: none; + } +} + .saveContainer { display: flex; flex-direction: column; From b5f94fd340ad8672f386d230787c506828da3229 Mon Sep 17 00:00:00 2001 From: Jaden Date: Sun, 26 Nov 2023 16:22:39 -0800 Subject: [PATCH 40/42] fix container scrolling and tablet view --- .../CatalogView/CatalogView.module.scss | 22 ++++++++++++++----- .../app/Catalog/CatalogView/CatalogView.tsx | 3 +-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index 148860eb8..7a89d2b7f 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -57,7 +57,6 @@ padding-right: 10px; // Add size of fixed header. padding-top: 30px; - } &[data-modal='false'] { @@ -348,7 +347,8 @@ display: flex; flex-direction: row; padding: 0 20px; - gap: 20px; + grid-area: view; + overflow: hidden; } @include media(mobile) { @@ -361,6 +361,18 @@ display: flex; flex-direction: column; flex: 1; + overflow-y: scroll; + overflow-x: hidden; + margin: 20px 0px; + + &:first-child { + padding-right: 10px; + // border-right: 1.5px solid #eaeaea; + } + + &:last-child { + padding-left: 10px; + } } .header { @@ -386,7 +398,7 @@ background: none; padding: 0 8px; border-radius: 4px; - font-size: 14px; + font-size: 16px; &:hover { background: hsla(0, 0%, 92.2%, 0.6196078431); } @@ -395,8 +407,8 @@ .seperator { display: flex; - height: 100%; - width: 1px; + width: 1.5px; + margin: 20px 0px; background: #eaeaea; } diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 3d7debb21..2b5ffcd46 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -209,7 +209,7 @@ const CatalogView = () => {
- Recents + Recent
{recentCourses.length > 0 ? ( @@ -238,7 +238,6 @@ const CatalogView = () => {
Recently viewed courses will appear here!
)}
-
Saved From 45519776fcb45034f90c21de3e775dd78ed6ee57 Mon Sep 17 00:00:00 2001 From: Jaden Date: Sun, 26 Nov 2023 19:49:20 -0800 Subject: [PATCH 41/42] remove used import --- frontend/src/Berkeleytime.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index b0b72c73e..dc460a79c 100644 --- a/frontend/src/Berkeleytime.tsx +++ b/frontend/src/Berkeleytime.tsx @@ -5,7 +5,6 @@ import useDimensions from 'react-cool-dimensions'; import easterEgg from 'utils/easterEgg'; import Routes from './Routes'; import { fetchEnrollContext, fetchGradeContext } from 'redux/actions'; -import { IconoirProvider } from 'iconoir-react'; const Berkeleytime = () => { const dispatch = useDispatch(); From c05832ece7efd69cbcd35e1c34db9b78bc4a89ad Mon Sep 17 00:00:00 2001 From: Jaden Date: Sun, 26 Nov 2023 20:06:09 -0800 Subject: [PATCH 42/42] capital Clear --- frontend/src/app/Catalog/CatalogView/CatalogView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 2b5ffcd46..ecf1623d6 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -210,7 +210,7 @@ const CatalogView = () => {
Recent - +
{recentCourses.length > 0 ? (