From 3a38f5dd84ede5b093aa503a153f4bdef48995c4 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:05:07 -0700 Subject: [PATCH 01/27] downloaded types for object-hash --- frontend/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/package.json b/frontend/package.json index e6cb986bd..2444f2db5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@apollo/client": "^3.2.5", + "@types/object-hash": "^3.0.5", "axios": "^1.2.6", "bootstrap": "^4.5.2", "color": "^3.1.3", From 7fee9d907347c820f2aba2780a90cf0de4a28a23 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:05:15 -0700 Subject: [PATCH 02/27] updated Store.ts --- frontend/src/redux/store.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/redux/store.ts b/frontend/src/redux/store.ts index 70f531ab6..d184ad22c 100644 --- a/frontend/src/redux/store.ts +++ b/frontend/src/redux/store.ts @@ -6,6 +6,7 @@ import enrollment from './reducers/enrollment'; import authReducer from './auth/reducer'; import { commonReducer } from './common/reducer'; +import { TypedUseSelectorHook, useSelector } from 'react-redux'; const reducer = combineReducers({ grade, @@ -16,6 +17,8 @@ const reducer = combineReducers({ export type ReduxState = ReturnType; +export const useReduxSelector: TypedUseSelectorHook = useSelector + const composeEnhancers = (window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; -export default createStore(reducer, composeEnhancers(applyMiddleware(thunkMiddleware))); +export default createStore(reducer, composeEnhancers(applyMiddleware(thunkMiddleware))); \ No newline at end of file From a4eb84a09a09393b637bb91457e5b01715e45af2 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:08:13 -0700 Subject: [PATCH 03/27] Error.view: typed --- frontend/src/views/{Error.jsx => Error.tsx} | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) rename frontend/src/views/{Error.jsx => Error.tsx} (79%) diff --git a/frontend/src/views/Error.jsx b/frontend/src/views/Error.tsx similarity index 79% rename from frontend/src/views/Error.jsx rename to frontend/src/views/Error.tsx index 9bdb72c27..44b0fa207 100644 --- a/frontend/src/views/Error.jsx +++ b/frontend/src/views/Error.tsx @@ -1,8 +1,8 @@ -import { Container, Row, Col, ButtonToolbar, ButtonGroup } from 'react-bootstrap'; -import empty_graph from '../assets/img/images/empty-graph.png'; import { Button } from 'bt/custom'; +import { ButtonGroup, ButtonToolbar, Col, Container, Row } from 'react-bootstrap'; +import empty_graph from '../assets/img/images/empty-graph.png'; -function Error() { +export default function Error() { return (
@@ -16,7 +16,7 @@ function Error() {

Here are a couple of things you can do.

- @@ -27,5 +27,3 @@ function Error() {
); } - -export default Error; From f42df21c4ee4aff96d331552ce2eaa0881d27d98 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:11:00 -0700 Subject: [PATCH 04/27] Releases: typed --- frontend/src/views/{Releases.jsx => Releases.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename frontend/src/views/{Releases.jsx => Releases.tsx} (91%) diff --git a/frontend/src/views/Releases.jsx b/frontend/src/views/Releases.tsx similarity index 91% rename from frontend/src/views/Releases.jsx rename to frontend/src/views/Releases.tsx index 34c0b5b79..55e81741d 100644 --- a/frontend/src/views/Releases.jsx +++ b/frontend/src/views/Releases.tsx @@ -1,4 +1,4 @@ -import { Container, Row, Col, ButtonToolbar } from 'react-bootstrap'; +import { ButtonToolbar, Col, Container, Row } from 'react-bootstrap'; import releases from '../lib/releases'; import Log from '../components/Releases/Log'; From 76b1013441b0c32f518f145c2b40b0660a1df63b Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:24:16 -0700 Subject: [PATCH 05/27] Release: typed components & lib --- frontend/src/components/Releases/{Log.jsx => Log.tsx} | 6 +++--- frontend/src/lib/releases.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) rename frontend/src/components/Releases/{Log.jsx => Log.tsx} (85%) diff --git a/frontend/src/components/Releases/Log.jsx b/frontend/src/components/Releases/Log.tsx similarity index 85% rename from frontend/src/components/Releases/Log.jsx rename to frontend/src/components/Releases/Log.tsx index 940f81112..2d4efa8dc 100644 --- a/frontend/src/components/Releases/Log.jsx +++ b/frontend/src/components/Releases/Log.tsx @@ -1,4 +1,6 @@ -function Log({ date, whatsNew, fixes }) { +import { ReleaseType } from 'lib/releases'; + +export default function Log({ date, whatsNew, fixes }: ReleaseType) { return (
@@ -31,5 +33,3 @@ function Log({ date, whatsNew, fixes }) {
); } - -export default Log; diff --git a/frontend/src/lib/releases.ts b/frontend/src/lib/releases.ts index 6928dd38f..a60581db4 100644 --- a/frontend/src/lib/releases.ts +++ b/frontend/src/lib/releases.ts @@ -1,4 +1,6 @@ -const releases = [ +export type ReleaseType = { date: string; whatsNew: string[]; fixes: string[] }; + +const releases: ReleaseType[] = [ { date: 'Jan 24, 2021', whatsNew: [ From 8adaa9491545e031bf1d711544b7e7df8257362b Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:24:25 -0700 Subject: [PATCH 06/27] utils: typed --- frontend/src/utils/{utils.jsx => utils.tsx} | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) rename frontend/src/utils/{utils.jsx => utils.tsx} (84%) diff --git a/frontend/src/utils/utils.jsx b/frontend/src/utils/utils.tsx similarity index 84% rename from frontend/src/utils/utils.jsx rename to frontend/src/utils/utils.tsx index 8c40e07ba..d202d2f56 100644 --- a/frontend/src/utils/utils.jsx +++ b/frontend/src/utils/utils.tsx @@ -7,7 +7,7 @@ * @param {string} text text in the paragraph tag * @param {number} percentage percentage from 0.0 to 1.0 */ -function applyIndicatorPercent(text, percentage) { +function applyIndicatorPercent(text: string, percentage: number) { let theme = 'bt-indicator-red'; if (percentage < 0.34) { theme = 'bt-indicator-green'; @@ -23,7 +23,7 @@ function applyIndicatorPercent(text, percentage) { * @param {string} text text in the paragraph tag * @param {string | null} grade grade, either as a string (ex. "B+") or null */ -function applyIndicatorGrade(grade) { +function applyIndicatorGrade(grade: string | null) { if (grade === null) { return N/A; } @@ -46,7 +46,7 @@ function applyIndicatorGrade(grade) { * ex: "4.0" -> "4 Units" * "2.0 - 12.0" -> "2-12 Units" */ -function formatUnits(units) { +function formatUnits(units: string) { return `${units} Unit${units === '1.0' || units === '1' ? '' : 's'}` .replace(/.0/g, '') .replace(/ - /, '-') @@ -54,7 +54,7 @@ function formatUnits(units) { } /** Accepts a percentile between 0 and 1, converts it to a string. */ -function percentileToString(percentile) { +function percentileToString(percentile: number) { if (percentile === 1) { return '100th'; } @@ -83,7 +83,7 @@ function percentileToString(percentile) { } } -function getGradeColor(grade) { +function getGradeColor(grade: string | undefined) { if (grade === undefined) { return ''; } else if (grade.includes('A') || grade === 'P') { @@ -95,7 +95,11 @@ function getGradeColor(grade) { } } -function getEnrollmentDay(selectedPoint, telebears) { +/** + * TODO: remove the any's + * + */ +function getEnrollmentDay(selectedPoint: any, telebears: any) { let period = ''; let daysAfterPeriodStarts = 0; if (selectedPoint.day < telebears.phase2_start_day) { @@ -111,14 +115,14 @@ function getEnrollmentDay(selectedPoint, telebears) { return { period, daysAfterPeriodStarts }; } -function formatPercentage(num) { - if (num === -1) { +function formatPercentage(number: number) { + if (number === -1) { return 'N/A'; } - return (num * 100).toFixed(1).toString() + '%'; + return (number * 100).toFixed(1).toString() + '%'; } -function applyIndicatorEnrollment(enrolled, enrolledMax, percentage) { +function applyIndicatorEnrollment(enrolled: number, enrolledMax: number, percentage: number) { let theme; if (percentage < 0.34) { theme = 'bt-indicator-green'; From 527816823f85ab3b42275bcb1acb893f760b7d95 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 30 Oct 2023 22:40:56 -0700 Subject: [PATCH 07/27] Dashboard: typed these components are no longer used though --- .../{CurrentSchedule.jsx => CurrentSchedule.tsx} | 9 ++++++--- .../{PastSchedule.jsx => PastSchedule.tsx} | 11 +++++++---- .../Dashboard/{Profile.jsx => Profile.tsx} | 10 ++++++---- frontend/src/components/Dashboard/type.ts | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) rename frontend/src/components/Dashboard/{CurrentSchedule.jsx => CurrentSchedule.tsx} (81%) rename frontend/src/components/Dashboard/{PastSchedule.jsx => PastSchedule.tsx} (79%) rename frontend/src/components/Dashboard/{Profile.jsx => Profile.tsx} (86%) create mode 100644 frontend/src/components/Dashboard/type.ts diff --git a/frontend/src/components/Dashboard/CurrentSchedule.jsx b/frontend/src/components/Dashboard/CurrentSchedule.tsx similarity index 81% rename from frontend/src/components/Dashboard/CurrentSchedule.jsx rename to frontend/src/components/Dashboard/CurrentSchedule.tsx index 48df75079..5a33d7d1e 100644 --- a/frontend/src/components/Dashboard/CurrentSchedule.jsx +++ b/frontend/src/components/Dashboard/CurrentSchedule.tsx @@ -1,6 +1,9 @@ -/* eslint-disable */ +import { CurrentScheduleProps } from './type'; -function CurrentSchedule({ current }) { +/** + * @audit this file is not used + */ +function CurrentSchedule({ current }: CurrentScheduleProps) { return (
@@ -10,7 +13,7 @@ function CurrentSchedule({ current }) {
{current.semester}
{current.classes.map((currentClass) => ( -

+

{currentClass.name} {currentClass.units} Units

))} diff --git a/frontend/src/components/Dashboard/PastSchedule.jsx b/frontend/src/components/Dashboard/PastSchedule.tsx similarity index 79% rename from frontend/src/components/Dashboard/PastSchedule.jsx rename to frontend/src/components/Dashboard/PastSchedule.tsx index d2d56e00d..52315a703 100644 --- a/frontend/src/components/Dashboard/PastSchedule.jsx +++ b/frontend/src/components/Dashboard/PastSchedule.tsx @@ -1,6 +1,9 @@ -/* eslint-disable */ +import { PastScheduleProps } from './type'; -function PastSchedule({ past }) { +/** + * @audit this file is not used + */ +function PastSchedule({ past }: PastScheduleProps) { return (
@@ -8,10 +11,10 @@ function PastSchedule({ past }) { Past Semesters {past.map((semester) => ( -
+
{semester.semester}
{semester.classes.map((pastClass) => ( -

+

{pastClass.name} {pastClass.units} Units

))} diff --git a/frontend/src/components/Dashboard/Profile.jsx b/frontend/src/components/Dashboard/Profile.tsx similarity index 86% rename from frontend/src/components/Dashboard/Profile.jsx rename to frontend/src/components/Dashboard/Profile.tsx index a0090b97d..8824e4fc8 100644 --- a/frontend/src/components/Dashboard/Profile.jsx +++ b/frontend/src/components/Dashboard/Profile.tsx @@ -1,8 +1,10 @@ -/* eslint-disable */ - import jemma from 'assets/img/about/2020-21/michael_1.jpg'; +import { ProfileProps } from './type'; -function Profile({ profile }) { +/** + * @audit this file is not used + */ +function Profile({ profile }: ProfileProps) { return (
@@ -16,7 +18,7 @@ function Profile({ profile }) {

Major(s)

{profile.majors.map((major) => ( -

{major}

+

{major}

))}
diff --git a/frontend/src/components/Dashboard/type.ts b/frontend/src/components/Dashboard/type.ts new file mode 100644 index 000000000..1c23a93d3 --- /dev/null +++ b/frontend/src/components/Dashboard/type.ts @@ -0,0 +1,16 @@ +export type CurrentScheduleProps = { + current: ScheduleSemesterType; +}; + +export type PastScheduleProps = { + past: ScheduleSemesterType[]; +}; + +export type ProfileProps = { + profile: { name: string; pic: string; majors: string[]; academic_career: string; level: string }; +}; + +export type ScheduleSemesterType = { + semester: string; + classes: { name: string; units: number }[]; +}; From 2d0f86ae737754bdab88c8b7380eb2a01d42791b Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 00:07:03 -0800 Subject: [PATCH 08/27] Redux: typed --- frontend/src/redux/actionTypes.js | 13 - frontend/src/redux/actionTypes.ts | 206 ++++++++ frontend/src/redux/actions.js | 464 ---------------- frontend/src/redux/actions.ts | 496 ++++++++++++++++++ frontend/src/redux/common/reducer.ts | 6 +- .../reducers/{enrollment.js => enrollment.ts} | 45 +- .../src/redux/reducers/{grade.js => grade.ts} | 45 +- 7 files changed, 746 insertions(+), 529 deletions(-) delete mode 100644 frontend/src/redux/actionTypes.js create mode 100644 frontend/src/redux/actionTypes.ts delete mode 100644 frontend/src/redux/actions.js create mode 100644 frontend/src/redux/actions.ts rename frontend/src/redux/reducers/{enrollment.js => enrollment.ts} (71%) rename frontend/src/redux/reducers/{grade.js => grade.ts} (61%) diff --git a/frontend/src/redux/actionTypes.js b/frontend/src/redux/actionTypes.js deleted file mode 100644 index 42cbb9155..000000000 --- a/frontend/src/redux/actionTypes.js +++ /dev/null @@ -1,13 +0,0 @@ -export const UPDATE_GRADE_CONTEXT = 'UPDATE_GRADE_CONTEXT'; -export const GRADE_ADD_COURSE = 'GRADE_ADD_COURSE'; -export const GRADE_REMOVE_COURSE = 'GRADE_REMOVE_COURSE'; -export const UPDATE_GRADE_DATA = 'UPDATE_GRADE_DATA'; -export const UPDATE_GRADE_SELECTED = 'UPDATE_GRADE_SELECTED'; -export const GRADE_RESET = 'GRADE_RESET'; - -export const UPDATE_ENROLL_CONTEXT = 'UPDATE_ENROLL_CONTEXT'; -export const ENROLL_ADD_COURSE = 'ENROLL_ADD_COURSE'; -export const ENROLL_REMOVE_COURSE = 'ENROLL_REMOVE_COURSE'; -export const UPDATE_ENROLL_DATA = 'UPDATE_ENROLL_DATA'; -export const UPDATE_ENROLL_SELECTED = 'UPDATE_ENROLL_SELECTED'; -export const ENROLL_RESET = 'ENROLL_RESET'; diff --git a/frontend/src/redux/actionTypes.ts b/frontend/src/redux/actionTypes.ts new file mode 100644 index 000000000..948964ae6 --- /dev/null +++ b/frontend/src/redux/actionTypes.ts @@ -0,0 +1,206 @@ +export const UPDATE_GRADE_CONTEXT = 'UPDATE_GRADE_CONTEXT'; +export const GRADE_ADD_COURSE = 'GRADE_ADD_COURSE'; +export const GRADE_REMOVE_COURSE = 'GRADE_REMOVE_COURSE'; +export const UPDATE_GRADE_DATA = 'UPDATE_GRADE_DATA'; +export const UPDATE_GRADE_SELECTED = 'UPDATE_GRADE_SELECTED'; +export const GRADE_RESET = 'GRADE_RESET'; + +export const UPDATE_ENROLL_CONTEXT = 'UPDATE_ENROLL_CONTEXT'; +export const ENROLL_ADD_COURSE = 'ENROLL_ADD_COURSE'; +export const ENROLL_REMOVE_COURSE = 'ENROLL_REMOVE_COURSE'; +export const UPDATE_ENROLL_DATA = 'UPDATE_ENROLL_DATA'; +export const UPDATE_ENROLL_SELECTED = 'UPDATE_ENROLL_SELECTED'; +export const ENROLL_RESET = 'ENROLL_RESET'; + +export type CourseSnapshotType = { + abbreviation: string; + course_number: string; + id: number; +}; + +export type UnformattedCourseType = { + colorId: string; + courseID: number; + id: string; + instructor: string; + sections: (number | string)[]; + semester: string; +}; + +export type FormattedCourseDataType = { course: string; title: string }; + +export type FormattedCourseType = UnformattedCourseType & FormattedCourseDataType; + +export type BaseDataType = { + colorId: string; + course_id: number; + id: string; + instructor: string; + subtitle: string; + title: string; +}; + +export type GradesDataType = BaseDataType & { + course_gpa: number; + course_letter: string; + denominator: number; + section_gpa: number; + section_letter: string; + semester: string; +} & { + [grade in GRADE]: { + percent: number; + numerator: number; + percentile_high: number; + percentile_low: number; + }; +}; + +export type EnrollmentDataType = BaseDataType & { + data: { + date: string; + day: number; + enrolled: number; + enrolled_max: number; + enrolled_percent: number; + waitlisted: number; + waitlisted_max: number; + waitlisted_percent: number; + }[]; + enrolled_max: number; + enrolled_percent_max: number; + enrolled_scale_max: number; + section_id: number; + section_name: string; + telebears: { + adj_start_date: string; + adj_start_day: number; + phase1_end_date: number; + phase1_start_date: string; + phase1_start_day: number; + phase2_end_date: number; + phase2_start_date: string; + phase2_start_day: number; + }; + title: string; + waitlisted_max: number; + waitlisted_percent_max: number; + waitlisted_scale_max: number; +}; + +export enum GRADE { + 'A+' = 'A+', + 'A' = 'A', + 'A-' = 'A-', + 'B+' = 'B+', + 'B' = 'B', + 'B-' = 'B-', + 'C+' = 'C+', + 'C' = 'C', + 'C-' = 'C-', + 'D' = 'D', + 'F' = 'F', + 'P' = 'P', + 'NP' = 'NP' +} + +export type GradeSelectedType = { + grade_id: number; + instructor: string; + section_number: string; + semester: string; + year: string; +}; + +export type SectionType = { + sections: { instructor: string; section_id: number; section_number: string }[]; + semester: string; + year: string; +}; + +export type UpdatedClassType = { + value: number; + label: string; + course: CourseSnapshotType; +}; + +export type GradeAction = + | { type: typeof UPDATE_GRADE_CONTEXT; payload: { data: { courses: CourseSnapshotType[] } } } + | { type: typeof GRADE_RESET } + | { + type: typeof GRADE_ADD_COURSE; + payload: { + formattedCourse: FormattedCourseType; + }; + } + | { + type: typeof GRADE_REMOVE_COURSE; + payload: { + id: string; + color: string; + }; + } + | { + type: typeof UPDATE_GRADE_DATA; + payload: { + gradesData: GradesDataType[]; + }; + } + | { + type: typeof UPDATE_GRADE_SELECTED; + payload: { + data: GradeSelectedType[]; + }; + }; + +export type EnrollAction = + | { + type: typeof UPDATE_ENROLL_CONTEXT; + payload: { data: { courses: CourseSnapshotType[] } }; + } + | { + type: typeof UPDATE_ENROLL_DATA; + payload: { enrollmentData: EnrollmentDataType[] }; + } + | { + type: typeof UPDATE_ENROLL_SELECTED; + payload: { + sections: SectionType[]; + }; + } + | { + type: typeof ENROLL_RESET; + } + | { + type: typeof ENROLL_ADD_COURSE; + payload: { + formattedCourse: FormattedCourseType; + }; + } + | { + type: typeof ENROLL_REMOVE_COURSE; + payload: { + id: string; + color: string; + }; + }; + +type BaseState = { + context: { courses: CourseSnapshotType[] }; + selectedCourses: FormattedCourseType[]; + selectPrimary: string; + selectSecondary: string | { value: string; label: string }; + usedColorIds: string[]; +}; + +export type GradeState = BaseState & { + gradesData: GradesDataType[]; + sections: GradeSelectedType[]; + graphData: { name: string }[]; +}; + +export type EnrollmentState = BaseState & { + enrollmentData: EnrollmentDataType[]; + sections: SectionType[]; + graphData: { name: number }[]; +}; diff --git a/frontend/src/redux/actions.js b/frontend/src/redux/actions.js deleted file mode 100644 index 574ff99c7..000000000 --- a/frontend/src/redux/actions.js +++ /dev/null @@ -1,464 +0,0 @@ -/* eslint-disable */ -import axios from 'axios'; -import hash from 'object-hash'; -import { - UPDATE_GRADE_CONTEXT, - GRADE_ADD_COURSE, - GRADE_REMOVE_COURSE, - GRADE_RESET, - UPDATE_GRADE_DATA, - UPDATE_GRADE_SELECTED, - UPDATE_ENROLL_CONTEXT, - ENROLL_RESET, - ENROLL_ADD_COURSE, - ENROLL_REMOVE_COURSE, - UPDATE_ENROLL_DATA, - UPDATE_ENROLL_SELECTED -} from './actionTypes'; - -axios.defaults.baseURL = import.meta.env.PROD ? axios.defaults.baseURL : 'https://staging.berkeleytime.com'; - -// update grade list -const updateGradeContext = (data) => ({ - type: UPDATE_GRADE_CONTEXT, - payload: { - data - } -}); - -export const gradeReset = () => ({ - type: GRADE_RESET -}); - -// add displayed course to the grade page -const gradeAddCourse = (formattedCourse) => ({ - type: GRADE_ADD_COURSE, - payload: { - formattedCourse - } -}); - -export const gradeRemoveCourse = (id, color) => ({ - type: GRADE_REMOVE_COURSE, - payload: { - id, - color - } -}); - -const updateGradeData = (gradesData) => ({ - type: UPDATE_GRADE_DATA, - payload: { - gradesData - } -}); - -const updatedGradeSelected = (data) => ({ - type: UPDATE_GRADE_SELECTED, - payload: { - data - } -}); - -// update enroll list -const updateEnrollContext = (data) => ({ - type: UPDATE_ENROLL_CONTEXT, - payload: { - data - } -}); - -export const enrollReset = () => ({ - type: ENROLL_RESET -}); - -// add displayed course to the enroll page -const enrollAddCourse = (formattedCourse) => ({ - type: ENROLL_ADD_COURSE, - payload: { - formattedCourse - } -}); - -export const enrollRemoveCourse = (id, color) => ({ - type: ENROLL_REMOVE_COURSE, - payload: { - id, - color - } -}); - -export const updateEnrollData = (enrollmentData) => ({ - type: UPDATE_ENROLL_DATA, - payload: { - enrollmentData - } -}); - -const updatedEnrollSelected = (sections) => ({ - type: UPDATE_ENROLL_SELECTED, - payload: { - sections - } -}); - -export function fetchGradeContext() { - return (dispatch) => - axios.get('/api/grades/grades_json/').then( - (res) => { - dispatch(updateGradeContext(res.data)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeClass(course) { - return (dispatch) => - axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( - (res) => { - const courseData = res.data; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(gradeAddCourse(formattedCourse)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeData(classData) { - const promises = []; - for (const course of classData) { - const { sections } = course; - const url = `/api/grades/sections/${sections.join('&')}/`; - promises.push(axios.get(url)); - } - return (dispatch) => - axios.all(promises).then( - (data) => { - let gradesData = data.map((res, i) => { - let gradesData = res.data; - gradesData['id'] = classData[i].id; - gradesData['instructor'] = - classData[i].instructor === 'all' ? 'All Instructors' : classData[i].instructor; - gradesData['semester'] = - classData[i].semester === 'all' ? 'All Semesters' : classData[i].semester; - gradesData['colorId'] = classData[i].colorId; - return gradesData; - }); - dispatch(updateGradeData(gradesData)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeSelected(updatedClass) { - const url = `/api/grades/course_grades/${updatedClass.value}/`; - return (dispatch) => - axios.get(url).then( - (res) => { - dispatch(updatedGradeSelected(res.data)); - // if (updatedClass.addSelected) { - // this.addSelected(); - // this.handleClassSelect({value: updatedClass.value, addSelected: false}); - // } - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeFromUrl(url, navigate) { - const toUrlForm = (s) => s.replace('/', '_').toLowerCase().split(' ').join('-'); - const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1); - let courseUrls = url.split('/')[2].split('&'); - const urlData = []; - let promises = []; - for (const c of courseUrls) { - let cUrl = c.split('-'); - let semester, instructor; - if (cUrl[2] === 'all') { - semester = cUrl[2]; - instructor = cUrl.slice(3).join('-'); - } else if (cUrl[4] === '_') { - semester = capitalize(cUrl[2]) + ' ' + cUrl[3] + ' / ' + cUrl[5]; - instructor = cUrl.slice(6).join('-').replace('_', '/'); - } else { - semester = capitalize(cUrl[2]) + ' ' + cUrl[3]; - instructor = cUrl.slice(4).join('-').replace('_', '/'); - } - urlData.push({ - colorId: cUrl[0], - courseID: cUrl[1], - semester: semester, - instructor: instructor - }); - let u = `/api/grades/course_grades/${cUrl[1]}/`; - promises.push(axios.get(u)); - } - let courses = []; - let success = true; - return (dispatch) => - axios - .all(promises) - .then( - (data) => { - courses = data.map((res, i) => { - try { - let instructor = urlData[i].instructor; - let semester = urlData[i].semester; - let sections = []; - if (instructor === 'all') { - res.data.map((item, i) => (sections[i] = item.grade_id)); - } else { - let matches = []; - if (instructor.includes('/')) { - matches = res.data.filter( - (item) => - instructor === toUrlForm(item.instructor) + '-/-' + item.section_number - ); - matches.map((item, i) => (sections[i] = item.grade_id)); - instructor = matches[0].instructor + ' / ' + matches[0].section_number; - } else { - matches = res.data.filter((item) => instructor === toUrlForm(item.instructor)); - matches.map((item, i) => (sections[i] = item.grade_id)); - instructor = matches[0].instructor; - } - } - if (semester !== 'all') { - let matches = []; - if (semester.split(' ').length > 2) { - matches = res.data.filter( - (item) => - semester === - capitalize(item.semester) + ' ' + item.year + ' / ' + item.section_number - ); - } else { - matches = res.data.filter( - (item) => semester === capitalize(item.semester) + ' ' + item.year - ); - } - let allSems = matches.map((item) => item.grade_id); - sections = sections.filter((item) => allSems.includes(item)); - } - let formattedCourse = { - courseID: parseInt(urlData[i].courseID), - instructor: instructor, - semester: semester, - sections: sections - }; - formattedCourse.id = hash(formattedCourse); - formattedCourse.colorId = urlData[i].colorId; - return formattedCourse; - } catch (err) { - success = false; - navigate('/error'); - } - }); - }, - (error) => console.log('An error occurred.', error) - ) - .then(() => { - if (success) { - promises = []; - for (const course of courses) { - const u = `/api/catalog/catalog_json/course/${course.courseID}/`; - promises.push(axios.get(u)); - } - axios.all(promises).then( - (data) => { - data.map((res, i) => { - const courseData = res.data; - const course = courses[i]; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(gradeAddCourse(formattedCourse)); - }); - }, - (error) => console.log('An error occurred.', error) - ); - } - }); -} - -export function fetchEnrollContext() { - return async (dispatch, getState) => { - // Avoid fetching enrollment data twice. - if (getState().enrollment.context?.courses) { - return; - } - - const res = await axios.get('/api/enrollment/enrollment_json/'); - dispatch(updateEnrollContext(res.data)); - }; -} - -export function fetchEnrollClass(course) { - return (dispatch) => - axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( - (res) => { - const courseData = res.data; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(enrollAddCourse(formattedCourse)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollData(classData) { - const promises = []; - for (const course of classData) { - const { instructor, courseID, semester, sections } = course; - let url; - if (instructor === 'all') { - const [sem, year] = semester.split(' '); - url = `/api/enrollment/aggregate/${courseID}/${sem.toLowerCase()}/${year}/`; - } else { - url = `/api/enrollment/data/${sections[0]}/`; - } - promises.push(axios.get(url)); - } - return (dispatch) => - axios.all(promises).then( - (data) => { - let enrollmentData = data.map((res, i) => { - let enrollmentData = res.data; - enrollmentData['id'] = classData[i].id; - enrollmentData['colorId'] = classData[i].colorId; - return enrollmentData; - }); - dispatch(updateEnrollData(enrollmentData)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollSelected(updatedClass) { - const url = `/api/enrollment/sections/${updatedClass.value}/`; - return (dispatch) => - axios.get(url).then( - (res) => { - dispatch(updatedEnrollSelected(res.data)); - // if (updatedClass.addSelected) { - // this.addSelected(); - // this.handleClassSelect({value: updatedClass.value, addSelected: false}); - // } - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollFromUrl(url, navigate) { - const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1); - let courseUrls = url.split('/')[2].split('&'); - const urlData = []; - let promises = []; - for (const c of courseUrls) { - let cUrl = c.split('-'); - let semester = capitalize(cUrl[2]) + ' ' + cUrl[3]; - urlData.push({ - colorId: cUrl[0], - courseID: cUrl[1], - semester: semester, - section: cUrl[4] - }); - let u = `/api/enrollment/sections/${cUrl[1]}/`; - promises.push(axios.get(u)); - } - let courses = []; - let success = true; - return (dispatch) => - axios - .all(promises) - .then( - (data) => { - courses = data.map((res, i) => { - try { - let semester = urlData[i].semester; - let section = - urlData[i].section === 'all' ? urlData[i].section : parseInt(urlData[i].section); - let sections = [section]; - let instructor = 'all'; - let match = []; - if (section === 'all') { - match = res.data.filter( - (item) => semester === capitalize(item.semester) + ' ' + item.year - )[0]; - sections = match.sections.map((item) => item.section_id); - } else { - match = res.data.map((item) => - item.sections.filter((item) => item.section_id == section) - ); - match = match.filter((item) => item.length !== 0); - instructor = match[0][0].instructor + ' / ' + match[0][0].section_number; - } - let formattedCourse = { - courseID: parseInt(urlData[i].courseID), - instructor: instructor, - semester: semester, - sections: sections - }; - formattedCourse.id = hash(formattedCourse); - formattedCourse.colorId = urlData[i].colorId; - return formattedCourse; - } catch (err) { - success = false; - navigate('/error'); - } - }); - }, - (error) => console.log('An error occurred.', error) - ) - .then(() => { - if (success) { - promises = []; - for (const course of courses) { - const u = `/api/catalog/catalog_json/course/${course.courseID}/`; - promises.push(axios.get(u)); - } - axios.all(promises).then( - (data) => { - data.map((res, i) => { - const courseData = res.data; - const course = courses[i]; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(enrollAddCourse(formattedCourse)); - }); - }, - (error) => console.log('An error occurred.', error) - ); - } - }); -} diff --git a/frontend/src/redux/actions.ts b/frontend/src/redux/actions.ts new file mode 100644 index 000000000..1b6f42851 --- /dev/null +++ b/frontend/src/redux/actions.ts @@ -0,0 +1,496 @@ +/* eslint-disable */ +import axios, { AxiosResponse } from 'axios'; +import hash from 'object-hash'; +import { NavigateFunction } from 'react-router-dom'; +import { Action } from 'redux'; +import { ThunkAction } from 'redux-thunk'; +import { + CourseSnapshotType, + ENROLL_ADD_COURSE, + ENROLL_REMOVE_COURSE, + ENROLL_RESET, + EnrollmentDataType, + FormattedCourseDataType, + FormattedCourseType, + GRADE_ADD_COURSE, + GRADE_REMOVE_COURSE, + GRADE_RESET, + GradeSelectedType, + GradesDataType, + SectionType, + UPDATE_ENROLL_CONTEXT, + UPDATE_ENROLL_DATA, + UPDATE_ENROLL_SELECTED, + UPDATE_GRADE_CONTEXT, + UPDATE_GRADE_DATA, + UPDATE_GRADE_SELECTED, + UnformattedCourseType, + UpdatedClassType +} from './actionTypes'; +import { ReduxState } from './store'; + +axios.defaults.baseURL = import.meta.env.PROD + ? axios.defaults.baseURL + : 'https://staging.berkeleytime.com'; + +// update grade list +const updateGradeContext = (data: { courses: CourseSnapshotType[] }) => ({ + type: UPDATE_GRADE_CONTEXT, + payload: { + data + } +}); + +export const gradeReset = () => ({ + type: GRADE_RESET +}); + +// add displayed course to the grade page +const gradeAddCourse = (formattedCourse: FormattedCourseType) => ({ + type: GRADE_ADD_COURSE, + payload: { + formattedCourse + } +}); + +export const gradeRemoveCourse = (id: string, color: string) => ({ + type: GRADE_REMOVE_COURSE, + payload: { + id, + color + } +}); + +const updateGradeData = (gradesData: GradesDataType[]) => ({ + type: UPDATE_GRADE_DATA, + payload: { + gradesData + } +}); + +const updatedGradeSelected = (data: GradeSelectedType[]) => ({ + type: UPDATE_GRADE_SELECTED, + payload: { + data + } +}); + +// update enroll list +const updateEnrollContext = (data: { courses: CourseSnapshotType[] }) => ({ + type: UPDATE_ENROLL_CONTEXT, + payload: { + data + } +}); + +export const enrollReset = () => ({ + type: ENROLL_RESET +}); + +// add displayed course to the enroll page +const enrollAddCourse = (formattedCourse: FormattedCourseType) => ({ + type: ENROLL_ADD_COURSE, + payload: { + formattedCourse + } +}); + +export const enrollRemoveCourse = (id: string, color: string) => ({ + type: ENROLL_REMOVE_COURSE, + payload: { + id, + color + } +}); + +export const updateEnrollData = (enrollmentData: EnrollmentDataType[]) => ({ + type: UPDATE_ENROLL_DATA, + payload: { + enrollmentData + } +}); + +const updatedEnrollSelected = (sections: SectionType[]) => ({ + type: UPDATE_ENROLL_SELECTED, + payload: { + sections + } +}); + +export function fetchGradeContext(): ThunkAction { + return (dispatch) => + axios.get('/api/grades/grades_json/').then( + (res) => { + dispatch(updateGradeContext(res.data)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeClass( + course: UnformattedCourseType +): ThunkAction { + return (dispatch) => + axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( + (res) => { + const courseData = res.data; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(gradeAddCourse(formattedCourse)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeData( + classData: FormattedCourseType[] +): ThunkAction { + const promises = classData.map((course) => { + const { sections } = course; + const url = `/api/grades/sections/${sections.join('&')}/`; + return axios.get(url); + }); + return (dispatch) => + axios.all(promises).then( + (data) => { + let gradesData = data.map((res, i) => { + let gradesData = res.data; + gradesData['id'] = classData[i].id; + gradesData['instructor'] = + classData[i].instructor === 'all' ? 'All Instructors' : classData[i].instructor; + gradesData['semester'] = + classData[i].semester === 'all' ? 'All Semesters' : classData[i].semester; + gradesData['colorId'] = classData[i].colorId; + return gradesData; + }); + dispatch(updateGradeData(gradesData)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeSelected( + updatedClass: UpdatedClassType +): ThunkAction { + const url = `/api/grades/course_grades/${updatedClass.value}/`; + return (dispatch) => + axios.get(url).then( + (res) => { + dispatch(updatedGradeSelected(res.data)); + // if (updatedClass.addSelected) { + // this.addSelected(); + // this.handleClassSelect({value: updatedClass.value, addSelected: false}); + // } + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeFromUrl( + url: string, + navigate: NavigateFunction +): ThunkAction { + const toUrlForm = (string: string) => string.replace('/', '_').toLowerCase().split(' ').join('-'); + const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); + + let courseUrls = url.split('/')[2].split('&'); + const urlData = courseUrls.map((course) => { + let courseUrl = course.split('-'); + let semester, instructor; + if (courseUrl[2] === 'all') { + semester = courseUrl[2]; + instructor = courseUrl.slice(3).join('-'); + } else if (courseUrl[4] === '_') { + semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3] + ' / ' + courseUrl[5]; + instructor = courseUrl.slice(6).join('-').replace('_', '/'); + } else { + semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; + instructor = courseUrl.slice(4).join('-').replace('_', '/'); + } + return { + colorId: courseUrl[0], + courseID: courseUrl[1], + semester: semester, + instructor: instructor + }; + }); + let promises = urlData.map(({ courseID }) => { + const u = `/api/grades/course_grades/${courseID}/`; + return axios.get(u); + }); + + let success = true; + + return (dispatch) => + axios + .all(promises) + .then((data) => { + const courses = data.map((res, i) => { + let instructor = urlData[i].instructor; + let semester = urlData[i].semester; + let sections: number[] = []; + if (instructor === 'all') { + res.data.map((item, i) => (sections[i] = item.grade_id)); + } else { + let matches = []; + if (instructor.includes('/')) { + matches = res.data.filter( + (item) => instructor === toUrlForm(item.instructor) + '-/-' + item.section_number + ); + matches.map((item, i) => (sections[i] = item.grade_id)); + instructor = matches[0].instructor + ' / ' + matches[0].section_number; + } else { + matches = res.data.filter((item) => instructor === toUrlForm(item.instructor)); + matches.map((item, i) => (sections[i] = item.grade_id)); + instructor = matches[0].instructor; + } + } + if (semester !== 'all') { + let matches = []; + if (semester.split(' ').length > 2) { + matches = res.data.filter( + (item) => + semester === + capitalize(item.semester) + ' ' + item.year + ' / ' + item.section_number + ); + } else { + matches = res.data.filter( + (item) => semester === capitalize(item.semester) + ' ' + item.year + ); + } + let allSems = matches.map((item) => item.grade_id); + sections = sections.filter((item) => allSems.includes(item)); + } + const formattedCourse = { + courseID: parseInt(urlData[i].courseID), + instructor: instructor, + semester: semester, + sections: sections + }; + + return { + ...formattedCourse, + id: hash(formattedCourse), + colorId: urlData[i].colorId + }; + }); + return courses; + }) + .catch((error) => { + success = false; + navigate('/error'); + console.log('An error occurred.', error); + }) + .then((courses) => { + if (success && courses) { + const promises = courses.map((course) => { + const u = `/api/catalog/catalog_json/course/${course.courseID}/`; + return axios.get(u); + }); + axios.all(promises).then( + (data) => { + data.map((res, i) => { + const courseData = res.data; + const course = courses[i]; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(gradeAddCourse(formattedCourse)); + }); + }, + (error) => console.log('An error occurred.', error) + ); + } + }); +} + +export function fetchEnrollContext(): ThunkAction { + return async (dispatch, getState) => { + // Avoid fetching enrollment data twice. + if (getState().enrollment.context.courses) { + return; + } + + const res = await axios.get('/api/enrollment/enrollment_json/'); + dispatch(updateEnrollContext(res.data)); + }; +} + +export function fetchEnrollClass( + course: UnformattedCourseType +): ThunkAction { + return (dispatch) => + axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( + (res) => { + const courseData = res.data; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(enrollAddCourse(formattedCourse)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollData( + classData: FormattedCourseType[] +): ThunkAction { + const promises = classData.map((course) => { + const { instructor, courseID, semester, sections } = course; + let url; + if (instructor === 'all') { + const [sem, year] = semester.split(' '); + url = `/api/enrollment/aggregate/${courseID}/${sem.toLowerCase()}/${year}/`; + } else { + url = `/api/enrollment/data/${sections[0]}/`; + } + return axios.get(url); + }); + + return (dispatch) => + axios.all(promises).then( + (data) => { + let enrollmentData: EnrollmentDataType[] = data.map((res, i) => ({ + ...res.data, + id: classData[i].id, + colorId: classData[i].colorId + })); + dispatch(updateEnrollData(enrollmentData)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollSelected( + updatedClass: UpdatedClassType +): ThunkAction { + const url = `/api/enrollment/sections/${updatedClass.value}/`; + return (dispatch) => + axios.get(url).then( + (res) => { + dispatch(updatedEnrollSelected(res.data)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollFromUrl( + url: string, + navigate: NavigateFunction +): ThunkAction { + const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); + + let courseUrls = url.split('/')[2].split('&'); + const urlData = courseUrls.map((course) => { + const courseUrl = course.split('-'); + const semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; + return { + colorId: courseUrl[0], + courseID: courseUrl[1], + semester: semester, + section: courseUrl[4] + }; + }); + let promises = urlData.map(({ courseID }) => { + let u = `/api/enrollment/sections/${courseID}/`; + return axios.get(u); + }); + + let success = true; + return (dispatch) => + axios + .all(promises) + .then((data) => { + return data.map((res, i) => { + let semester = urlData[i].semester; + let section = + urlData[i].section === 'all' ? urlData[i].section : parseInt(urlData[i].section); + let sections = [section]; + let instructor = 'all'; + let match: SectionType; + if (section === 'all') { + match = res.data.filter( + (item) => semester === capitalize(item.semester) + ' ' + item.year + )[0]; + sections = match.sections.map((item) => item.section_id); + } else { + match = res.data + .map(({ sections, ...rest }) => ({ + sections: sections.filter((item) => item.section_id === section), + ...rest + })) + .filter((item) => { + item.sections.length > 0; + })[0]; + instructor = match.sections[0].instructor + ' / ' + match.sections[0].section_number; + } + const formattedCourse = { + courseID: parseInt(urlData[i].courseID), + instructor: instructor, + semester: semester, + sections: sections + }; + return { + ...formattedCourse, + id: hash(formattedCourse), + colorId: urlData[i].colorId + }; + }); + }) + .catch((error) => { + success = false; + navigate('/error'); + console.log('An error occurred.', error); + }) + .then((courses) => { + if (success && courses) { + const promises = courses.map((course) => { + const u = `/api/catalog/catalog_json/course/${course.courseID}/`; + return axios.get(u); + }); + + axios.all(promises).then( + (data) => { + data.map((res, i) => { + const courseData = res.data; + const course = courses[i]; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + } satisfies FormattedCourseType; + dispatch(enrollAddCourse(formattedCourse)); + }); + }, + (error) => console.log('An error occurred.', error) + ); + } + }); +} diff --git a/frontend/src/redux/common/reducer.ts b/frontend/src/redux/common/reducer.ts index a9d361132..d00bd16a7 100644 --- a/frontend/src/redux/common/reducer.ts +++ b/frontend/src/redux/common/reducer.ts @@ -23,8 +23,7 @@ export function commonReducer(state = initialState, action: CommonAction): Commo banner: true }; case CLOSE_BANNER: - const bannerType = 'fa23recruitment'; - localStorage.setItem('bt-hide-banner', bannerType); + localStorage.setItem('bt-hide-banner', 'fa23recruitment'); return { ...state, banner: false @@ -35,8 +34,7 @@ export function commonReducer(state = initialState, action: CommonAction): Commo landingModal: true }; case CLOSE_LANDING_MODAL: - const modalType = 'sp22scheduler'; - localStorage.setItem('bt-hide-landing-modal', modalType); + localStorage.setItem('bt-hide-landing-modal', 'sp22scheduler'); return { ...state, landingModal: false diff --git a/frontend/src/redux/reducers/enrollment.js b/frontend/src/redux/reducers/enrollment.ts similarity index 71% rename from frontend/src/redux/reducers/enrollment.js rename to frontend/src/redux/reducers/enrollment.ts index 51f55b560..e8aee9ff2 100644 --- a/frontend/src/redux/reducers/enrollment.js +++ b/frontend/src/redux/reducers/enrollment.ts @@ -4,11 +4,13 @@ import { UPDATE_ENROLL_DATA, UPDATE_ENROLL_SELECTED, ENROLL_REMOVE_COURSE, - ENROLL_RESET + ENROLL_RESET, + EnrollAction, + EnrollmentState } from '../actionTypes'; -const initialState = { - context: {}, +const initialState: EnrollmentState = { + context: { courses: [] }, selectedCourses: [], enrollmentData: [], graphData: [], @@ -18,7 +20,7 @@ const initialState = { usedColorIds: [] }; -export default function enrollment(state = initialState, action) { +export default function enrollment(state = initialState, action: EnrollAction) { switch (action.type) { case ENROLL_RESET: { return { @@ -51,23 +53,18 @@ export default function enrollment(state = initialState, action) { case UPDATE_ENROLL_DATA: { const { enrollmentData } = action.payload; const days = [...Array(200).keys()]; - const graphData = days.map((day) => { - const ret = { - name: day - }; - for (const enrollment of enrollmentData) { + const graphData = days.map((day) => ({ + name: day, + ...enrollmentData.reduce((enrollmentTimes, enrollment) => { const validTimes = enrollment.data.filter((time) => time.day >= 0); - const enrollmentTimes = {}; - for (const validTime of validTimes) { - enrollmentTimes[validTime.day] = validTime; - } - - if (day in enrollmentTimes) { - ret[enrollment.id] = (enrollmentTimes[day].enrolled_percent * 100).toFixed(1); - } - } - return ret; - }); + validTimes.forEach((validTime) => { + if (validTime.day === day) { + enrollmentTimes[enrollment.id] = (validTime.enrolled_percent * 100).toFixed(1); + } + }); + return enrollmentTimes; + }, {} as Record) + })); return { ...state, enrollmentData, @@ -96,11 +93,3 @@ export default function enrollment(state = initialState, action) { return state; } } -// -// capitalize(str) { -// return str.charAt(0).toUpperCase() + str.slice(1); -// } -// -// getSectionSemester(section) { -// return `${this.capitalize(section.semester)} ${section.year}`; -// } diff --git a/frontend/src/redux/reducers/grade.js b/frontend/src/redux/reducers/grade.ts similarity index 61% rename from frontend/src/redux/reducers/grade.js rename to frontend/src/redux/reducers/grade.ts index 2085274d7..6d33b359d 100644 --- a/frontend/src/redux/reducers/grade.js +++ b/frontend/src/redux/reducers/grade.ts @@ -1,15 +1,18 @@ +import vars from '../../utils/variables'; import { - UPDATE_GRADE_CONTEXT, + GRADE, GRADE_ADD_COURSE, - UPDATE_GRADE_DATA, - UPDATE_GRADE_SELECTED, GRADE_REMOVE_COURSE, - GRADE_RESET + GRADE_RESET, + GradeAction, + GradeState, + UPDATE_GRADE_CONTEXT, + UPDATE_GRADE_DATA, + UPDATE_GRADE_SELECTED } from '../actionTypes'; -import vars from '../../utils/variables'; -const initialState = { - context: {}, +const initialState: GradeState = { + context: { courses: [] }, selectedCourses: [], gradesData: [], graphData: [], @@ -19,10 +22,13 @@ const initialState = { usedColorIds: [] }; -export default function grade(state = initialState, action) { +export default function grade(state = initialState, action: GradeAction): GradeState { switch (action.type) { case GRADE_RESET: { - return initialState; + return { + ...initialState, + context: state.context + }; } case UPDATE_GRADE_CONTEXT: { const { data } = action.payload; @@ -37,8 +43,8 @@ export default function grade(state = initialState, action) { } case GRADE_REMOVE_COURSE: { const { id, color } = action.payload; - let updatedCourses = state.selectedCourses.filter((classInfo) => classInfo.id !== id); - let updatedColors = state.usedColorIds.filter((c) => c !== color); + const updatedCourses = state.selectedCourses.filter((classInfo) => classInfo.id !== id); + const updatedColors = state.usedColorIds.filter((c) => c !== color); return Object.assign({}, state, { selectedCourses: updatedCourses, usedColorIds: updatedColors @@ -46,15 +52,14 @@ export default function grade(state = initialState, action) { } case UPDATE_GRADE_DATA: { const { gradesData } = action.payload; - const graphData = vars.possibleGrades.map((letterGrade) => { - const ret = { - name: letterGrade - }; - for (const grade of gradesData) { - ret[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; - } - return ret; - }); + const graphData = vars.possibleGrades.map((letterGrade) => ({ + name: letterGrade, + ...gradesData.reduce((grades, grade) => { + grades[grade.id] = + (grade[letterGrade as keyof typeof GRADE].numerator / grade.denominator) * 100; + return grades; + }, {} as Record) + })); return { ...state, gradesData, From 10d92dbb305e5fdab5b78f4ae42af8f5e470cad1 Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 01:45:08 -0800 Subject: [PATCH 09/27] Redux: reorganized --- frontend/src/redux/actionTypes.ts | 206 -------- frontend/src/redux/actions.ts | 496 ------------------ frontend/src/redux/enrollment/actions.ts | 245 +++++++++ .../enrollment.ts => enrollment/reducers.ts} | 2 +- frontend/src/redux/enrollment/types.ts | 88 ++++ frontend/src/redux/grades/actions.ts | 263 ++++++++++ .../{reducers/grade.ts => grades/reducers.ts} | 10 +- frontend/src/redux/grades/types.ts | 89 ++++ frontend/src/redux/store.ts | 8 +- frontend/src/redux/types.ts | 35 ++ 10 files changed, 729 insertions(+), 713 deletions(-) delete mode 100644 frontend/src/redux/actionTypes.ts delete mode 100644 frontend/src/redux/actions.ts create mode 100644 frontend/src/redux/enrollment/actions.ts rename frontend/src/redux/{reducers/enrollment.ts => enrollment/reducers.ts} (98%) create mode 100644 frontend/src/redux/enrollment/types.ts create mode 100644 frontend/src/redux/grades/actions.ts rename frontend/src/redux/{reducers/grade.ts => grades/reducers.ts} (87%) create mode 100644 frontend/src/redux/grades/types.ts create mode 100644 frontend/src/redux/types.ts diff --git a/frontend/src/redux/actionTypes.ts b/frontend/src/redux/actionTypes.ts deleted file mode 100644 index 948964ae6..000000000 --- a/frontend/src/redux/actionTypes.ts +++ /dev/null @@ -1,206 +0,0 @@ -export const UPDATE_GRADE_CONTEXT = 'UPDATE_GRADE_CONTEXT'; -export const GRADE_ADD_COURSE = 'GRADE_ADD_COURSE'; -export const GRADE_REMOVE_COURSE = 'GRADE_REMOVE_COURSE'; -export const UPDATE_GRADE_DATA = 'UPDATE_GRADE_DATA'; -export const UPDATE_GRADE_SELECTED = 'UPDATE_GRADE_SELECTED'; -export const GRADE_RESET = 'GRADE_RESET'; - -export const UPDATE_ENROLL_CONTEXT = 'UPDATE_ENROLL_CONTEXT'; -export const ENROLL_ADD_COURSE = 'ENROLL_ADD_COURSE'; -export const ENROLL_REMOVE_COURSE = 'ENROLL_REMOVE_COURSE'; -export const UPDATE_ENROLL_DATA = 'UPDATE_ENROLL_DATA'; -export const UPDATE_ENROLL_SELECTED = 'UPDATE_ENROLL_SELECTED'; -export const ENROLL_RESET = 'ENROLL_RESET'; - -export type CourseSnapshotType = { - abbreviation: string; - course_number: string; - id: number; -}; - -export type UnformattedCourseType = { - colorId: string; - courseID: number; - id: string; - instructor: string; - sections: (number | string)[]; - semester: string; -}; - -export type FormattedCourseDataType = { course: string; title: string }; - -export type FormattedCourseType = UnformattedCourseType & FormattedCourseDataType; - -export type BaseDataType = { - colorId: string; - course_id: number; - id: string; - instructor: string; - subtitle: string; - title: string; -}; - -export type GradesDataType = BaseDataType & { - course_gpa: number; - course_letter: string; - denominator: number; - section_gpa: number; - section_letter: string; - semester: string; -} & { - [grade in GRADE]: { - percent: number; - numerator: number; - percentile_high: number; - percentile_low: number; - }; -}; - -export type EnrollmentDataType = BaseDataType & { - data: { - date: string; - day: number; - enrolled: number; - enrolled_max: number; - enrolled_percent: number; - waitlisted: number; - waitlisted_max: number; - waitlisted_percent: number; - }[]; - enrolled_max: number; - enrolled_percent_max: number; - enrolled_scale_max: number; - section_id: number; - section_name: string; - telebears: { - adj_start_date: string; - adj_start_day: number; - phase1_end_date: number; - phase1_start_date: string; - phase1_start_day: number; - phase2_end_date: number; - phase2_start_date: string; - phase2_start_day: number; - }; - title: string; - waitlisted_max: number; - waitlisted_percent_max: number; - waitlisted_scale_max: number; -}; - -export enum GRADE { - 'A+' = 'A+', - 'A' = 'A', - 'A-' = 'A-', - 'B+' = 'B+', - 'B' = 'B', - 'B-' = 'B-', - 'C+' = 'C+', - 'C' = 'C', - 'C-' = 'C-', - 'D' = 'D', - 'F' = 'F', - 'P' = 'P', - 'NP' = 'NP' -} - -export type GradeSelectedType = { - grade_id: number; - instructor: string; - section_number: string; - semester: string; - year: string; -}; - -export type SectionType = { - sections: { instructor: string; section_id: number; section_number: string }[]; - semester: string; - year: string; -}; - -export type UpdatedClassType = { - value: number; - label: string; - course: CourseSnapshotType; -}; - -export type GradeAction = - | { type: typeof UPDATE_GRADE_CONTEXT; payload: { data: { courses: CourseSnapshotType[] } } } - | { type: typeof GRADE_RESET } - | { - type: typeof GRADE_ADD_COURSE; - payload: { - formattedCourse: FormattedCourseType; - }; - } - | { - type: typeof GRADE_REMOVE_COURSE; - payload: { - id: string; - color: string; - }; - } - | { - type: typeof UPDATE_GRADE_DATA; - payload: { - gradesData: GradesDataType[]; - }; - } - | { - type: typeof UPDATE_GRADE_SELECTED; - payload: { - data: GradeSelectedType[]; - }; - }; - -export type EnrollAction = - | { - type: typeof UPDATE_ENROLL_CONTEXT; - payload: { data: { courses: CourseSnapshotType[] } }; - } - | { - type: typeof UPDATE_ENROLL_DATA; - payload: { enrollmentData: EnrollmentDataType[] }; - } - | { - type: typeof UPDATE_ENROLL_SELECTED; - payload: { - sections: SectionType[]; - }; - } - | { - type: typeof ENROLL_RESET; - } - | { - type: typeof ENROLL_ADD_COURSE; - payload: { - formattedCourse: FormattedCourseType; - }; - } - | { - type: typeof ENROLL_REMOVE_COURSE; - payload: { - id: string; - color: string; - }; - }; - -type BaseState = { - context: { courses: CourseSnapshotType[] }; - selectedCourses: FormattedCourseType[]; - selectPrimary: string; - selectSecondary: string | { value: string; label: string }; - usedColorIds: string[]; -}; - -export type GradeState = BaseState & { - gradesData: GradesDataType[]; - sections: GradeSelectedType[]; - graphData: { name: string }[]; -}; - -export type EnrollmentState = BaseState & { - enrollmentData: EnrollmentDataType[]; - sections: SectionType[]; - graphData: { name: number }[]; -}; diff --git a/frontend/src/redux/actions.ts b/frontend/src/redux/actions.ts deleted file mode 100644 index 1b6f42851..000000000 --- a/frontend/src/redux/actions.ts +++ /dev/null @@ -1,496 +0,0 @@ -/* eslint-disable */ -import axios, { AxiosResponse } from 'axios'; -import hash from 'object-hash'; -import { NavigateFunction } from 'react-router-dom'; -import { Action } from 'redux'; -import { ThunkAction } from 'redux-thunk'; -import { - CourseSnapshotType, - ENROLL_ADD_COURSE, - ENROLL_REMOVE_COURSE, - ENROLL_RESET, - EnrollmentDataType, - FormattedCourseDataType, - FormattedCourseType, - GRADE_ADD_COURSE, - GRADE_REMOVE_COURSE, - GRADE_RESET, - GradeSelectedType, - GradesDataType, - SectionType, - UPDATE_ENROLL_CONTEXT, - UPDATE_ENROLL_DATA, - UPDATE_ENROLL_SELECTED, - UPDATE_GRADE_CONTEXT, - UPDATE_GRADE_DATA, - UPDATE_GRADE_SELECTED, - UnformattedCourseType, - UpdatedClassType -} from './actionTypes'; -import { ReduxState } from './store'; - -axios.defaults.baseURL = import.meta.env.PROD - ? axios.defaults.baseURL - : 'https://staging.berkeleytime.com'; - -// update grade list -const updateGradeContext = (data: { courses: CourseSnapshotType[] }) => ({ - type: UPDATE_GRADE_CONTEXT, - payload: { - data - } -}); - -export const gradeReset = () => ({ - type: GRADE_RESET -}); - -// add displayed course to the grade page -const gradeAddCourse = (formattedCourse: FormattedCourseType) => ({ - type: GRADE_ADD_COURSE, - payload: { - formattedCourse - } -}); - -export const gradeRemoveCourse = (id: string, color: string) => ({ - type: GRADE_REMOVE_COURSE, - payload: { - id, - color - } -}); - -const updateGradeData = (gradesData: GradesDataType[]) => ({ - type: UPDATE_GRADE_DATA, - payload: { - gradesData - } -}); - -const updatedGradeSelected = (data: GradeSelectedType[]) => ({ - type: UPDATE_GRADE_SELECTED, - payload: { - data - } -}); - -// update enroll list -const updateEnrollContext = (data: { courses: CourseSnapshotType[] }) => ({ - type: UPDATE_ENROLL_CONTEXT, - payload: { - data - } -}); - -export const enrollReset = () => ({ - type: ENROLL_RESET -}); - -// add displayed course to the enroll page -const enrollAddCourse = (formattedCourse: FormattedCourseType) => ({ - type: ENROLL_ADD_COURSE, - payload: { - formattedCourse - } -}); - -export const enrollRemoveCourse = (id: string, color: string) => ({ - type: ENROLL_REMOVE_COURSE, - payload: { - id, - color - } -}); - -export const updateEnrollData = (enrollmentData: EnrollmentDataType[]) => ({ - type: UPDATE_ENROLL_DATA, - payload: { - enrollmentData - } -}); - -const updatedEnrollSelected = (sections: SectionType[]) => ({ - type: UPDATE_ENROLL_SELECTED, - payload: { - sections - } -}); - -export function fetchGradeContext(): ThunkAction { - return (dispatch) => - axios.get('/api/grades/grades_json/').then( - (res) => { - dispatch(updateGradeContext(res.data)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeClass( - course: UnformattedCourseType -): ThunkAction { - return (dispatch) => - axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( - (res) => { - const courseData = res.data; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(gradeAddCourse(formattedCourse)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeData( - classData: FormattedCourseType[] -): ThunkAction { - const promises = classData.map((course) => { - const { sections } = course; - const url = `/api/grades/sections/${sections.join('&')}/`; - return axios.get(url); - }); - return (dispatch) => - axios.all(promises).then( - (data) => { - let gradesData = data.map((res, i) => { - let gradesData = res.data; - gradesData['id'] = classData[i].id; - gradesData['instructor'] = - classData[i].instructor === 'all' ? 'All Instructors' : classData[i].instructor; - gradesData['semester'] = - classData[i].semester === 'all' ? 'All Semesters' : classData[i].semester; - gradesData['colorId'] = classData[i].colorId; - return gradesData; - }); - dispatch(updateGradeData(gradesData)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeSelected( - updatedClass: UpdatedClassType -): ThunkAction { - const url = `/api/grades/course_grades/${updatedClass.value}/`; - return (dispatch) => - axios.get(url).then( - (res) => { - dispatch(updatedGradeSelected(res.data)); - // if (updatedClass.addSelected) { - // this.addSelected(); - // this.handleClassSelect({value: updatedClass.value, addSelected: false}); - // } - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchGradeFromUrl( - url: string, - navigate: NavigateFunction -): ThunkAction { - const toUrlForm = (string: string) => string.replace('/', '_').toLowerCase().split(' ').join('-'); - const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); - - let courseUrls = url.split('/')[2].split('&'); - const urlData = courseUrls.map((course) => { - let courseUrl = course.split('-'); - let semester, instructor; - if (courseUrl[2] === 'all') { - semester = courseUrl[2]; - instructor = courseUrl.slice(3).join('-'); - } else if (courseUrl[4] === '_') { - semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3] + ' / ' + courseUrl[5]; - instructor = courseUrl.slice(6).join('-').replace('_', '/'); - } else { - semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; - instructor = courseUrl.slice(4).join('-').replace('_', '/'); - } - return { - colorId: courseUrl[0], - courseID: courseUrl[1], - semester: semester, - instructor: instructor - }; - }); - let promises = urlData.map(({ courseID }) => { - const u = `/api/grades/course_grades/${courseID}/`; - return axios.get(u); - }); - - let success = true; - - return (dispatch) => - axios - .all(promises) - .then((data) => { - const courses = data.map((res, i) => { - let instructor = urlData[i].instructor; - let semester = urlData[i].semester; - let sections: number[] = []; - if (instructor === 'all') { - res.data.map((item, i) => (sections[i] = item.grade_id)); - } else { - let matches = []; - if (instructor.includes('/')) { - matches = res.data.filter( - (item) => instructor === toUrlForm(item.instructor) + '-/-' + item.section_number - ); - matches.map((item, i) => (sections[i] = item.grade_id)); - instructor = matches[0].instructor + ' / ' + matches[0].section_number; - } else { - matches = res.data.filter((item) => instructor === toUrlForm(item.instructor)); - matches.map((item, i) => (sections[i] = item.grade_id)); - instructor = matches[0].instructor; - } - } - if (semester !== 'all') { - let matches = []; - if (semester.split(' ').length > 2) { - matches = res.data.filter( - (item) => - semester === - capitalize(item.semester) + ' ' + item.year + ' / ' + item.section_number - ); - } else { - matches = res.data.filter( - (item) => semester === capitalize(item.semester) + ' ' + item.year - ); - } - let allSems = matches.map((item) => item.grade_id); - sections = sections.filter((item) => allSems.includes(item)); - } - const formattedCourse = { - courseID: parseInt(urlData[i].courseID), - instructor: instructor, - semester: semester, - sections: sections - }; - - return { - ...formattedCourse, - id: hash(formattedCourse), - colorId: urlData[i].colorId - }; - }); - return courses; - }) - .catch((error) => { - success = false; - navigate('/error'); - console.log('An error occurred.', error); - }) - .then((courses) => { - if (success && courses) { - const promises = courses.map((course) => { - const u = `/api/catalog/catalog_json/course/${course.courseID}/`; - return axios.get(u); - }); - axios.all(promises).then( - (data) => { - data.map((res, i) => { - const courseData = res.data; - const course = courses[i]; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(gradeAddCourse(formattedCourse)); - }); - }, - (error) => console.log('An error occurred.', error) - ); - } - }); -} - -export function fetchEnrollContext(): ThunkAction { - return async (dispatch, getState) => { - // Avoid fetching enrollment data twice. - if (getState().enrollment.context.courses) { - return; - } - - const res = await axios.get('/api/enrollment/enrollment_json/'); - dispatch(updateEnrollContext(res.data)); - }; -} - -export function fetchEnrollClass( - course: UnformattedCourseType -): ThunkAction { - return (dispatch) => - axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( - (res) => { - const courseData = res.data; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - }; - dispatch(enrollAddCourse(formattedCourse)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollData( - classData: FormattedCourseType[] -): ThunkAction { - const promises = classData.map((course) => { - const { instructor, courseID, semester, sections } = course; - let url; - if (instructor === 'all') { - const [sem, year] = semester.split(' '); - url = `/api/enrollment/aggregate/${courseID}/${sem.toLowerCase()}/${year}/`; - } else { - url = `/api/enrollment/data/${sections[0]}/`; - } - return axios.get(url); - }); - - return (dispatch) => - axios.all(promises).then( - (data) => { - let enrollmentData: EnrollmentDataType[] = data.map((res, i) => ({ - ...res.data, - id: classData[i].id, - colorId: classData[i].colorId - })); - dispatch(updateEnrollData(enrollmentData)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollSelected( - updatedClass: UpdatedClassType -): ThunkAction { - const url = `/api/enrollment/sections/${updatedClass.value}/`; - return (dispatch) => - axios.get(url).then( - (res) => { - dispatch(updatedEnrollSelected(res.data)); - }, - (error) => console.log('An error occurred.', error) - ); -} - -export function fetchEnrollFromUrl( - url: string, - navigate: NavigateFunction -): ThunkAction { - const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); - - let courseUrls = url.split('/')[2].split('&'); - const urlData = courseUrls.map((course) => { - const courseUrl = course.split('-'); - const semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; - return { - colorId: courseUrl[0], - courseID: courseUrl[1], - semester: semester, - section: courseUrl[4] - }; - }); - let promises = urlData.map(({ courseID }) => { - let u = `/api/enrollment/sections/${courseID}/`; - return axios.get(u); - }); - - let success = true; - return (dispatch) => - axios - .all(promises) - .then((data) => { - return data.map((res, i) => { - let semester = urlData[i].semester; - let section = - urlData[i].section === 'all' ? urlData[i].section : parseInt(urlData[i].section); - let sections = [section]; - let instructor = 'all'; - let match: SectionType; - if (section === 'all') { - match = res.data.filter( - (item) => semester === capitalize(item.semester) + ' ' + item.year - )[0]; - sections = match.sections.map((item) => item.section_id); - } else { - match = res.data - .map(({ sections, ...rest }) => ({ - sections: sections.filter((item) => item.section_id === section), - ...rest - })) - .filter((item) => { - item.sections.length > 0; - })[0]; - instructor = match.sections[0].instructor + ' / ' + match.sections[0].section_number; - } - const formattedCourse = { - courseID: parseInt(urlData[i].courseID), - instructor: instructor, - semester: semester, - sections: sections - }; - return { - ...formattedCourse, - id: hash(formattedCourse), - colorId: urlData[i].colorId - }; - }); - }) - .catch((error) => { - success = false; - navigate('/error'); - console.log('An error occurred.', error); - }) - .then((courses) => { - if (success && courses) { - const promises = courses.map((course) => { - const u = `/api/catalog/catalog_json/course/${course.courseID}/`; - return axios.get(u); - }); - - axios.all(promises).then( - (data) => { - data.map((res, i) => { - const courseData = res.data; - const course = courses[i]; - const formattedCourse = { - id: course.id, - course: courseData.course, - title: courseData.title, - semester: course.semester, - instructor: course.instructor, - courseID: course.courseID, - sections: course.sections, - colorId: course.colorId - } satisfies FormattedCourseType; - dispatch(enrollAddCourse(formattedCourse)); - }); - }, - (error) => console.log('An error occurred.', error) - ); - } - }); -} diff --git a/frontend/src/redux/enrollment/actions.ts b/frontend/src/redux/enrollment/actions.ts new file mode 100644 index 000000000..41a1d897b --- /dev/null +++ b/frontend/src/redux/enrollment/actions.ts @@ -0,0 +1,245 @@ +import axios from 'axios'; +import hash from 'object-hash'; +import { NavigateFunction } from 'react-router-dom'; +import { Action } from 'redux'; +import { ThunkAction } from 'redux-thunk'; +import { + CourseSnapshotType, + FormattedCourseDataType, + FormattedCourseType, + UnformattedCourseType +} from 'redux/types'; +import { SectionType } from './types'; +import { ReduxState } from '../store'; +import { + ENROLL_ADD_COURSE, + ENROLL_REMOVE_COURSE, + ENROLL_RESET, + EnrollmentDataType, + UPDATE_ENROLL_CONTEXT, + UPDATE_ENROLL_DATA, + UPDATE_ENROLL_SELECTED +} from './types'; +import { UpdatedClassType } from 'redux/grades/types'; + +axios.defaults.baseURL = import.meta.env.PROD + ? axios.defaults.baseURL + : 'https://staging.berkeleytime.com'; + +// update enroll list +const updateEnrollContext = (data: { courses: CourseSnapshotType[] }) => ({ + type: UPDATE_ENROLL_CONTEXT, + payload: { + data + } +}); + +export const enrollReset = () => ({ + type: ENROLL_RESET +}); + +// add displayed course to the enroll page +const enrollAddCourse = (formattedCourse: FormattedCourseType) => ({ + type: ENROLL_ADD_COURSE, + payload: { + formattedCourse + } +}); + +export const enrollRemoveCourse = (id: string, color: string) => ({ + type: ENROLL_REMOVE_COURSE, + payload: { + id, + color + } +}); + +export const updateEnrollData = (enrollmentData: EnrollmentDataType[]) => ({ + type: UPDATE_ENROLL_DATA, + payload: { + enrollmentData + } +}); + +const updatedEnrollSelected = (sections: SectionType[]) => ({ + type: UPDATE_ENROLL_SELECTED, + payload: { + sections + } +}); + +export function fetchEnrollContext(): ThunkAction { + return async (dispatch, getState) => { + // Avoid fetching enrollment data twice. + if (getState().enrollment.context.courses) { + return; + } + + const res = await axios.get('/api/enrollment/enrollment_json/'); + dispatch(updateEnrollContext(res.data)); + }; +} + +export function fetchEnrollClass( + course: UnformattedCourseType +): ThunkAction { + return (dispatch) => + axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( + (res) => { + const courseData = res.data; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(enrollAddCourse(formattedCourse)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollData( + classData: FormattedCourseType[] +): ThunkAction { + const promises = classData.map((course) => { + const { instructor, courseID, semester, sections } = course; + let url; + if (instructor === 'all') { + const [sem, year] = semester.split(' '); + url = `/api/enrollment/aggregate/${courseID}/${sem.toLowerCase()}/${year}/`; + } else { + url = `/api/enrollment/data/${sections[0]}/`; + } + return axios.get(url); + }); + + return (dispatch) => + axios.all(promises).then( + (data) => { + const enrollmentData: EnrollmentDataType[] = data.map((res, i) => ({ + ...res.data, + id: classData[i].id, + colorId: classData[i].colorId + })); + dispatch(updateEnrollData(enrollmentData)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollSelected( + updatedClass: UpdatedClassType +): ThunkAction { + const url = `/api/enrollment/sections/${updatedClass.value}/`; + return (dispatch) => + axios.get(url).then( + (res) => { + dispatch(updatedEnrollSelected(res.data)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchEnrollFromUrl( + url: string, + navigate: NavigateFunction +): ThunkAction { + const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); + + const courseUrls = url.split('/')[2].split('&'); + const urlData = courseUrls.map((course) => { + const courseUrl = course.split('-'); + const semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; + return { + colorId: courseUrl[0], + courseID: courseUrl[1], + semester: semester, + section: courseUrl[4] + }; + }); + const promises = urlData.map(({ courseID }) => { + const u = `/api/enrollment/sections/${courseID}/`; + return axios.get(u); + }); + + let success = true; + return (dispatch) => + axios + .all(promises) + .then((data) => { + return data.map((res, i) => { + const semester = urlData[i].semester; + const section = + urlData[i].section === 'all' ? urlData[i].section : parseInt(urlData[i].section); + let sections = [section]; + let instructor = 'all'; + let match: SectionType; + if (section === 'all') { + match = res.data.filter( + (item) => semester === capitalize(item.semester) + ' ' + item.year + )[0]; + sections = match.sections.map((item) => item.section_id); + } else { + match = res.data + .map(({ sections, ...rest }) => ({ + sections: sections.filter((item) => item.section_id === section), + ...rest + })) + .filter((item) => { + item.sections.length > 0; + })[0]; + instructor = match.sections[0].instructor + ' / ' + match.sections[0].section_number; + } + const formattedCourse = { + courseID: parseInt(urlData[i].courseID), + instructor: instructor, + semester: semester, + sections: sections + }; + return { + ...formattedCourse, + id: hash(formattedCourse), + colorId: urlData[i].colorId + }; + }); + }) + .catch((error) => { + success = false; + navigate('/error'); + console.log('An error occurred.', error); + }) + .then((courses) => { + if (success && courses) { + const promises = courses.map((course) => { + const u = `/api/catalog/catalog_json/course/${course.courseID}/`; + return axios.get(u); + }); + + axios.all(promises).then( + (data) => { + data.map((res, i) => { + const courseData = res.data; + const course = courses[i]; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + } satisfies FormattedCourseType; + dispatch(enrollAddCourse(formattedCourse)); + }); + }, + (error) => console.log('An error occurred.', error) + ); + } + }); +} diff --git a/frontend/src/redux/reducers/enrollment.ts b/frontend/src/redux/enrollment/reducers.ts similarity index 98% rename from frontend/src/redux/reducers/enrollment.ts rename to frontend/src/redux/enrollment/reducers.ts index e8aee9ff2..8fbdb0749 100644 --- a/frontend/src/redux/reducers/enrollment.ts +++ b/frontend/src/redux/enrollment/reducers.ts @@ -7,7 +7,7 @@ import { ENROLL_RESET, EnrollAction, EnrollmentState -} from '../actionTypes'; +} from './types'; const initialState: EnrollmentState = { context: { courses: [] }, diff --git a/frontend/src/redux/enrollment/types.ts b/frontend/src/redux/enrollment/types.ts new file mode 100644 index 000000000..33efa9f44 --- /dev/null +++ b/frontend/src/redux/enrollment/types.ts @@ -0,0 +1,88 @@ +import { BaseDataType, BaseState, CourseSnapshotType, FormattedCourseType } from 'redux/types'; + +export const UPDATE_ENROLL_CONTEXT = 'UPDATE_ENROLL_CONTEXT'; +export const ENROLL_ADD_COURSE = 'ENROLL_ADD_COURSE'; +export const ENROLL_REMOVE_COURSE = 'ENROLL_REMOVE_COURSE'; +export const UPDATE_ENROLL_DATA = 'UPDATE_ENROLL_DATA'; +export const UPDATE_ENROLL_SELECTED = 'UPDATE_ENROLL_SELECTED'; +export const ENROLL_RESET = 'ENROLL_RESET'; + +export type EnrollAction = + | { + type: typeof UPDATE_ENROLL_CONTEXT; + payload: { data: { courses: CourseSnapshotType[] } }; + } + | { + type: typeof UPDATE_ENROLL_DATA; + payload: { enrollmentData: EnrollmentDataType[] }; + } + | { + type: typeof UPDATE_ENROLL_SELECTED; + payload: { + sections: SectionType[]; + }; + } + | { + type: typeof ENROLL_RESET; + } + | { + type: typeof ENROLL_ADD_COURSE; + payload: { + formattedCourse: FormattedCourseType; + }; + } + | { + type: typeof ENROLL_REMOVE_COURSE; + payload: { + id: string; + color: string; + }; + }; + +export type EnrollmentState = BaseState & { + enrollmentData: EnrollmentDataType[]; + sections: SectionType[]; + graphData: { name: number }[]; +}; + +export type EnrollmentStatusType = { + date: string; + day: number; + enrolled: number; + enrolled_max: number; + enrolled_percent: number; + waitlisted: number; + waitlisted_max: number; + waitlisted_percent: number; +}; + +export type TelebearsType = { + adj_start_date: string; + adj_start_day: number; + phase1_end_date: number; + phase1_start_date: string; + phase1_start_day: number; + phase2_end_date: number; + phase2_start_date: string; + phase2_start_day: number; +}; + +export type EnrollmentDataType = BaseDataType & { + data: EnrollmentStatusType[]; + enrolled_max: number; + enrolled_percent_max: number; + enrolled_scale_max: number; + section_id: number; + section_name: string; + telebears: TelebearsType; + title: string; + waitlisted_max: number; + waitlisted_percent_max: number; + waitlisted_scale_max: number; +}; + +export type SectionType = { + sections: { instructor: string; section_id: number; section_number: string }[]; + semester: string; + year: string; +}; diff --git a/frontend/src/redux/grades/actions.ts b/frontend/src/redux/grades/actions.ts new file mode 100644 index 000000000..f5f8091bf --- /dev/null +++ b/frontend/src/redux/grades/actions.ts @@ -0,0 +1,263 @@ +import axios from 'axios'; +import hash from 'object-hash'; +import { NavigateFunction } from 'react-router-dom'; +import { Action } from 'redux'; +import { ThunkAction } from 'redux-thunk'; +import { ReduxState } from 'redux/store'; +import { + CourseSnapshotType, + FormattedCourseDataType, + FormattedCourseType, + UnformattedCourseType +} from 'redux/types'; +import { + GRADE_ADD_COURSE, + GRADE_REMOVE_COURSE, + GRADE_RESET, + GradeSelectedType, + GradesDataType, + UPDATE_GRADE_CONTEXT, + UPDATE_GRADE_DATA, + UPDATE_GRADE_SELECTED, + UpdatedClassType +} from './types'; + +axios.defaults.baseURL = import.meta.env.PROD + ? axios.defaults.baseURL + : 'https://staging.berkeleytime.com'; + +const updateGradeContext = (data: { courses: CourseSnapshotType[] }) => ({ + type: UPDATE_GRADE_CONTEXT, + payload: { + data + } +}); + +export const gradeReset = () => ({ + type: GRADE_RESET +}); + +const gradeAddCourse = (formattedCourse: FormattedCourseType) => ({ + type: GRADE_ADD_COURSE, + payload: { + formattedCourse + } +}); + +export const gradeRemoveCourse = (id: string, color: string) => ({ + type: GRADE_REMOVE_COURSE, + payload: { + id, + color + } +}); + +const updateGradeData = (gradesData: GradesDataType[]) => ({ + type: UPDATE_GRADE_DATA, + payload: { + gradesData + } +}); + +const updatedGradeSelected = (data: GradeSelectedType[]) => ({ + type: UPDATE_GRADE_SELECTED, + payload: { + data + } +}); + +export function fetchGradeContext(): ThunkAction { + return (dispatch) => + axios.get('/api/grades/grades_json/').then( + (res) => { + dispatch(updateGradeContext(res.data)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeClass( + course: UnformattedCourseType +): ThunkAction { + return (dispatch) => + axios.get(`/api/catalog/catalog_json/course/${course.courseID}/`).then( + (res) => { + const courseData = res.data; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(gradeAddCourse(formattedCourse)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeData( + classData: FormattedCourseType[] +): ThunkAction { + const promises = classData.map((course) => { + const { sections } = course; + const url = `/api/grades/sections/${sections.join('&')}/`; + return axios.get(url); + }); + return (dispatch) => + axios.all(promises).then( + (data) => { + const gradesData = data.map((res, i) => ({ + ...res.data, + id: classData[i].id, + instructor: + classData[i].instructor === 'all' ? 'All Instructors' : classData[i].instructor, + semester: classData[i].semester === 'all' ? 'All Semesters' : classData[i].semester, + colorId: classData[i].colorId + })); + dispatch(updateGradeData(gradesData)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeSelected( + updatedClass: UpdatedClassType +): ThunkAction { + const url = `/api/grades/course_grades/${updatedClass.value}/`; + return (dispatch) => + axios.get(url).then( + (res) => { + dispatch(updatedGradeSelected(res.data)); + }, + (error) => console.log('An error occurred.', error) + ); +} + +export function fetchGradeFromUrl( + url: string, + navigate: NavigateFunction +): ThunkAction { + const toUrlForm = (string: string) => string.replace('/', '_').toLowerCase().split(' ').join('-'); + const capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1); + + const courseUrls = url.split('/')[2].split('&'); + const urlData = courseUrls.map((course) => { + const courseUrl = course.split('-'); + let semester, instructor; + if (courseUrl[2] === 'all') { + semester = courseUrl[2]; + instructor = courseUrl.slice(3).join('-'); + } else if (courseUrl[4] === '_') { + semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3] + ' / ' + courseUrl[5]; + instructor = courseUrl.slice(6).join('-').replace('_', '/'); + } else { + semester = capitalize(courseUrl[2]) + ' ' + courseUrl[3]; + instructor = courseUrl.slice(4).join('-').replace('_', '/'); + } + return { + colorId: courseUrl[0], + courseID: courseUrl[1], + semester: semester, + instructor: instructor + }; + }); + const promises = urlData.map(({ courseID }) => { + const u = `/api/grades/course_grades/${courseID}/`; + return axios.get(u); + }); + + let success = true; + + return (dispatch) => + axios + .all(promises) + .then((data) => { + const courses = data.map((res, i) => { + let instructor = urlData[i].instructor; + const semester = urlData[i].semester; + let sections: number[] = []; + if (instructor === 'all') { + res.data.map((item, i) => (sections[i] = item.grade_id)); + } else { + let matches = []; + if (instructor.includes('/')) { + matches = res.data.filter( + (item) => instructor === toUrlForm(item.instructor) + '-/-' + item.section_number + ); + matches.map((item, i) => (sections[i] = item.grade_id)); + instructor = matches[0].instructor + ' / ' + matches[0].section_number; + } else { + matches = res.data.filter((item) => instructor === toUrlForm(item.instructor)); + matches.map((item, i) => (sections[i] = item.grade_id)); + instructor = matches[0].instructor; + } + } + if (semester !== 'all') { + let matches = []; + if (semester.split(' ').length > 2) { + matches = res.data.filter( + (item) => + semester === + capitalize(item.semester) + ' ' + item.year + ' / ' + item.section_number + ); + } else { + matches = res.data.filter( + (item) => semester === capitalize(item.semester) + ' ' + item.year + ); + } + const allSems = matches.map((item) => item.grade_id); + sections = sections.filter((item) => allSems.includes(item)); + } + const formattedCourse = { + courseID: parseInt(urlData[i].courseID), + instructor: instructor, + semester: semester, + sections: sections + }; + + return { + ...formattedCourse, + id: hash(formattedCourse), + colorId: urlData[i].colorId + }; + }); + return courses; + }) + .catch((error) => { + success = false; + navigate('/error'); + console.log('An error occurred.', error); + }) + .then((courses) => { + if (success && courses) { + const promises = courses.map((course) => { + const u = `/api/catalog/catalog_json/course/${course.courseID}/`; + return axios.get(u); + }); + axios.all(promises).then( + (data) => { + data.map((res, i) => { + const courseData = res.data; + const course = courses[i]; + const formattedCourse = { + id: course.id, + course: courseData.course, + title: courseData.title, + semester: course.semester, + instructor: course.instructor, + courseID: course.courseID, + sections: course.sections, + colorId: course.colorId + }; + dispatch(gradeAddCourse(formattedCourse)); + }); + }, + (error) => console.log('An error occurred.', error) + ); + } + }); +} diff --git a/frontend/src/redux/reducers/grade.ts b/frontend/src/redux/grades/reducers.ts similarity index 87% rename from frontend/src/redux/reducers/grade.ts rename to frontend/src/redux/grades/reducers.ts index 6d33b359d..39618dad7 100644 --- a/frontend/src/redux/reducers/grade.ts +++ b/frontend/src/redux/grades/reducers.ts @@ -1,6 +1,5 @@ -import vars from '../../utils/variables'; +import { GRADE } from './types'; import { - GRADE, GRADE_ADD_COURSE, GRADE_REMOVE_COURSE, GRADE_RESET, @@ -9,7 +8,7 @@ import { UPDATE_GRADE_CONTEXT, UPDATE_GRADE_DATA, UPDATE_GRADE_SELECTED -} from '../actionTypes'; +} from './types'; const initialState: GradeState = { context: { courses: [] }, @@ -52,11 +51,10 @@ export default function grade(state = initialState, action: GradeAction): GradeS } case UPDATE_GRADE_DATA: { const { gradesData } = action.payload; - const graphData = vars.possibleGrades.map((letterGrade) => ({ + const graphData = Object.values(GRADE).map((letterGrade) => ({ name: letterGrade, ...gradesData.reduce((grades, grade) => { - grades[grade.id] = - (grade[letterGrade as keyof typeof GRADE].numerator / grade.denominator) * 100; + grades[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100; return grades; }, {} as Record) })); diff --git a/frontend/src/redux/grades/types.ts b/frontend/src/redux/grades/types.ts new file mode 100644 index 000000000..e3899ecb3 --- /dev/null +++ b/frontend/src/redux/grades/types.ts @@ -0,0 +1,89 @@ +import { BaseDataType, BaseState, CourseSnapshotType, FormattedCourseType } from 'redux/types'; + +export const UPDATE_GRADE_CONTEXT = 'UPDATE_GRADE_CONTEXT'; +export const GRADE_ADD_COURSE = 'GRADE_ADD_COURSE'; +export const GRADE_REMOVE_COURSE = 'GRADE_REMOVE_COURSE'; +export const UPDATE_GRADE_DATA = 'UPDATE_GRADE_DATA'; +export const UPDATE_GRADE_SELECTED = 'UPDATE_GRADE_SELECTED'; +export const GRADE_RESET = 'GRADE_RESET'; + +export type GradeAction = + | { type: typeof UPDATE_GRADE_CONTEXT; payload: { data: { courses: CourseSnapshotType[] } } } + | { type: typeof GRADE_RESET } + | { + type: typeof GRADE_ADD_COURSE; + payload: { + formattedCourse: FormattedCourseType; + }; + } + | { + type: typeof GRADE_REMOVE_COURSE; + payload: { + id: string; + color: string; + }; + } + | { + type: typeof UPDATE_GRADE_DATA; + payload: { + gradesData: GradesDataType[]; + }; + } + | { + type: typeof UPDATE_GRADE_SELECTED; + payload: { + data: GradeSelectedType[]; + }; + }; + +export type GradeState = BaseState & { + gradesData: GradesDataType[]; + sections: GradeSelectedType[]; + graphData: { name: string }[]; +}; + +export enum GRADE { + 'A+' = 'A+', + 'A' = 'A', + 'A-' = 'A-', + 'B+' = 'B+', + 'B' = 'B', + 'B-' = 'B-', + 'C+' = 'C+', + 'C' = 'C', + 'C-' = 'C-', + 'D' = 'D', + 'F' = 'F', + 'P' = 'P', + 'NP' = 'NP' +} + +export type GradesDataType = BaseDataType & { + course_gpa: number; + course_letter: string; + denominator: number; + section_gpa: number; + section_letter: string; + semester: string; +} & { + [grade in GRADE]: { + percent: number; + numerator: number; + percentile_high: number; + percentile_low: number; + }; +}; + +export type GradeSelectedType = { + grade_id: number; + instructor: string; + section_number: string; + semester: string; + year: string; +}; + +export type UpdatedClassType = { + value: number; + label: string; + course: CourseSnapshotType; +}; diff --git a/frontend/src/redux/store.ts b/frontend/src/redux/store.ts index d184ad22c..846c68047 100644 --- a/frontend/src/redux/store.ts +++ b/frontend/src/redux/store.ts @@ -1,8 +1,8 @@ import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; -import grade from './reducers/grade'; -import enrollment from './reducers/enrollment'; +import grade from './grades/reducers'; +import enrollment from './enrollment/reducers'; import authReducer from './auth/reducer'; import { commonReducer } from './common/reducer'; @@ -17,8 +17,8 @@ const reducer = combineReducers({ export type ReduxState = ReturnType; -export const useReduxSelector: TypedUseSelectorHook = useSelector +export const useReduxSelector: TypedUseSelectorHook = useSelector; const composeEnhancers = (window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; -export default createStore(reducer, composeEnhancers(applyMiddleware(thunkMiddleware))); \ No newline at end of file +export default createStore(reducer, composeEnhancers(applyMiddleware(thunkMiddleware))); diff --git a/frontend/src/redux/types.ts b/frontend/src/redux/types.ts new file mode 100644 index 000000000..18a63d72d --- /dev/null +++ b/frontend/src/redux/types.ts @@ -0,0 +1,35 @@ +export type CourseSnapshotType = { + abbreviation: string; + course_number: string; + id: number; +}; + +export type UnformattedCourseType = { + colorId: string; + courseID: number; + id: string; + instructor: string; + sections: (number | string)[]; + semester: string; +}; + +export type FormattedCourseDataType = { course: string; title: string }; + +export type FormattedCourseType = UnformattedCourseType & FormattedCourseDataType; + +export type BaseState = { + context: { courses: CourseSnapshotType[] }; + selectedCourses: FormattedCourseType[]; + selectPrimary: string | { value: string; label: string }; + selectSecondary: string | { value: string; label: string }; + usedColorIds: string[]; +}; + +export type BaseDataType = { + colorId: string; + course_id: number; + id: string; + instructor: string; + subtitle: string; + title: string; +}; From 4f6d314bc2af70ec09e126e0692fd5067c1919b5 Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 01:47:19 -0800 Subject: [PATCH 10/27] Update variables.js --- frontend/src/utils/variables.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/utils/variables.js b/frontend/src/utils/variables.js index 39c7e0f8f..a10c3b2a4 100644 --- a/frontend/src/utils/variables.js +++ b/frontend/src/utils/variables.js @@ -610,8 +610,6 @@ var grades = [ { name: 'F', classA: 0, classB: 2 } ]; -var possibleGrades = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP']; - var colors = ['#4EA6FB', '#6AE086', '#ED5186', '#F9E152']; const vars = { @@ -634,7 +632,7 @@ const vars = { optionsEnrollment, responsiveEnrollment, grades, - possibleGrades + }; export default vars; From c27d53c6d165ccc58e3c54b9bf2c4bc39ff2b8de Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 02:49:18 -0800 Subject: [PATCH 11/27] Enrollment: typing IP --- .../app/Enrollment/{index.jsx => index.tsx} | 75 +++++++++++-------- ...entInfoCard.jsx => EnrollmentInfoCard.tsx} | 16 ++++ ...tGraphCard.jsx => EnrollmentGraphCard.tsx} | 67 ++++++++++------- 3 files changed, 101 insertions(+), 57 deletions(-) rename frontend/src/app/Enrollment/{index.jsx => index.tsx} (64%) rename frontend/src/components/EnrollmentInfoCard/{EnrollmentInfoCard.jsx => EnrollmentInfoCard.tsx} (79%) rename frontend/src/components/GraphCard/{EnrollmentGraphCard.jsx => EnrollmentGraphCard.tsx} (68%) diff --git a/frontend/src/app/Enrollment/index.jsx b/frontend/src/app/Enrollment/index.tsx similarity index 64% rename from frontend/src/app/Enrollment/index.jsx rename to frontend/src/app/Enrollment/index.tsx index 1dcafff05..22045102d 100644 --- a/frontend/src/app/Enrollment/index.jsx +++ b/frontend/src/app/Enrollment/index.tsx @@ -1,30 +1,35 @@ import { useLocation, useNavigate } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; -import { useDispatch, useSelector } from 'react-redux'; import ClassCardList from '../../components/ClassCards/ClassCardList'; +import EnrollmentSearchBar from '../../components/ClassSearchBar/EnrollmentSearchBar.jsx'; import EnrollmentGraphCard from '../../components/GraphCard/EnrollmentGraphCard'; -import EnrollmentSearchBar from '../../components/ClassSearchBar/EnrollmentSearchBar'; import info from '../../assets/img/images/graphs/info.svg'; +import { useCallback, useEffect, useState } from 'react'; +import type { UnformattedCourseType } from 'redux/types'; +import { useReduxSelector } from 'redux/store'; import { - fetchEnrollContext, - fetchEnrollClass, enrollRemoveCourse, enrollReset, + fetchEnrollClass, + fetchEnrollContext, fetchEnrollFromUrl -} from '../../redux/actions'; -import { useCallback, useEffect, useState } from 'react'; +} from '../../redux/enrollment/actions'; +import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; -const toUrlForm = (s) => { - return s.toLowerCase().split(' ').join('-'); +const toUrlForm = (string: string) => { + return string.toLowerCase().split(' ').join('-'); }; export function Component() { - const [additionalInfo, setAdditionalInfo] = useState([]); + const [additionalInfo, setAdditionalInfo] = useState< + [EnrollmentStatusType, TelebearsType, number[], number[]][] + >([]); - const { context, selectedCourses, usedColorIds } = useSelector((state) => state.enrollment); - const { mobile: isMobile } = useSelector((state) => state.common); + const { context, selectedCourses, usedColorIds } = useReduxSelector((state) => state.enrollment); + const { mobile: isMobile } = useReduxSelector((state) => state.common); const dispatch = useDispatch(); const location = useLocation(); @@ -33,7 +38,7 @@ export function Component() { useEffect(() => { const fillFromUrl = () => { try { - let url = location.pathname; + const url = location.pathname; if (url && (url === '/enrollment/' || url === '/enrollment')) { dispatch(enrollReset()); @@ -48,13 +53,13 @@ export function Component() { dispatch(fetchEnrollContext()); dispatch(enrollReset()); fillFromUrl(); - }, []); + }, [dispatch, location.pathname, navigate]); const addToUrl = useCallback( - (course) => { - let instructor = course.instructor === 'all' ? 'all' : course.sections[0]; + (course: UnformattedCourseType) => { + const instructor = course.instructor === 'all' ? 'all' : course.sections[0]; - let courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( + const courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( course.semester )}-${instructor}`; @@ -72,8 +77,8 @@ export function Component() { ); const addCourse = useCallback( - (course) => { - for (let selected of selectedCourses) { + (course: UnformattedCourseType) => { + for (const selected of selectedCourses) { if (selected.id === course.id) { return; } @@ -97,16 +102,16 @@ export function Component() { ); const refillUrl = useCallback( - (id) => { - let updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); + (id: string) => { + const updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); let url = '/enrollment/'; for (let i = 0; i < updatedCourses.length; i++) { - let c = updatedCourses[i]; + const course = updatedCourses[i]; if (i !== 0) url += '&'; - let instructor = c.instructor === 'all' ? 'all' : c.sections[0]; - url += `${c.colorId}-${c.courseID}-${toUrlForm(c.semester)}-${instructor}`; + const instructor = course.instructor === 'all' ? 'all' : course.sections[0]; + url += `${course.colorId}-${course.courseID}-${toUrlForm(course.semester)}-${instructor}`; } navigate(url, { replace: true }); @@ -115,7 +120,7 @@ export function Component() { ); const removeCourse = useCallback( - (id, color) => { + (id: string, color: string) => { refillUrl(id); dispatch(enrollRemoveCourse(id, color)); }, @@ -123,12 +128,21 @@ export function Component() { ); const updateClassCardEnrollment = useCallback( - (latest_point, telebears, enrolled_info, waitlisted_info) => { - var info = []; - - for (var i = 0; i < latest_point.length; i++) { - info.push([latest_point[i], telebears[i], enrolled_info[i], waitlisted_info[i]]); - } + ( + latest_point: EnrollmentStatusType[], + telebears: TelebearsType[], + enrolled_info: number[][], + waitlisted_info: number[][] + ) => { + const info = latest_point.map( + (_, i) => + [latest_point[i], telebears[i], enrolled_info[i], waitlisted_info[i]] as [ + EnrollmentStatusType, + TelebearsType, + number[], + number[] + ] + ); setAdditionalInfo(info); }, @@ -144,6 +158,7 @@ export function Component() { fromCatalog={location.state ? location.state.course : false} isFull={selectedCourses.length === 4} isMobile={isMobile} + sectionNumber={'qwq'} /> getEnrollmentDay(selectedPoint, telebears), diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx similarity index 68% rename from frontend/src/components/GraphCard/EnrollmentGraphCard.jsx rename to frontend/src/components/GraphCard/EnrollmentGraphCard.tsx index 6fa4a9563..f1c925bde 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx @@ -1,18 +1,37 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col } from 'react-bootstrap'; +import { HTMLAttributes, useCallback, useEffect, useMemo, useState } from 'react'; +import { Col, Container, Row } from 'react-bootstrap'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import vars from '../../utils/variables'; +import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; import EnrollmentGraph from '../Graphs/EnrollmentGraph.jsx'; import GraphEmpty from '../Graphs/GraphEmpty.jsx'; -import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; - -import { fetchEnrollData } from '../../redux/actions'; -export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollment }) { - const [hoveredClass, setHoveredClass] = useState(false); - const { enrollmentData, graphData, selectedCourses } = useSelector((state) => state.enrollment); +import { EnrollmentDataType, EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; +import { useReduxSelector } from 'redux/store'; +import { fetchEnrollData } from '../../redux/enrollment/actions'; +import { FormattedCourseType } from 'redux/types'; + +export default function EnrollmentGraphCard({ + isMobile, + updateClassCardEnrollment, + ...rest +}: { + isMobile: boolean; + updateClassCardEnrollment: ( + latest_point: EnrollmentStatusType[], + telebears: TelebearsType[], + enrolled_info: number[][], + waitlisted_info: number[][] + ) => void; +} & HTMLAttributes) { + const [hoveredClass, setHoveredClass] = useState< + FormattedCourseType & EnrollmentDataType & { hoverDay: number } + >(); + const { enrollmentData, graphData, selectedCourses } = useReduxSelector( + (state) => state.enrollment + ); const dispatch = useDispatch(); useEffect(() => { @@ -20,7 +39,7 @@ export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollmen }, [selectedCourses, dispatch]); const update = useCallback( - (course, day) => { + (course: FormattedCourseType, day: number) => { if (!course || !enrollmentData || enrollmentData.length === 0) return; const selectedEnrollment = enrollmentData.filter((c) => course.id === c.id)[0]; @@ -45,7 +64,9 @@ export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollmen update(selectedCourses[0], 1); } - const latest_point = enrollmentData.map((course) => course.data[course.data.length - 1]); + const latest_point: EnrollmentStatusType[] = enrollmentData.map( + (course) => course.data[course.data.length - 1] + ); const telebears = enrollmentData.map((course) => course.telebears); const enrolled_info = latest_point.map((course) => [ course.enrolled, @@ -63,33 +84,25 @@ export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollmen // Handler function for updating EnrollmentInfoCard on hover const updateLineHover = useCallback( - (lineData) => { - const selectedClassID = lineData.dataKey; - const day = lineData.index; - const selectedCourse = selectedCourses.filter((course) => selectedClassID === course.id)[0]; - update(selectedCourse, day); + ({ dataKey: selectedClassID, index: day }: { dataKey: string; index: number }) => { + update(selectedCourses.filter((course) => selectedClassID === course.id)[0], day); }, [selectedCourses, update] ); // Handler function for updating EnrollmentInfoCard on hover with single course const updateGraphHover = useCallback( - (data) => { - const { isTooltipActive, activeLabel } = data; - + ({ isTooltipActive, activeLabel: day }: { isTooltipActive: boolean; activeLabel: number }) => { if (!isTooltipActive || selectedCourses.length !== 1) return; const selectedCourse = selectedCourses[0]; - const day = activeLabel; + update(selectedCourse, day); }, [selectedCourses, update] ); - const telebears = useMemo( - () => (enrollmentData.length ? enrollmentData[0].telebears : {}), - [enrollmentData] - ); + const telebears = useMemo(() => enrollmentData[0].telebears, [enrollmentData]); const graphEmpty = useMemo( () => enrollmentData.length === 0 || selectedCourses.length === 0, @@ -97,7 +110,7 @@ export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollmen ); return ( -
+
{hoveredClass && ( pt.day === hoveredClass.hoverDay)[0] } - todayPoint={hoveredClass.data[hoveredClass.data.length - 1]} telebears={telebears} - color={vars.colors[hoveredClass.colorId]} + color={vars.colors[parseInt(hoveredClass.colorId)]} enrolledMax={hoveredClass.enrolled_max} waitlistedMax={hoveredClass.waitlisted_max} /> From 2862eb2906f7555d0bd491112a94fd2e7c6fd2f3 Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 02:51:48 -0800 Subject: [PATCH 12/27] Grades: typing IP --- .../src/app/Grades/{index.jsx => index.tsx} | 81 +++++++++++-------- ...{GradesInfoCard.jsx => GradesInfoCard.tsx} | 23 +++++- .../GradesInfoCard/GradesInfoCardMobile.jsx | 35 -------- ...radesGraphCard.jsx => GradesGraphCard.tsx} | 81 ++++++++++++------- 4 files changed, 118 insertions(+), 102 deletions(-) rename frontend/src/app/Grades/{index.jsx => index.tsx} (64%) rename frontend/src/components/GradesInfoCard/{GradesInfoCard.jsx => GradesInfoCard.tsx} (87%) delete mode 100644 frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx rename frontend/src/components/GraphCard/{GradesGraphCard.jsx => GradesGraphCard.tsx} (67%) diff --git a/frontend/src/app/Grades/index.jsx b/frontend/src/app/Grades/index.tsx similarity index 64% rename from frontend/src/app/Grades/index.jsx rename to frontend/src/app/Grades/index.tsx index 694d0bf2c..36e9ae217 100644 --- a/frontend/src/app/Grades/index.jsx +++ b/frontend/src/app/Grades/index.tsx @@ -1,42 +1,43 @@ import { useCallback, useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import ClassCardList from '../../components/ClassCards/ClassCardList'; -import GradesGraphCard from '../../components/GraphCard/GradesGraphCard'; import GradesSearchBar from '../../components/ClassSearchBar/GradesSearchBar'; +import GradesGraphCard from '../../components/GraphCard/GradesGraphCard'; import info from '../../assets/img/images/graphs/info.svg'; import { - fetchGradeContext, fetchGradeClass, + fetchGradeContext, + fetchGradeFromUrl, gradeRemoveCourse, - gradeReset, - fetchGradeFromUrl -} from '../../redux/actions'; + gradeReset +} from 'redux/grades/actions'; +import { useReduxSelector } from 'redux/store'; +import { UnformattedCourseType } from 'redux/types'; -const toUrlForm = (s) => { - s = s.replace('/', '_'); - return s.toLowerCase().split(' ').join('-'); +const toUrlForm = (string: string) => { + return string.replace('/', '_').toLowerCase().split(' ').join('-'); }; export function Component() { - const [additionalInfo, setAdditionalInfo] = useState([]); + const [additionalInfo, setAdditionalInfo] = useState<[string, number, string, number][]>([]); const location = useLocation(); const navigate = useNavigate(); - const { context, selectedCourses, usedColorIds } = useSelector((state) => state.grade); - const { mobile: isMobile } = useSelector((state) => state.common); + const { context, selectedCourses, usedColorIds } = useReduxSelector((state) => state.grade); + const { mobile: isMobile } = useReduxSelector((state) => state.common); const dispatch = useDispatch(); const addToUrl = useCallback( - (course) => { - let instructor = toUrlForm(course.instructor); + (course: UnformattedCourseType) => { + const instructor = toUrlForm(course.instructor); - let courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( + const courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( course.semester )}-${instructor}`; @@ -54,8 +55,8 @@ export function Component() { ); const addCourse = useCallback( - (course) => { - for (let selected of selectedCourses) { + (course: UnformattedCourseType) => { + for (const selected of selectedCourses) { if (selected.id === course.id) { return; } @@ -77,21 +78,24 @@ export function Component() { ); const refillUrl = useCallback( - (id) => { - let updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); - let url = '/grades/'; - for (let i = 0; i < updatedCourses.length; i++) { - let c = updatedCourses[i]; - if (i !== 0) url += '&'; - url += `${c.colorId}-${c.courseID}-${toUrlForm(c.semester)}-${toUrlForm(c.instructor)}`; - } + (id: string) => { + const updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); + const url = + '/grades/' + + [ + updatedCourses.map((course) => { + return `${course.colorId}-${course.courseID}-${toUrlForm(course.semester)}-${toUrlForm( + course.instructor + )}`; + }) + ].join('&'); navigate(url, { replace: true }); }, [navigate, selectedCourses] ); const removeCourse = useCallback( - (id, color) => { + (id: string, color: string) => { refillUrl(id); dispatch(gradeRemoveCourse(id, color)); }, @@ -99,11 +103,22 @@ export function Component() { ); const updateClassCardGrade = useCallback( - (course_letter, course_gpa, section_letter, section_gpa) => { - var info = []; - for (var i = 0; i < course_letter.length; i++) { - info.push([course_letter[i], course_gpa[i], section_letter[i], section_gpa[i]]); - } + ( + course_letter: string[], + course_gpa: number[], + section_letter: string[], + section_gpa: number[] + ) => { + const info = course_letter.map( + (_, i) => + [course_letter[i], course_gpa[i], section_letter[i], section_gpa[i]] as [ + string, + number, + string, + number + ] + ); + setAdditionalInfo(info); }, [] @@ -112,7 +127,7 @@ export function Component() { useEffect(() => { const fillFromUrl = () => { try { - let url = location.pathname; + const url = location.pathname; if (url && (url === '/grades/' || url === '/grades')) { dispatch(gradeReset()); @@ -127,7 +142,7 @@ export function Component() { dispatch(fetchGradeContext()); dispatch(gradeReset()); fillFromUrl(); - }, []); + }, [dispatch, location.pathname, navigate]); return (
diff --git a/frontend/src/components/GradesInfoCard/GradesInfoCard.jsx b/frontend/src/components/GradesInfoCard/GradesInfoCard.tsx similarity index 87% rename from frontend/src/components/GradesInfoCard/GradesInfoCard.jsx rename to frontend/src/components/GradesInfoCard/GradesInfoCard.tsx index b4caf8246..6e811b92c 100644 --- a/frontend/src/components/GradesInfoCard/GradesInfoCard.jsx +++ b/frontend/src/components/GradesInfoCard/GradesInfoCard.tsx @@ -4,11 +4,11 @@ import 'react-tooltip/dist/react-tooltip.css'; import { percentileToString, getGradeColor } from '../../utils/utils'; import info from '../../assets/img/images/graphs/info.svg'; -var courseAvgText = +const courseAvgText = 'Course average refers to the average of all
sections available across all instructors.
'; -var sectionAvgText = +const sectionAvgText = 'Section average refers to the average of all sections that
have been filtered for using the specified options.
'; -var percentileText = +const percentileText = 'Percentile refers to the percentile range out of students who
received a letter grade, while the count and percentage
also include students who received P/NP grades.
'; export default function GradesInfoCard({ @@ -24,6 +24,23 @@ export default function GradesInfoCard({ selectedGrade, denominator, color +}: { + course: string; + subtitle: string; + semester: string; + instructor: string; + courseLetter: string; + courseGPA: number; + sectionLetter: string; + sectionGPA: number; + selectedPercentiles: { + numerator: number; + percentile_low: number; + percentile_high: number; + }; + selectedGrade: string; + denominator: number; + color: string; }) { return (
diff --git a/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx b/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx deleted file mode 100644 index 9493d73fe..000000000 --- a/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import GradesInfoCard from './GradesInfoCard'; - -export default function GradesInfoCardMobile({ - course, - subtitle, - semester, - instructor, - courseLetter, - courseGPA, - sectionLetter, - sectionGPA, - denominator, - betterGrade, - worseGrade, - color -}) { - const checkNullState = (item) => item || ' '; - - return ( - - ); -} diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.tsx similarity index 67% rename from frontend/src/components/GraphCard/GradesGraphCard.jsx rename to frontend/src/components/GraphCard/GradesGraphCard.tsx index 5ce7e5f05..a3192719d 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.tsx @@ -1,18 +1,35 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col } from 'react-bootstrap'; +import { HTMLAttributes, useCallback, useEffect, useMemo, useState } from 'react'; +import { Col, Container, Row } from 'react-bootstrap'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import vars from '../../utils/variables'; +import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; import GradesGraph from '../Graphs/GradesGraph'; import GraphEmpty from '../Graphs/GraphEmpty'; -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 [hoveredClass, setHoveredClass] = useState(false); +import { fetchGradeData } from 'redux/grades/actions'; +import { GRADE, GradesDataType } from 'redux/grades/types'; +import { useReduxSelector } from 'redux/store'; +import { FormattedCourseType } from 'redux/types'; + +export default function GradesGraphCard({ + isMobile, + updateClassCardGrade, + ...rest +}: { + isMobile: boolean; + updateClassCardGrade: ( + course_letter: string[], + course_gpa: number[], + section_letter: string[], + section_gpa: number[] + ) => void; +} & HTMLAttributes) { + const { gradesData, graphData, selectedCourses } = useReduxSelector((state) => state.grade); + const [hoveredClass, setHoveredClass] = useState< + FormattedCourseType & GradesDataType & { hoverGrade: GRADE } + >(); const [updateMobileHover, setUpdateMobileHover] = useState(true); const dispatch = useDispatch(); @@ -21,7 +38,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { }, [selectedCourses, dispatch]); const update = useCallback( - (course, grade) => { + (course: FormattedCourseType, grade: GRADE) => { if (!course || !gradesData || gradesData.length === 0) return; const selectedGrades = gradesData.filter((c) => course.id === c.id)[0]; @@ -38,7 +55,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { useEffect(() => { if (gradesData.length > 0 && selectedCourses.length === 1) { - update(selectedCourses[0], 0); + update(selectedCourses[0], GRADE.P); } const course_letter = gradesData.map((course) => course.course_letter); @@ -50,7 +67,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { // Handler function for updating GradesInfoCard on hover const updateBarHover = useCallback( - (barData) => { + (barData: { payload: Record; name: GRADE; value: number }) => { const { payload, name, value } = barData; let selectedClassID = ''; @@ -71,7 +88,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { // Handler function for updating GradesInfoCard on hover with single course const updateGraphHover = useCallback( - (data) => { + (data: { isTooltipActive: boolean; activeLabel: GRADE }) => { const { isTooltipActive, activeLabel } = data; const noBarMobile = updateMobileHover && isMobile; @@ -95,7 +112,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { ); return ( -
+
{isMobile &&
Grade Distribution
} - + {hoveredClass && ( + + )} {graphEmpty && ( @@ -153,7 +172,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { denominator={hoveredClass.denominator} selectedPercentiles={hoveredClass[hoveredClass.hoverGrade]} selectedGrade={hoveredClass.hoverGrade} - color={vars.colors[hoveredClass.colorId]} + color={vars.colors[parseInt(hoveredClass.colorId)]} /> )} From 55771cdd7242df71691aeafa71eba161baf323aa Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 02:52:00 -0800 Subject: [PATCH 13/27] Utilities: typed --- frontend/src/utils/utils.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/utils.tsx b/frontend/src/utils/utils.tsx index d202d2f56..820082c44 100644 --- a/frontend/src/utils/utils.tsx +++ b/frontend/src/utils/utils.tsx @@ -2,6 +2,8 @@ * A bunch of utility functions */ +import { TelebearsType } from 'redux/enrollment/types'; + /** * Returns a paragraph tag styled with color with respect to percentage * @param {string} text text in the paragraph tag @@ -99,7 +101,7 @@ function getGradeColor(grade: string | undefined) { * TODO: remove the any's * */ -function getEnrollmentDay(selectedPoint: any, telebears: any) { +function getEnrollmentDay(selectedPoint: any, telebears: TelebearsType) { let period = ''; let daysAfterPeriodStarts = 0; if (selectedPoint.day < telebears.phase2_start_day) { From 7f260893a477d91ebdafecd27e88e97175d1dd4a Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 02:52:15 -0800 Subject: [PATCH 14/27] fixing imports --- frontend/src/Berkeleytime.tsx | 2 +- frontend/src/app/Enrollment/index.tsx | 2 +- .../ClassSearchBar/EnrollmentSearchBar.jsx | 79 ++++++++++++++++++- .../ClassSearchBar/GradesSearchBar.jsx | 2 +- frontend/src/components/Common/Banner.tsx | 2 +- .../GraphCard/EnrollmentGraphCard.tsx | 2 +- .../src/components/Landing/LandingModal.tsx | 4 +- 7 files changed, 85 insertions(+), 8 deletions(-) diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index 9d6f45d6a..d76d9ed7e 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 } from 'redux/enrollment/actions'; import { IconoirProvider } from 'iconoir-react'; const Berkeleytime = () => { diff --git a/frontend/src/app/Enrollment/index.tsx b/frontend/src/app/Enrollment/index.tsx index 22045102d..12b8ed16d 100644 --- a/frontend/src/app/Enrollment/index.tsx +++ b/frontend/src/app/Enrollment/index.tsx @@ -16,7 +16,7 @@ import { fetchEnrollClass, fetchEnrollContext, fetchEnrollFromUrl -} from '../../redux/enrollment/actions'; +} from 'redux/enrollment/actions'; import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; const toUrlForm = (string: string) => { diff --git a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx index f320e166d..bfd946b2b 100644 --- a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx @@ -2,10 +2,87 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Container, Row, Col, Button } from 'react-bootstrap'; import hash from 'object-hash'; -import { fetchEnrollSelected } from '../../redux/actions'; +import { fetchEnrollSelected } from 'redux/enrollment/actions'; import { useDispatch, useSelector } from 'react-redux'; import BTSelect from 'components/Custom/Select'; +/** typed portions + * + * const buildCoursesOptions = (courses: CourseSnapshotType[]) => { + if (!courses) { + return []; + } + const options = courses.map((course) => ({ + value: course.id, + label: `${course.abbreviation} ${course.course_number}`, + course + })); + + return options; +}; + +const capitalize = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; + +const getSectionSemester = (section: SectionType) => { + return `${capitalize(section.semester)} ${section.year}`; +}; + +const buildPrimaryOptions = (sections: SectionType[]) => { + const ret = []; + const map = new Map(); + + for (const section of sections) { + const semester = getSectionSemester(section); + if (!map.has(semester)) { + map.set(semester, true); + ret.push({ + value: semester, + label: semester + }); + } + } + + return ret; +}; + +const buildSecondaryOptions = (semesters: SectionType[], selectPrimary: string) => { + if (semesters.length === 0 || selectPrimary === undefined || selectPrimary === '') { + return []; + } + + const ret = []; + + const sections = semesters.filter((semester) => getSectionSemester(semester) === selectPrimary)[0] + .sections; + if (sections.length > 1) { + ret.push({ value: 'all', label: 'All Instructors' }); + } + + for (const section of sections) { + const instructor = `${ + section.instructor === null || section.instructor === '' ? 'None' : section.instructor + } / ${section.section_number}`; + ret.push({ + value: instructor, + label: instructor, + sectionNumber: instructor.split(' / ')[1], + sectionId: section.section_id + }); + } + return ret; +}; + +const customStyles = { + clearIndicator: (provided: Record): Record => ({ + ...provided, + marginRight: 0, + paddingRight: 0 + }) +}; + */ + const buildCoursesOptions = (courses) => { if (!courses) { return []; diff --git a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx index 39038bedc..63d0fc03c 100644 --- a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx @@ -4,7 +4,7 @@ import hash from 'object-hash'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchGradeSelected } from '../../redux/actions'; +import { fetchGradeSelected } from 'redux/grades/actions'; import BTSelect from 'components/Custom/Select'; const sortOptions = [ diff --git a/frontend/src/components/Common/Banner.tsx b/frontend/src/components/Common/Banner.tsx index 61295a1f4..065dbd870 100644 --- a/frontend/src/components/Common/Banner.tsx +++ b/frontend/src/components/Common/Banner.tsx @@ -1,6 +1,6 @@ import { useSelector, useDispatch } from 'react-redux'; import { Button } from 'bt/custom'; -import { closeBanner } from '../../redux/common/actions'; +import { closeBanner } from 'redux/common/actions'; import close from '../../assets/svg/common/close.svg'; import { ReduxState } from 'redux/store'; diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx index f1c925bde..4ac972552 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx @@ -10,7 +10,7 @@ import GraphEmpty from '../Graphs/GraphEmpty.jsx'; import { EnrollmentDataType, EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; import { useReduxSelector } from 'redux/store'; -import { fetchEnrollData } from '../../redux/enrollment/actions'; +import { fetchEnrollData } from 'redux/enrollment/actions'; import { FormattedCourseType } from 'redux/types'; export default function EnrollmentGraphCard({ diff --git a/frontend/src/components/Landing/LandingModal.tsx b/frontend/src/components/Landing/LandingModal.tsx index 5ac92185a..fa9a505e4 100644 --- a/frontend/src/components/Landing/LandingModal.tsx +++ b/frontend/src/components/Landing/LandingModal.tsx @@ -5,8 +5,8 @@ import closeIcon from 'assets/svg/common/close.svg'; import schedulerImg from '../../assets/img/landing/scheduler.png'; import { useDispatch, useSelector } from 'react-redux'; -import { ReduxState } from '../../redux/store'; -import { closeLandingModal } from '../../redux/common/actions'; +import { ReduxState } from 'redux/store'; +import { closeLandingModal } from 'redux/common/actions'; const modal_info = { subtitle: 'NEW!', From 36d97e0d489c61488af9732de43ee8746499247d Mon Sep 17 00:00:00 2001 From: akmazian Date: Tue, 7 Nov 2023 02:53:35 -0800 Subject: [PATCH 15/27] shared assets: typed --- .../{ClassCard.jsx => ClassCard.tsx} | 50 +++++++---- .../{ClassCardList.jsx => ClassCardList.tsx} | 16 +++- .../components/ClassCards/ClassCardMobile.jsx | 76 ---------------- .../components/ClassCards/ClassCardMobile.tsx | 88 +++++++++++++++++++ 4 files changed, 134 insertions(+), 96 deletions(-) rename frontend/src/components/ClassCards/{ClassCard.jsx => ClassCard.tsx} (56%) rename frontend/src/components/ClassCards/{ClassCardList.jsx => ClassCardList.tsx} (53%) delete mode 100644 frontend/src/components/ClassCards/ClassCardMobile.jsx create mode 100644 frontend/src/components/ClassCards/ClassCardMobile.tsx diff --git a/frontend/src/components/ClassCards/ClassCard.jsx b/frontend/src/components/ClassCards/ClassCard.tsx similarity index 56% rename from frontend/src/components/ClassCards/ClassCard.jsx rename to frontend/src/components/ClassCards/ClassCard.tsx index f3a2ba1db..937800e87 100644 --- a/frontend/src/components/ClassCards/ClassCard.jsx +++ b/frontend/src/components/ClassCards/ClassCard.tsx @@ -1,22 +1,36 @@ import { Col } from 'react-bootstrap'; import ClassCardMobile from './ClassCardMobile'; +import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; -function ClassCard(props) { - const { - id, - course, - title, - fill, - semester, - faculty, - removeCourse, - colorId, - additionalInfo, - type, - isMobile - } = props; - +function ClassCard({ + id, + course, + title, + fill, + semester, + instructor, + removeCourse, + colorId, + additionalInfo, + type, + isMobile +}: { + id: string; + course: string; + title: string; + fill: string; + semester: string; + instructor: string; + removeCourse: (id: string, color: string) => void; + colorId: string; + additionalInfo: + | [string, number, string, number] + | [EnrollmentStatusType, TelebearsType, number[], number[]] + | undefined; + type: 'grades' | 'enrollment'; + isMobile: boolean; +}) { return (
@@ -38,8 +52,10 @@ function ClassCard(props) {
-
{title}
-
{`${semester} • ${faculty}`}
+
+ {title} +
+
{`${semester} • ${instructor}`}
{isMobile ? : null}
diff --git a/frontend/src/components/ClassCards/ClassCardList.jsx b/frontend/src/components/ClassCards/ClassCardList.tsx similarity index 53% rename from frontend/src/components/ClassCards/ClassCardList.jsx rename to frontend/src/components/ClassCards/ClassCardList.tsx index fc85a09ea..56c7d9707 100644 --- a/frontend/src/components/ClassCards/ClassCardList.jsx +++ b/frontend/src/components/ClassCards/ClassCardList.tsx @@ -2,6 +2,8 @@ import { Container, Row } from 'react-bootstrap'; import ClassCard from './ClassCard'; import vars from '../../utils/variables'; +import { FormattedCourseType } from 'redux/types'; +import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; export default function ClassCardList({ selectedCourses, @@ -9,6 +11,14 @@ export default function ClassCardList({ additionalInfo, type, isMobile +}: { + selectedCourses: FormattedCourseType[]; + removeCourse: (id: string, color: string) => void; + additionalInfo: + | [string, number, string, number][] + | [EnrollmentStatusType, TelebearsType, number[], number[]][]; + type: 'grades' | 'enrollment'; + isMobile: boolean; }) { return ( @@ -19,12 +29,12 @@ export default function ClassCardList({ id={item.id} course={item.course} title={item.title} - fill={vars.colors[item.colorId]} + fill={vars.colors[parseInt(item.colorId)]} semester={item.semester === 'all' ? 'All Semesters' : item.semester} - faculty={item.instructor === 'all' ? 'All Instructors' : item.instructor} + instructor={item.instructor === 'all' ? 'All Instructors' : item.instructor} removeCourse={removeCourse} colorId={item.colorId} - additionalInfo={additionalInfo ? additionalInfo[i] : 0} + additionalInfo={additionalInfo ? additionalInfo[i] : undefined} type={type} isMobile={isMobile} /> diff --git a/frontend/src/components/ClassCards/ClassCardMobile.jsx b/frontend/src/components/ClassCards/ClassCardMobile.jsx deleted file mode 100644 index e80e6d825..000000000 --- a/frontend/src/components/ClassCards/ClassCardMobile.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import { getGradeColor, getEnrollmentDay, applyIndicatorEnrollment } from '../../utils/utils'; - -function ClassCardMobile(props) { - const { additionalInfo, type } = props; - - const nullCheck = (e) => { - return e !== undefined && e !== null; - }; - - if (type === 'grades') { - const courseLetter = additionalInfo ? additionalInfo[0] : null; - const courseGPA = additionalInfo ? additionalInfo[1] : null; - const sectionLetter = additionalInfo ? additionalInfo[2] : null; - const sectionGPA = additionalInfo ? additionalInfo[3] : null; - - return ( -
-
-
Course Average
- {nullCheck(courseLetter) ? ( -
- {courseLetter} (GPA: {courseGPA}) -
- ) : ( - '--' - )} -
-
-
Section Average
- {nullCheck(sectionLetter) ? ( -
- {sectionLetter} (GPA:{' '} - {sectionGPA}) -
- ) : ( - '--' - )} -
-
- ); - } else { - const latest_point = additionalInfo ? additionalInfo[0] : null; - const telebears = additionalInfo ? additionalInfo[1] : null; - const enrollment_info = additionalInfo ? additionalInfo[2] : null; - const waitlisted_info = additionalInfo ? additionalInfo[3] : null; - - let date_info = []; - if (latest_point != null && telebears != null) { - date_info = getEnrollmentDay(latest_point, telebears); - } - - return ( -
-
-
- {date_info ? date_info['period'] + ': ' + date_info['daysAfterPeriodStarts'] : '--'} -
-
- Enrollment Percent: - {nullCheck(enrollment_info) - ? applyIndicatorEnrollment.apply(null, enrollment_info) - : '--'} -
-
- Waitlist Percent: - {nullCheck(waitlisted_info) - ? applyIndicatorEnrollment.apply(null, waitlisted_info) - : '--'} -
-
-
- ); - } -} - -export default ClassCardMobile; diff --git a/frontend/src/components/ClassCards/ClassCardMobile.tsx b/frontend/src/components/ClassCards/ClassCardMobile.tsx new file mode 100644 index 000000000..8817a2dbe --- /dev/null +++ b/frontend/src/components/ClassCards/ClassCardMobile.tsx @@ -0,0 +1,88 @@ +import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; +import { getGradeColor, getEnrollmentDay, applyIndicatorEnrollment } from '../../utils/utils'; + +function ClassCardMobile({ + additionalInfo, + type +}: { + additionalInfo: + | [string, number, string, number] + | [EnrollmentStatusType, TelebearsType, number[], number[]] + | undefined; + type: 'grades' | 'enrollment'; +}) { + if (type === 'grades') { + additionalInfo = additionalInfo as [string, number, string, number]; + + const courseLetter = additionalInfo ? additionalInfo[0].toString() : undefined; + const courseGPA = additionalInfo ? additionalInfo[1] : undefined; + const sectionLetter = additionalInfo ? additionalInfo[2].toString() : undefined; + const sectionGPA = additionalInfo ? additionalInfo[3] : undefined; + + return ( +
+
+
Course Average
+ {courseLetter ? ( +
+ {courseLetter} (GPA: {courseGPA}) +
+ ) : ( + '--' + )} +
+
+
Section Average
+ {sectionLetter ? ( +
+ {sectionLetter} (GPA:{' '} + {sectionGPA}) +
+ ) : ( + '--' + )} +
+
+ ); + } else { + additionalInfo = additionalInfo as [EnrollmentStatusType, TelebearsType, number[], number[]]; + + const latest_point = additionalInfo ? additionalInfo[0] : undefined; + const telebears = additionalInfo ? additionalInfo[1] : undefined; + const enrollment_info = additionalInfo ? additionalInfo[2] : undefined; + const waitlisted_info = additionalInfo ? additionalInfo[3] : undefined; + + let date_info; + if (latest_point != null && telebears != null) { + date_info = getEnrollmentDay(latest_point, telebears); + } + + return ( +
+
+
+ {date_info ? date_info['period'] + ': ' + date_info['daysAfterPeriodStarts'] : '--'} +
+
+ Enrollment Percent: + {enrollment_info !== undefined && + waitlisted_info !== null && + enrollment_info.length === 3 + ? applyIndicatorEnrollment(...(enrollment_info as [number, number, number])) + : '--'} +
+
+ Waitlist Percent: + {waitlisted_info !== undefined && + waitlisted_info !== null && + waitlisted_info.length === 3 + ? applyIndicatorEnrollment(...(waitlisted_info as [number, number, number])) + : '--'} +
+
+
+ ); + } +} + +export default ClassCardMobile; From c820b43e70065330e3c3082efa07a169d19d1301 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:46:19 -0800 Subject: [PATCH 16/27] Revert "shared assets: typed" This reverts commit 36d97e0d489c61488af9732de43ee8746499247d. --- .../{ClassCard.tsx => ClassCard.jsx} | 50 ++++------- .../{ClassCardList.tsx => ClassCardList.jsx} | 16 +--- .../components/ClassCards/ClassCardMobile.jsx | 76 ++++++++++++++++ .../components/ClassCards/ClassCardMobile.tsx | 88 ------------------- 4 files changed, 96 insertions(+), 134 deletions(-) rename frontend/src/components/ClassCards/{ClassCard.tsx => ClassCard.jsx} (56%) rename frontend/src/components/ClassCards/{ClassCardList.tsx => ClassCardList.jsx} (53%) create mode 100644 frontend/src/components/ClassCards/ClassCardMobile.jsx delete mode 100644 frontend/src/components/ClassCards/ClassCardMobile.tsx diff --git a/frontend/src/components/ClassCards/ClassCard.tsx b/frontend/src/components/ClassCards/ClassCard.jsx similarity index 56% rename from frontend/src/components/ClassCards/ClassCard.tsx rename to frontend/src/components/ClassCards/ClassCard.jsx index 937800e87..f3a2ba1db 100644 --- a/frontend/src/components/ClassCards/ClassCard.tsx +++ b/frontend/src/components/ClassCards/ClassCard.jsx @@ -1,36 +1,22 @@ import { Col } from 'react-bootstrap'; import ClassCardMobile from './ClassCardMobile'; -import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; -function ClassCard({ - id, - course, - title, - fill, - semester, - instructor, - removeCourse, - colorId, - additionalInfo, - type, - isMobile -}: { - id: string; - course: string; - title: string; - fill: string; - semester: string; - instructor: string; - removeCourse: (id: string, color: string) => void; - colorId: string; - additionalInfo: - | [string, number, string, number] - | [EnrollmentStatusType, TelebearsType, number[], number[]] - | undefined; - type: 'grades' | 'enrollment'; - isMobile: boolean; -}) { +function ClassCard(props) { + const { + id, + course, + title, + fill, + semester, + faculty, + removeCourse, + colorId, + additionalInfo, + type, + isMobile + } = props; + return (
@@ -52,10 +38,8 @@ function ClassCard({
-
- {title} -
-
{`${semester} • ${instructor}`}
+
{title}
+
{`${semester} • ${faculty}`}
{isMobile ? : null}
diff --git a/frontend/src/components/ClassCards/ClassCardList.tsx b/frontend/src/components/ClassCards/ClassCardList.jsx similarity index 53% rename from frontend/src/components/ClassCards/ClassCardList.tsx rename to frontend/src/components/ClassCards/ClassCardList.jsx index 56c7d9707..fc85a09ea 100644 --- a/frontend/src/components/ClassCards/ClassCardList.tsx +++ b/frontend/src/components/ClassCards/ClassCardList.jsx @@ -2,8 +2,6 @@ import { Container, Row } from 'react-bootstrap'; import ClassCard from './ClassCard'; import vars from '../../utils/variables'; -import { FormattedCourseType } from 'redux/types'; -import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; export default function ClassCardList({ selectedCourses, @@ -11,14 +9,6 @@ export default function ClassCardList({ additionalInfo, type, isMobile -}: { - selectedCourses: FormattedCourseType[]; - removeCourse: (id: string, color: string) => void; - additionalInfo: - | [string, number, string, number][] - | [EnrollmentStatusType, TelebearsType, number[], number[]][]; - type: 'grades' | 'enrollment'; - isMobile: boolean; }) { return ( @@ -29,12 +19,12 @@ export default function ClassCardList({ id={item.id} course={item.course} title={item.title} - fill={vars.colors[parseInt(item.colorId)]} + fill={vars.colors[item.colorId]} semester={item.semester === 'all' ? 'All Semesters' : item.semester} - instructor={item.instructor === 'all' ? 'All Instructors' : item.instructor} + faculty={item.instructor === 'all' ? 'All Instructors' : item.instructor} removeCourse={removeCourse} colorId={item.colorId} - additionalInfo={additionalInfo ? additionalInfo[i] : undefined} + additionalInfo={additionalInfo ? additionalInfo[i] : 0} type={type} isMobile={isMobile} /> diff --git a/frontend/src/components/ClassCards/ClassCardMobile.jsx b/frontend/src/components/ClassCards/ClassCardMobile.jsx new file mode 100644 index 000000000..e80e6d825 --- /dev/null +++ b/frontend/src/components/ClassCards/ClassCardMobile.jsx @@ -0,0 +1,76 @@ +import { getGradeColor, getEnrollmentDay, applyIndicatorEnrollment } from '../../utils/utils'; + +function ClassCardMobile(props) { + const { additionalInfo, type } = props; + + const nullCheck = (e) => { + return e !== undefined && e !== null; + }; + + if (type === 'grades') { + const courseLetter = additionalInfo ? additionalInfo[0] : null; + const courseGPA = additionalInfo ? additionalInfo[1] : null; + const sectionLetter = additionalInfo ? additionalInfo[2] : null; + const sectionGPA = additionalInfo ? additionalInfo[3] : null; + + return ( +
+
+
Course Average
+ {nullCheck(courseLetter) ? ( +
+ {courseLetter} (GPA: {courseGPA}) +
+ ) : ( + '--' + )} +
+
+
Section Average
+ {nullCheck(sectionLetter) ? ( +
+ {sectionLetter} (GPA:{' '} + {sectionGPA}) +
+ ) : ( + '--' + )} +
+
+ ); + } else { + const latest_point = additionalInfo ? additionalInfo[0] : null; + const telebears = additionalInfo ? additionalInfo[1] : null; + const enrollment_info = additionalInfo ? additionalInfo[2] : null; + const waitlisted_info = additionalInfo ? additionalInfo[3] : null; + + let date_info = []; + if (latest_point != null && telebears != null) { + date_info = getEnrollmentDay(latest_point, telebears); + } + + return ( +
+
+
+ {date_info ? date_info['period'] + ': ' + date_info['daysAfterPeriodStarts'] : '--'} +
+
+ Enrollment Percent: + {nullCheck(enrollment_info) + ? applyIndicatorEnrollment.apply(null, enrollment_info) + : '--'} +
+
+ Waitlist Percent: + {nullCheck(waitlisted_info) + ? applyIndicatorEnrollment.apply(null, waitlisted_info) + : '--'} +
+
+
+ ); + } +} + +export default ClassCardMobile; diff --git a/frontend/src/components/ClassCards/ClassCardMobile.tsx b/frontend/src/components/ClassCards/ClassCardMobile.tsx deleted file mode 100644 index 8817a2dbe..000000000 --- a/frontend/src/components/ClassCards/ClassCardMobile.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; -import { getGradeColor, getEnrollmentDay, applyIndicatorEnrollment } from '../../utils/utils'; - -function ClassCardMobile({ - additionalInfo, - type -}: { - additionalInfo: - | [string, number, string, number] - | [EnrollmentStatusType, TelebearsType, number[], number[]] - | undefined; - type: 'grades' | 'enrollment'; -}) { - if (type === 'grades') { - additionalInfo = additionalInfo as [string, number, string, number]; - - const courseLetter = additionalInfo ? additionalInfo[0].toString() : undefined; - const courseGPA = additionalInfo ? additionalInfo[1] : undefined; - const sectionLetter = additionalInfo ? additionalInfo[2].toString() : undefined; - const sectionGPA = additionalInfo ? additionalInfo[3] : undefined; - - return ( -
-
-
Course Average
- {courseLetter ? ( -
- {courseLetter} (GPA: {courseGPA}) -
- ) : ( - '--' - )} -
-
-
Section Average
- {sectionLetter ? ( -
- {sectionLetter} (GPA:{' '} - {sectionGPA}) -
- ) : ( - '--' - )} -
-
- ); - } else { - additionalInfo = additionalInfo as [EnrollmentStatusType, TelebearsType, number[], number[]]; - - const latest_point = additionalInfo ? additionalInfo[0] : undefined; - const telebears = additionalInfo ? additionalInfo[1] : undefined; - const enrollment_info = additionalInfo ? additionalInfo[2] : undefined; - const waitlisted_info = additionalInfo ? additionalInfo[3] : undefined; - - let date_info; - if (latest_point != null && telebears != null) { - date_info = getEnrollmentDay(latest_point, telebears); - } - - return ( -
-
-
- {date_info ? date_info['period'] + ': ' + date_info['daysAfterPeriodStarts'] : '--'} -
-
- Enrollment Percent: - {enrollment_info !== undefined && - waitlisted_info !== null && - enrollment_info.length === 3 - ? applyIndicatorEnrollment(...(enrollment_info as [number, number, number])) - : '--'} -
-
- Waitlist Percent: - {waitlisted_info !== undefined && - waitlisted_info !== null && - waitlisted_info.length === 3 - ? applyIndicatorEnrollment(...(waitlisted_info as [number, number, number])) - : '--'} -
-
-
- ); - } -} - -export default ClassCardMobile; From a38e18ea44c2d10c8296da21758e95cece213736 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:47:01 -0800 Subject: [PATCH 17/27] Revert "fixing imports" This reverts commit 7f260893a477d91ebdafecd27e88e97175d1dd4a. --- frontend/src/Berkeleytime.tsx | 2 +- frontend/src/app/Enrollment/index.tsx | 2 +- .../ClassSearchBar/EnrollmentSearchBar.jsx | 79 +------------------ .../ClassSearchBar/GradesSearchBar.jsx | 2 +- frontend/src/components/Common/Banner.tsx | 2 +- .../GraphCard/EnrollmentGraphCard.tsx | 2 +- .../src/components/Landing/LandingModal.tsx | 4 +- 7 files changed, 8 insertions(+), 85 deletions(-) diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index d76d9ed7e..9d6f45d6a 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/enrollment/actions'; +import { fetchEnrollContext } from 'redux/actions'; import { IconoirProvider } from 'iconoir-react'; const Berkeleytime = () => { diff --git a/frontend/src/app/Enrollment/index.tsx b/frontend/src/app/Enrollment/index.tsx index 12b8ed16d..22045102d 100644 --- a/frontend/src/app/Enrollment/index.tsx +++ b/frontend/src/app/Enrollment/index.tsx @@ -16,7 +16,7 @@ import { fetchEnrollClass, fetchEnrollContext, fetchEnrollFromUrl -} from 'redux/enrollment/actions'; +} from '../../redux/enrollment/actions'; import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; const toUrlForm = (string: string) => { diff --git a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx index bfd946b2b..f320e166d 100644 --- a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx @@ -2,87 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Container, Row, Col, Button } from 'react-bootstrap'; import hash from 'object-hash'; -import { fetchEnrollSelected } from 'redux/enrollment/actions'; +import { fetchEnrollSelected } from '../../redux/actions'; import { useDispatch, useSelector } from 'react-redux'; import BTSelect from 'components/Custom/Select'; -/** typed portions - * - * const buildCoursesOptions = (courses: CourseSnapshotType[]) => { - if (!courses) { - return []; - } - const options = courses.map((course) => ({ - value: course.id, - label: `${course.abbreviation} ${course.course_number}`, - course - })); - - return options; -}; - -const capitalize = (string: string) => { - return string.charAt(0).toUpperCase() + string.slice(1); -}; - -const getSectionSemester = (section: SectionType) => { - return `${capitalize(section.semester)} ${section.year}`; -}; - -const buildPrimaryOptions = (sections: SectionType[]) => { - const ret = []; - const map = new Map(); - - for (const section of sections) { - const semester = getSectionSemester(section); - if (!map.has(semester)) { - map.set(semester, true); - ret.push({ - value: semester, - label: semester - }); - } - } - - return ret; -}; - -const buildSecondaryOptions = (semesters: SectionType[], selectPrimary: string) => { - if (semesters.length === 0 || selectPrimary === undefined || selectPrimary === '') { - return []; - } - - const ret = []; - - const sections = semesters.filter((semester) => getSectionSemester(semester) === selectPrimary)[0] - .sections; - if (sections.length > 1) { - ret.push({ value: 'all', label: 'All Instructors' }); - } - - for (const section of sections) { - const instructor = `${ - section.instructor === null || section.instructor === '' ? 'None' : section.instructor - } / ${section.section_number}`; - ret.push({ - value: instructor, - label: instructor, - sectionNumber: instructor.split(' / ')[1], - sectionId: section.section_id - }); - } - return ret; -}; - -const customStyles = { - clearIndicator: (provided: Record): Record => ({ - ...provided, - marginRight: 0, - paddingRight: 0 - }) -}; - */ - const buildCoursesOptions = (courses) => { if (!courses) { return []; diff --git a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx index 63d0fc03c..39038bedc 100644 --- a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx @@ -4,7 +4,7 @@ import hash from 'object-hash'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchGradeSelected } from 'redux/grades/actions'; +import { fetchGradeSelected } from '../../redux/actions'; import BTSelect from 'components/Custom/Select'; const sortOptions = [ diff --git a/frontend/src/components/Common/Banner.tsx b/frontend/src/components/Common/Banner.tsx index 065dbd870..61295a1f4 100644 --- a/frontend/src/components/Common/Banner.tsx +++ b/frontend/src/components/Common/Banner.tsx @@ -1,6 +1,6 @@ import { useSelector, useDispatch } from 'react-redux'; import { Button } from 'bt/custom'; -import { closeBanner } from 'redux/common/actions'; +import { closeBanner } from '../../redux/common/actions'; import close from '../../assets/svg/common/close.svg'; import { ReduxState } from 'redux/store'; diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx index 4ac972552..f1c925bde 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx @@ -10,7 +10,7 @@ import GraphEmpty from '../Graphs/GraphEmpty.jsx'; import { EnrollmentDataType, EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; import { useReduxSelector } from 'redux/store'; -import { fetchEnrollData } from 'redux/enrollment/actions'; +import { fetchEnrollData } from '../../redux/enrollment/actions'; import { FormattedCourseType } from 'redux/types'; export default function EnrollmentGraphCard({ diff --git a/frontend/src/components/Landing/LandingModal.tsx b/frontend/src/components/Landing/LandingModal.tsx index fa9a505e4..5ac92185a 100644 --- a/frontend/src/components/Landing/LandingModal.tsx +++ b/frontend/src/components/Landing/LandingModal.tsx @@ -5,8 +5,8 @@ import closeIcon from 'assets/svg/common/close.svg'; import schedulerImg from '../../assets/img/landing/scheduler.png'; import { useDispatch, useSelector } from 'react-redux'; -import { ReduxState } from 'redux/store'; -import { closeLandingModal } from 'redux/common/actions'; +import { ReduxState } from '../../redux/store'; +import { closeLandingModal } from '../../redux/common/actions'; const modal_info = { subtitle: 'NEW!', From 87a8f7796960f11bd7979fb0d066687fb8aac76e Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:47:04 -0800 Subject: [PATCH 18/27] Revert "Utilities: typed" This reverts commit 55771cdd7242df71691aeafa71eba161baf323aa. --- frontend/src/utils/utils.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/utils/utils.tsx b/frontend/src/utils/utils.tsx index 820082c44..d202d2f56 100644 --- a/frontend/src/utils/utils.tsx +++ b/frontend/src/utils/utils.tsx @@ -2,8 +2,6 @@ * A bunch of utility functions */ -import { TelebearsType } from 'redux/enrollment/types'; - /** * Returns a paragraph tag styled with color with respect to percentage * @param {string} text text in the paragraph tag @@ -101,7 +99,7 @@ function getGradeColor(grade: string | undefined) { * TODO: remove the any's * */ -function getEnrollmentDay(selectedPoint: any, telebears: TelebearsType) { +function getEnrollmentDay(selectedPoint: any, telebears: any) { let period = ''; let daysAfterPeriodStarts = 0; if (selectedPoint.day < telebears.phase2_start_day) { From dca0e61595a70d2b36a4484205d6c2779146d729 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:47:07 -0800 Subject: [PATCH 19/27] Revert "Grades: typing IP" This reverts commit 2862eb2906f7555d0bd491112a94fd2e7c6fd2f3. --- .../src/app/Grades/{index.tsx => index.jsx} | 81 ++++++++----------- ...{GradesInfoCard.tsx => GradesInfoCard.jsx} | 23 +----- .../GradesInfoCard/GradesInfoCardMobile.jsx | 35 ++++++++ ...radesGraphCard.tsx => GradesGraphCard.jsx} | 81 +++++++------------ 4 files changed, 102 insertions(+), 118 deletions(-) rename frontend/src/app/Grades/{index.tsx => index.jsx} (64%) rename frontend/src/components/GradesInfoCard/{GradesInfoCard.tsx => GradesInfoCard.jsx} (87%) create mode 100644 frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx rename frontend/src/components/GraphCard/{GradesGraphCard.tsx => GradesGraphCard.jsx} (67%) diff --git a/frontend/src/app/Grades/index.tsx b/frontend/src/app/Grades/index.jsx similarity index 64% rename from frontend/src/app/Grades/index.tsx rename to frontend/src/app/Grades/index.jsx index 36e9ae217..694d0bf2c 100644 --- a/frontend/src/app/Grades/index.tsx +++ b/frontend/src/app/Grades/index.jsx @@ -1,43 +1,42 @@ import { useCallback, useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import ClassCardList from '../../components/ClassCards/ClassCardList'; -import GradesSearchBar from '../../components/ClassSearchBar/GradesSearchBar'; import GradesGraphCard from '../../components/GraphCard/GradesGraphCard'; +import GradesSearchBar from '../../components/ClassSearchBar/GradesSearchBar'; import info from '../../assets/img/images/graphs/info.svg'; import { - fetchGradeClass, fetchGradeContext, - fetchGradeFromUrl, + fetchGradeClass, gradeRemoveCourse, - gradeReset -} from 'redux/grades/actions'; -import { useReduxSelector } from 'redux/store'; -import { UnformattedCourseType } from 'redux/types'; + gradeReset, + fetchGradeFromUrl +} from '../../redux/actions'; -const toUrlForm = (string: string) => { - return string.replace('/', '_').toLowerCase().split(' ').join('-'); +const toUrlForm = (s) => { + s = s.replace('/', '_'); + return s.toLowerCase().split(' ').join('-'); }; export function Component() { - const [additionalInfo, setAdditionalInfo] = useState<[string, number, string, number][]>([]); + const [additionalInfo, setAdditionalInfo] = useState([]); const location = useLocation(); const navigate = useNavigate(); - const { context, selectedCourses, usedColorIds } = useReduxSelector((state) => state.grade); - const { mobile: isMobile } = useReduxSelector((state) => state.common); + const { context, selectedCourses, usedColorIds } = useSelector((state) => state.grade); + const { mobile: isMobile } = useSelector((state) => state.common); const dispatch = useDispatch(); const addToUrl = useCallback( - (course: UnformattedCourseType) => { - const instructor = toUrlForm(course.instructor); + (course) => { + let instructor = toUrlForm(course.instructor); - const courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( + let courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( course.semester )}-${instructor}`; @@ -55,8 +54,8 @@ export function Component() { ); const addCourse = useCallback( - (course: UnformattedCourseType) => { - for (const selected of selectedCourses) { + (course) => { + for (let selected of selectedCourses) { if (selected.id === course.id) { return; } @@ -78,24 +77,21 @@ export function Component() { ); const refillUrl = useCallback( - (id: string) => { - const updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); - const url = - '/grades/' + - [ - updatedCourses.map((course) => { - return `${course.colorId}-${course.courseID}-${toUrlForm(course.semester)}-${toUrlForm( - course.instructor - )}`; - }) - ].join('&'); + (id) => { + let updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); + let url = '/grades/'; + for (let i = 0; i < updatedCourses.length; i++) { + let c = updatedCourses[i]; + if (i !== 0) url += '&'; + url += `${c.colorId}-${c.courseID}-${toUrlForm(c.semester)}-${toUrlForm(c.instructor)}`; + } navigate(url, { replace: true }); }, [navigate, selectedCourses] ); const removeCourse = useCallback( - (id: string, color: string) => { + (id, color) => { refillUrl(id); dispatch(gradeRemoveCourse(id, color)); }, @@ -103,22 +99,11 @@ export function Component() { ); const updateClassCardGrade = useCallback( - ( - course_letter: string[], - course_gpa: number[], - section_letter: string[], - section_gpa: number[] - ) => { - const info = course_letter.map( - (_, i) => - [course_letter[i], course_gpa[i], section_letter[i], section_gpa[i]] as [ - string, - number, - string, - number - ] - ); - + (course_letter, course_gpa, section_letter, section_gpa) => { + var info = []; + for (var i = 0; i < course_letter.length; i++) { + info.push([course_letter[i], course_gpa[i], section_letter[i], section_gpa[i]]); + } setAdditionalInfo(info); }, [] @@ -127,7 +112,7 @@ export function Component() { useEffect(() => { const fillFromUrl = () => { try { - const url = location.pathname; + let url = location.pathname; if (url && (url === '/grades/' || url === '/grades')) { dispatch(gradeReset()); @@ -142,7 +127,7 @@ export function Component() { dispatch(fetchGradeContext()); dispatch(gradeReset()); fillFromUrl(); - }, [dispatch, location.pathname, navigate]); + }, []); return (
diff --git a/frontend/src/components/GradesInfoCard/GradesInfoCard.tsx b/frontend/src/components/GradesInfoCard/GradesInfoCard.jsx similarity index 87% rename from frontend/src/components/GradesInfoCard/GradesInfoCard.tsx rename to frontend/src/components/GradesInfoCard/GradesInfoCard.jsx index 6e811b92c..b4caf8246 100644 --- a/frontend/src/components/GradesInfoCard/GradesInfoCard.tsx +++ b/frontend/src/components/GradesInfoCard/GradesInfoCard.jsx @@ -4,11 +4,11 @@ import 'react-tooltip/dist/react-tooltip.css'; import { percentileToString, getGradeColor } from '../../utils/utils'; import info from '../../assets/img/images/graphs/info.svg'; -const courseAvgText = +var courseAvgText = 'Course average refers to the average of all
sections available across all instructors.
'; -const sectionAvgText = +var sectionAvgText = 'Section average refers to the average of all sections that
have been filtered for using the specified options.
'; -const percentileText = +var percentileText = 'Percentile refers to the percentile range out of students who
received a letter grade, while the count and percentage
also include students who received P/NP grades.
'; export default function GradesInfoCard({ @@ -24,23 +24,6 @@ export default function GradesInfoCard({ selectedGrade, denominator, color -}: { - course: string; - subtitle: string; - semester: string; - instructor: string; - courseLetter: string; - courseGPA: number; - sectionLetter: string; - sectionGPA: number; - selectedPercentiles: { - numerator: number; - percentile_low: number; - percentile_high: number; - }; - selectedGrade: string; - denominator: number; - color: string; }) { return (
diff --git a/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx b/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx new file mode 100644 index 000000000..9493d73fe --- /dev/null +++ b/frontend/src/components/GradesInfoCard/GradesInfoCardMobile.jsx @@ -0,0 +1,35 @@ +import GradesInfoCard from './GradesInfoCard'; + +export default function GradesInfoCardMobile({ + course, + subtitle, + semester, + instructor, + courseLetter, + courseGPA, + sectionLetter, + sectionGPA, + denominator, + betterGrade, + worseGrade, + color +}) { + const checkNullState = (item) => item || ' '; + + return ( + + ); +} diff --git a/frontend/src/components/GraphCard/GradesGraphCard.tsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx similarity index 67% rename from frontend/src/components/GraphCard/GradesGraphCard.tsx rename to frontend/src/components/GraphCard/GradesGraphCard.jsx index a3192719d..5ce7e5f05 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.tsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -1,35 +1,18 @@ -import { HTMLAttributes, useCallback, useEffect, useMemo, useState } from 'react'; -import { Col, Container, Row } from 'react-bootstrap'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Container, Row, Col } from 'react-bootstrap'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import vars from '../../utils/variables'; -import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; import GradesGraph from '../Graphs/GradesGraph'; import GraphEmpty from '../Graphs/GraphEmpty'; +import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; + +import { fetchGradeData } from '../../redux/actions'; -import { fetchGradeData } from 'redux/grades/actions'; -import { GRADE, GradesDataType } from 'redux/grades/types'; -import { useReduxSelector } from 'redux/store'; -import { FormattedCourseType } from 'redux/types'; - -export default function GradesGraphCard({ - isMobile, - updateClassCardGrade, - ...rest -}: { - isMobile: boolean; - updateClassCardGrade: ( - course_letter: string[], - course_gpa: number[], - section_letter: string[], - section_gpa: number[] - ) => void; -} & HTMLAttributes) { - const { gradesData, graphData, selectedCourses } = useReduxSelector((state) => state.grade); - const [hoveredClass, setHoveredClass] = useState< - FormattedCourseType & GradesDataType & { hoverGrade: GRADE } - >(); +export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { + const { gradesData, graphData, selectedCourses } = useSelector((state) => state.grade); + const [hoveredClass, setHoveredClass] = useState(false); const [updateMobileHover, setUpdateMobileHover] = useState(true); const dispatch = useDispatch(); @@ -38,7 +21,7 @@ export default function GradesGraphCard({ }, [selectedCourses, dispatch]); const update = useCallback( - (course: FormattedCourseType, grade: GRADE) => { + (course, grade) => { if (!course || !gradesData || gradesData.length === 0) return; const selectedGrades = gradesData.filter((c) => course.id === c.id)[0]; @@ -55,7 +38,7 @@ export default function GradesGraphCard({ useEffect(() => { if (gradesData.length > 0 && selectedCourses.length === 1) { - update(selectedCourses[0], GRADE.P); + update(selectedCourses[0], 0); } const course_letter = gradesData.map((course) => course.course_letter); @@ -67,7 +50,7 @@ export default function GradesGraphCard({ // Handler function for updating GradesInfoCard on hover const updateBarHover = useCallback( - (barData: { payload: Record; name: GRADE; value: number }) => { + (barData) => { const { payload, name, value } = barData; let selectedClassID = ''; @@ -88,7 +71,7 @@ export default function GradesGraphCard({ // Handler function for updating GradesInfoCard on hover with single course const updateGraphHover = useCallback( - (data: { isTooltipActive: boolean; activeLabel: GRADE }) => { + (data) => { const { isTooltipActive, activeLabel } = data; const noBarMobile = updateMobileHover && isMobile; @@ -112,7 +95,7 @@ export default function GradesGraphCard({ ); return ( -
+
{isMobile &&
Grade Distribution
} - {hoveredClass && ( - - )} + {graphEmpty && ( @@ -172,7 +153,7 @@ export default function GradesGraphCard({ denominator={hoveredClass.denominator} selectedPercentiles={hoveredClass[hoveredClass.hoverGrade]} selectedGrade={hoveredClass.hoverGrade} - color={vars.colors[parseInt(hoveredClass.colorId)]} + color={vars.colors[hoveredClass.colorId]} /> )} From f830dcd0cbcf2f0d95b5b6cd2454323054be2b41 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:47:09 -0800 Subject: [PATCH 20/27] Revert "Enrollment: typing IP" This reverts commit c27d53c6d165ccc58e3c54b9bf2c4bc39ff2b8de. --- .../app/Enrollment/{index.tsx => index.jsx} | 75 ++++++++----------- ...entInfoCard.tsx => EnrollmentInfoCard.jsx} | 16 ---- ...tGraphCard.tsx => EnrollmentGraphCard.jsx} | 67 +++++++---------- 3 files changed, 57 insertions(+), 101 deletions(-) rename frontend/src/app/Enrollment/{index.tsx => index.jsx} (64%) rename frontend/src/components/EnrollmentInfoCard/{EnrollmentInfoCard.tsx => EnrollmentInfoCard.jsx} (79%) rename frontend/src/components/GraphCard/{EnrollmentGraphCard.tsx => EnrollmentGraphCard.jsx} (68%) diff --git a/frontend/src/app/Enrollment/index.tsx b/frontend/src/app/Enrollment/index.jsx similarity index 64% rename from frontend/src/app/Enrollment/index.tsx rename to frontend/src/app/Enrollment/index.jsx index 22045102d..1dcafff05 100644 --- a/frontend/src/app/Enrollment/index.tsx +++ b/frontend/src/app/Enrollment/index.jsx @@ -1,35 +1,30 @@ import { useLocation, useNavigate } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import ClassCardList from '../../components/ClassCards/ClassCardList'; -import EnrollmentSearchBar from '../../components/ClassSearchBar/EnrollmentSearchBar.jsx'; import EnrollmentGraphCard from '../../components/GraphCard/EnrollmentGraphCard'; +import EnrollmentSearchBar from '../../components/ClassSearchBar/EnrollmentSearchBar'; import info from '../../assets/img/images/graphs/info.svg'; -import { useCallback, useEffect, useState } from 'react'; -import type { UnformattedCourseType } from 'redux/types'; -import { useReduxSelector } from 'redux/store'; import { + fetchEnrollContext, + fetchEnrollClass, enrollRemoveCourse, enrollReset, - fetchEnrollClass, - fetchEnrollContext, fetchEnrollFromUrl -} from '../../redux/enrollment/actions'; -import { EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; +} from '../../redux/actions'; +import { useCallback, useEffect, useState } from 'react'; -const toUrlForm = (string: string) => { - return string.toLowerCase().split(' ').join('-'); +const toUrlForm = (s) => { + return s.toLowerCase().split(' ').join('-'); }; export function Component() { - const [additionalInfo, setAdditionalInfo] = useState< - [EnrollmentStatusType, TelebearsType, number[], number[]][] - >([]); + const [additionalInfo, setAdditionalInfo] = useState([]); - const { context, selectedCourses, usedColorIds } = useReduxSelector((state) => state.enrollment); - const { mobile: isMobile } = useReduxSelector((state) => state.common); + const { context, selectedCourses, usedColorIds } = useSelector((state) => state.enrollment); + const { mobile: isMobile } = useSelector((state) => state.common); const dispatch = useDispatch(); const location = useLocation(); @@ -38,7 +33,7 @@ export function Component() { useEffect(() => { const fillFromUrl = () => { try { - const url = location.pathname; + let url = location.pathname; if (url && (url === '/enrollment/' || url === '/enrollment')) { dispatch(enrollReset()); @@ -53,13 +48,13 @@ export function Component() { dispatch(fetchEnrollContext()); dispatch(enrollReset()); fillFromUrl(); - }, [dispatch, location.pathname, navigate]); + }, []); const addToUrl = useCallback( - (course: UnformattedCourseType) => { - const instructor = course.instructor === 'all' ? 'all' : course.sections[0]; + (course) => { + let instructor = course.instructor === 'all' ? 'all' : course.sections[0]; - const courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( + let courseUrl = `${course.colorId}-${course.courseID}-${toUrlForm( course.semester )}-${instructor}`; @@ -77,8 +72,8 @@ export function Component() { ); const addCourse = useCallback( - (course: UnformattedCourseType) => { - for (const selected of selectedCourses) { + (course) => { + for (let selected of selectedCourses) { if (selected.id === course.id) { return; } @@ -102,16 +97,16 @@ export function Component() { ); const refillUrl = useCallback( - (id: string) => { - const updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); + (id) => { + let updatedCourses = selectedCourses.filter((classInfo) => classInfo.id !== id); let url = '/enrollment/'; for (let i = 0; i < updatedCourses.length; i++) { - const course = updatedCourses[i]; + let c = updatedCourses[i]; if (i !== 0) url += '&'; - const instructor = course.instructor === 'all' ? 'all' : course.sections[0]; - url += `${course.colorId}-${course.courseID}-${toUrlForm(course.semester)}-${instructor}`; + let instructor = c.instructor === 'all' ? 'all' : c.sections[0]; + url += `${c.colorId}-${c.courseID}-${toUrlForm(c.semester)}-${instructor}`; } navigate(url, { replace: true }); @@ -120,7 +115,7 @@ export function Component() { ); const removeCourse = useCallback( - (id: string, color: string) => { + (id, color) => { refillUrl(id); dispatch(enrollRemoveCourse(id, color)); }, @@ -128,21 +123,12 @@ export function Component() { ); const updateClassCardEnrollment = useCallback( - ( - latest_point: EnrollmentStatusType[], - telebears: TelebearsType[], - enrolled_info: number[][], - waitlisted_info: number[][] - ) => { - const info = latest_point.map( - (_, i) => - [latest_point[i], telebears[i], enrolled_info[i], waitlisted_info[i]] as [ - EnrollmentStatusType, - TelebearsType, - number[], - number[] - ] - ); + (latest_point, telebears, enrolled_info, waitlisted_info) => { + var info = []; + + for (var i = 0; i < latest_point.length; i++) { + info.push([latest_point[i], telebears[i], enrolled_info[i], waitlisted_info[i]]); + } setAdditionalInfo(info); }, @@ -158,7 +144,6 @@ export function Component() { fromCatalog={location.state ? location.state.course : false} isFull={selectedCourses.length === 4} isMobile={isMobile} - sectionNumber={'qwq'} /> getEnrollmentDay(selectedPoint, telebears), diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx similarity index 68% rename from frontend/src/components/GraphCard/EnrollmentGraphCard.tsx rename to frontend/src/components/GraphCard/EnrollmentGraphCard.jsx index f1c925bde..6fa4a9563 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.tsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx @@ -1,37 +1,18 @@ -import { HTMLAttributes, useCallback, useEffect, useMemo, useState } from 'react'; -import { Col, Container, Row } from 'react-bootstrap'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Container, Row, Col } from 'react-bootstrap'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import vars from '../../utils/variables'; -import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; import EnrollmentGraph from '../Graphs/EnrollmentGraph.jsx'; import GraphEmpty from '../Graphs/GraphEmpty.jsx'; +import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; -import { EnrollmentDataType, EnrollmentStatusType, TelebearsType } from 'redux/enrollment/types'; -import { useReduxSelector } from 'redux/store'; -import { fetchEnrollData } from '../../redux/enrollment/actions'; -import { FormattedCourseType } from 'redux/types'; - -export default function EnrollmentGraphCard({ - isMobile, - updateClassCardEnrollment, - ...rest -}: { - isMobile: boolean; - updateClassCardEnrollment: ( - latest_point: EnrollmentStatusType[], - telebears: TelebearsType[], - enrolled_info: number[][], - waitlisted_info: number[][] - ) => void; -} & HTMLAttributes) { - const [hoveredClass, setHoveredClass] = useState< - FormattedCourseType & EnrollmentDataType & { hoverDay: number } - >(); - const { enrollmentData, graphData, selectedCourses } = useReduxSelector( - (state) => state.enrollment - ); +import { fetchEnrollData } from '../../redux/actions'; + +export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollment }) { + const [hoveredClass, setHoveredClass] = useState(false); + const { enrollmentData, graphData, selectedCourses } = useSelector((state) => state.enrollment); const dispatch = useDispatch(); useEffect(() => { @@ -39,7 +20,7 @@ export default function EnrollmentGraphCard({ }, [selectedCourses, dispatch]); const update = useCallback( - (course: FormattedCourseType, day: number) => { + (course, day) => { if (!course || !enrollmentData || enrollmentData.length === 0) return; const selectedEnrollment = enrollmentData.filter((c) => course.id === c.id)[0]; @@ -64,9 +45,7 @@ export default function EnrollmentGraphCard({ update(selectedCourses[0], 1); } - const latest_point: EnrollmentStatusType[] = enrollmentData.map( - (course) => course.data[course.data.length - 1] - ); + const latest_point = enrollmentData.map((course) => course.data[course.data.length - 1]); const telebears = enrollmentData.map((course) => course.telebears); const enrolled_info = latest_point.map((course) => [ course.enrolled, @@ -84,25 +63,33 @@ export default function EnrollmentGraphCard({ // Handler function for updating EnrollmentInfoCard on hover const updateLineHover = useCallback( - ({ dataKey: selectedClassID, index: day }: { dataKey: string; index: number }) => { - update(selectedCourses.filter((course) => selectedClassID === course.id)[0], day); + (lineData) => { + const selectedClassID = lineData.dataKey; + const day = lineData.index; + const selectedCourse = selectedCourses.filter((course) => selectedClassID === course.id)[0]; + update(selectedCourse, day); }, [selectedCourses, update] ); // Handler function for updating EnrollmentInfoCard on hover with single course const updateGraphHover = useCallback( - ({ isTooltipActive, activeLabel: day }: { isTooltipActive: boolean; activeLabel: number }) => { + (data) => { + const { isTooltipActive, activeLabel } = data; + if (!isTooltipActive || selectedCourses.length !== 1) return; const selectedCourse = selectedCourses[0]; - + const day = activeLabel; update(selectedCourse, day); }, [selectedCourses, update] ); - const telebears = useMemo(() => enrollmentData[0].telebears, [enrollmentData]); + const telebears = useMemo( + () => (enrollmentData.length ? enrollmentData[0].telebears : {}), + [enrollmentData] + ); const graphEmpty = useMemo( () => enrollmentData.length === 0 || selectedCourses.length === 0, @@ -110,7 +97,7 @@ export default function EnrollmentGraphCard({ ); return ( -
+
{hoveredClass && ( pt.day === hoveredClass.hoverDay)[0] } + todayPoint={hoveredClass.data[hoveredClass.data.length - 1]} telebears={telebears} - color={vars.colors[parseInt(hoveredClass.colorId)]} + color={vars.colors[hoveredClass.colorId]} enrolledMax={hoveredClass.enrolled_max} waitlistedMax={hoveredClass.waitlisted_max} /> From fdc80d42bc8baf26ce21ed883cd1401e7a5a00bb Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 18:53:39 -0800 Subject: [PATCH 21/27] fixing redux-related imports --- frontend/src/Berkeleytime.tsx | 8 +++--- frontend/src/app/Enrollment/index.jsx | 25 ++++++++----------- frontend/src/app/Grades/index.jsx | 23 ++++++++--------- .../ClassSearchBar/EnrollmentSearchBar.jsx | 8 +++--- .../ClassSearchBar/GradesSearchBar.jsx | 10 +++----- frontend/src/components/Common/Banner.tsx | 7 +++--- .../GraphCard/EnrollmentGraphCard.jsx | 9 +++---- .../components/GraphCard/GradesGraphCard.jsx | 9 +++---- .../src/components/Landing/LandingModal.tsx | 11 ++++---- 9 files changed, 47 insertions(+), 63 deletions(-) diff --git a/frontend/src/Berkeleytime.tsx b/frontend/src/Berkeleytime.tsx index 9d6f45d6a..77c55a815 100644 --- a/frontend/src/Berkeleytime.tsx +++ b/frontend/src/Berkeleytime.tsx @@ -1,11 +1,11 @@ +import { IconoirProvider } from 'iconoir-react'; import { memo, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; -import { openBanner, enterMobile, exitMobile, openLandingModal } from './redux/common/actions'; import useDimensions from 'react-cool-dimensions'; +import { useDispatch } from 'react-redux'; +import { fetchEnrollContext } from 'redux/enrollment/actions'; import easterEgg from 'utils/easterEgg'; import Routes from './Routes'; -import { fetchEnrollContext } from 'redux/actions'; -import { IconoirProvider } from 'iconoir-react'; +import { enterMobile, exitMobile, openBanner, openLandingModal } from './redux/common/actions'; const Berkeleytime = () => { const dispatch = useDispatch(); diff --git a/frontend/src/app/Enrollment/index.jsx b/frontend/src/app/Enrollment/index.jsx index 1dcafff05..13dda0d84 100644 --- a/frontend/src/app/Enrollment/index.jsx +++ b/frontend/src/app/Enrollment/index.jsx @@ -1,20 +1,17 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - +import { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + enrollRemoveCourse, + enrollReset, + fetchEnrollClass, + fetchEnrollContext, + fetchEnrollFromUrl +} from 'redux/enrollment/actions'; +import info from '../../assets/img/images/graphs/info.svg'; import ClassCardList from '../../components/ClassCards/ClassCardList'; -import EnrollmentGraphCard from '../../components/GraphCard/EnrollmentGraphCard'; import EnrollmentSearchBar from '../../components/ClassSearchBar/EnrollmentSearchBar'; - -import info from '../../assets/img/images/graphs/info.svg'; - -import { - fetchEnrollContext, - fetchEnrollClass, - enrollRemoveCourse, - enrollReset, - fetchEnrollFromUrl -} from '../../redux/actions'; -import { useCallback, useEffect, useState } from 'react'; +import EnrollmentGraphCard from '../../components/GraphCard/EnrollmentGraphCard'; const toUrlForm = (s) => { return s.toLowerCase().split(' ').join('-'); diff --git a/frontend/src/app/Grades/index.jsx b/frontend/src/app/Grades/index.jsx index 694d0bf2c..fdfadfa56 100644 --- a/frontend/src/app/Grades/index.jsx +++ b/frontend/src/app/Grades/index.jsx @@ -1,20 +1,17 @@ import { useCallback, useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; - import { useDispatch, useSelector } from 'react-redux'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + fetchGradeClass, + fetchGradeContext, + fetchGradeFromUrl, + gradeRemoveCourse, + gradeReset +} from 'redux/grades/actions'; +import info from '../../assets/img/images/graphs/info.svg'; import ClassCardList from '../../components/ClassCards/ClassCardList'; -import GradesGraphCard from '../../components/GraphCard/GradesGraphCard'; import GradesSearchBar from '../../components/ClassSearchBar/GradesSearchBar'; - -import info from '../../assets/img/images/graphs/info.svg'; - -import { - fetchGradeContext, - fetchGradeClass, - gradeRemoveCourse, - gradeReset, - fetchGradeFromUrl -} from '../../redux/actions'; +import GradesGraphCard from '../../components/GraphCard/GradesGraphCard'; const toUrlForm = (s) => { s = s.replace('/', '_'); diff --git a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx index f320e166d..41054c99e 100644 --- a/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/EnrollmentSearchBar.jsx @@ -1,10 +1,10 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col, Button } from 'react-bootstrap'; import hash from 'object-hash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Button, Col, Container, Row } from 'react-bootstrap'; -import { fetchEnrollSelected } from '../../redux/actions'; -import { useDispatch, useSelector } from 'react-redux'; import BTSelect from 'components/Custom/Select'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchEnrollSelected } from 'redux/enrollment/actions'; const buildCoursesOptions = (courses) => { if (!courses) { diff --git a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx index 39038bedc..f1bc58706 100644 --- a/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx +++ b/frontend/src/components/ClassSearchBar/GradesSearchBar.jsx @@ -1,11 +1,9 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col, Button } from 'react-bootstrap'; +import BTSelect from 'components/Custom/Select'; import hash from 'object-hash'; - +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Button, Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; - -import { fetchGradeSelected } from '../../redux/actions'; -import BTSelect from 'components/Custom/Select'; +import { fetchGradeSelected } from 'redux/grades/actions'; const sortOptions = [ { value: 'instructor', label: 'By Instructor' }, diff --git a/frontend/src/components/Common/Banner.tsx b/frontend/src/components/Common/Banner.tsx index 61295a1f4..8d4508f3d 100644 --- a/frontend/src/components/Common/Banner.tsx +++ b/frontend/src/components/Common/Banner.tsx @@ -1,9 +1,8 @@ -import { useSelector, useDispatch } from 'react-redux'; import { Button } from 'bt/custom'; -import { closeBanner } from '../../redux/common/actions'; - -import close from '../../assets/svg/common/close.svg'; +import { useDispatch, useSelector } from 'react-redux'; +import { closeBanner } from 'redux/common/actions'; import { ReduxState } from 'redux/store'; +import close from '../../assets/svg/common/close.svg'; export default function Banner() { const { banner } = useSelector((state: ReduxState) => state.common); diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx index 6fa4a9563..6178865a1 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx @@ -1,14 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col } from 'react-bootstrap'; - +import { Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; +import { fetchEnrollData } from 'redux/enrollment/actions'; import vars from '../../utils/variables'; - +import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; import EnrollmentGraph from '../Graphs/EnrollmentGraph.jsx'; import GraphEmpty from '../Graphs/GraphEmpty.jsx'; -import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; - -import { fetchEnrollData } from '../../redux/actions'; export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollment }) { const [hoveredClass, setHoveredClass] = useState(false); diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx index 5ce7e5f05..e46675354 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -1,14 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Container, Row, Col } from 'react-bootstrap'; - +import { Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; +import { fetchGradeData } from 'redux/grades/actions'; import vars from '../../utils/variables'; - +import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; import GradesGraph from '../Graphs/GradesGraph'; import GraphEmpty from '../Graphs/GraphEmpty'; -import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; - -import { fetchGradeData } from '../../redux/actions'; export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { const { gradesData, graphData, selectedCourses } = useSelector((state) => state.grade); diff --git a/frontend/src/components/Landing/LandingModal.tsx b/frontend/src/components/Landing/LandingModal.tsx index 5ac92185a..ee1fd57d1 100644 --- a/frontend/src/components/Landing/LandingModal.tsx +++ b/frontend/src/components/Landing/LandingModal.tsx @@ -1,12 +1,11 @@ +import closeIcon from 'assets/svg/common/close.svg'; +import { Button, H3, P } from 'bt/custom'; import { useCallback, useEffect } from 'react'; import { Modal } from 'react-bootstrap'; -import { H3, P, Button } from 'bt/custom'; -import closeIcon from 'assets/svg/common/close.svg'; -import schedulerImg from '../../assets/img/landing/scheduler.png'; - import { useDispatch, useSelector } from 'react-redux'; -import { ReduxState } from '../../redux/store'; -import { closeLandingModal } from '../../redux/common/actions'; +import { closeLandingModal } from 'redux/common/actions'; +import { ReduxState } from 'redux/store'; +import schedulerImg from '../../assets/img/landing/scheduler.png'; const modal_info = { subtitle: 'NEW!', From 71d7a63e9716446f7095127427c0b11368756ea5 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 19:02:57 -0800 Subject: [PATCH 22/27] removed unused variables --- .../components/ClassCards/ClassCardList.jsx | 5 +- .../GraphCard/EnrollmentGraphCard.jsx | 4 +- .../components/GraphCard/GradesGraphCard.jsx | 6 +- .../src/components/Graphs/EnrollmentGraph.jsx | 23 +- .../src/components/Graphs/GradesGraph.jsx | 11 +- frontend/src/utils/colors.ts | 1 + frontend/src/utils/variables.js | 638 ------------------ 7 files changed, 24 insertions(+), 664 deletions(-) create mode 100644 frontend/src/utils/colors.ts delete mode 100644 frontend/src/utils/variables.js diff --git a/frontend/src/components/ClassCards/ClassCardList.jsx b/frontend/src/components/ClassCards/ClassCardList.jsx index fc85a09ea..20b782e63 100644 --- a/frontend/src/components/ClassCards/ClassCardList.jsx +++ b/frontend/src/components/ClassCards/ClassCardList.jsx @@ -1,7 +1,6 @@ import { Container, Row } from 'react-bootstrap'; - +import colors from 'utils/colors'; import ClassCard from './ClassCard'; -import vars from '../../utils/variables'; export default function ClassCardList({ selectedCourses, @@ -19,7 +18,7 @@ export default function ClassCardList({ id={item.id} course={item.course} title={item.title} - fill={vars.colors[item.colorId]} + fill={colors[item.colorId]} semester={item.semester === 'all' ? 'All Semesters' : item.semester} faculty={item.instructor === 'all' ? 'All Instructors' : item.instructor} removeCourse={removeCourse} diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx index 6178865a1..1438d183c 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; import { fetchEnrollData } from 'redux/enrollment/actions'; -import vars from '../../utils/variables'; +import colors from 'utils/colors.js'; import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; import EnrollmentGraph from '../Graphs/EnrollmentGraph.jsx'; import GraphEmpty from '../Graphs/GraphEmpty.jsx'; @@ -141,7 +141,7 @@ export default function EnrollmentGraphCard({ isMobile, updateClassCardEnrollmen } todayPoint={hoveredClass.data[hoveredClass.data.length - 1]} telebears={telebears} - color={vars.colors[hoveredClass.colorId]} + color={colors[hoveredClass.colorId]} enrolledMax={hoveredClass.enrolled_max} waitlistedMax={hoveredClass.waitlisted_max} /> diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx index e46675354..41fc6b2f8 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; import { fetchGradeData } from 'redux/grades/actions'; -import vars from '../../utils/variables'; +import colors from 'utils/colors'; import GradesInfoCard from '../GradesInfoCard/GradesInfoCard'; import GradesGraph from '../Graphs/GradesGraph'; import GraphEmpty from '../Graphs/GraphEmpty'; @@ -114,7 +114,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { } selectedPercentiles={hoveredClass[hoveredClass.hoverGrade]} denominator={hoveredClass.denominator} - color={vars.colors[hoveredClass.colorId]} + color={colors[hoveredClass.colorId]} isMobile={isMobile} graphEmpty={graphEmpty} /> @@ -150,7 +150,7 @@ export default function GradesGraphCard({ isMobile, updateClassCardGrade }) { denominator={hoveredClass.denominator} selectedPercentiles={hoveredClass[hoveredClass.hoverGrade]} selectedGrade={hoveredClass.hoverGrade} - color={vars.colors[hoveredClass.colorId]} + color={colors[hoveredClass.colorId]} /> )} diff --git a/frontend/src/components/Graphs/EnrollmentGraph.jsx b/frontend/src/components/Graphs/EnrollmentGraph.jsx index 88bf20fed..8494135d4 100644 --- a/frontend/src/components/Graphs/EnrollmentGraph.jsx +++ b/frontend/src/components/Graphs/EnrollmentGraph.jsx @@ -1,16 +1,15 @@ import { - LineChart, - XAxis, - YAxis, - Tooltip, - Line, - Legend, - ReferenceLine, - Label, - ResponsiveContainer + Label, + Legend, + Line, + LineChart, + ReferenceLine, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis } from 'recharts'; - -import vars from '../../utils/variables'; +import colors from 'utils/colors'; import emptyImage from '../../assets/img/images/graphs/empty.svg'; const EmptyLabel = (props) => { @@ -95,7 +94,7 @@ export default function EnrollmentGraph({ name={`${item.title} • ${item.section_name}`} type="monotone" dataKey={item.id} - stroke={vars.colors[item.colorId]} + stroke={colors[item.colorId]} strokeWidth={3} dot={false} activeDot={{ onMouseOver: updateLineHover }} diff --git a/frontend/src/components/Graphs/GradesGraph.jsx b/frontend/src/components/Graphs/GradesGraph.jsx index eb9d6c8db..4f7c25ccd 100644 --- a/frontend/src/components/Graphs/GradesGraph.jsx +++ b/frontend/src/components/Graphs/GradesGraph.jsx @@ -1,8 +1,7 @@ -import { BarChart, Bar, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts'; -import { percentileToString } from '../../utils/utils'; - -import vars from '../../utils/variables'; +import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +import colors from 'utils/colors'; import emptyImage from '../../assets/img/images/graphs/empty.svg'; +import { percentileToString } from '../../utils/utils'; const EmptyLabel = (props) => { return ( @@ -99,7 +98,7 @@ export default function GradesGraph({ key={i} name={`${item.title} • ${item.semester} • ${item.instructor}`} dataKey={item.id} - fill={vars.colors[item.colorId]} + fill={colors[item.colorId]} onMouseEnter={updateBarHover} radius={[4, 4, 0, 0]} /> @@ -143,7 +142,7 @@ export default function GradesGraph({ key={i} name={`${item.title} • ${item.semester} • ${item.instructor}`} dataKey={item.id} - fill={vars.colors[item.colorId]} + fill={colors[item.colorId]} onMouseEnter={updateBarHover} label={} radius={[0, 4, 4, 0]} diff --git a/frontend/src/utils/colors.ts b/frontend/src/utils/colors.ts new file mode 100644 index 000000000..fe760068c --- /dev/null +++ b/frontend/src/utils/colors.ts @@ -0,0 +1 @@ +export default ['#4EA6FB', '#6AE086', '#ED5186', '#F9E152']; diff --git a/frontend/src/utils/variables.js b/frontend/src/utils/variables.js deleted file mode 100644 index a10c3b2a4..000000000 --- a/frontend/src/utils/variables.js +++ /dev/null @@ -1,638 +0,0 @@ -// -// // -// // // For notifications -// // -// -var defaultWidth = window.screen.width > 768 ? (window.screen.width * 1) / 3 : window.screen.width; - -var style = { - Wrapper: {}, - Containers: { - DefaultStyle: { - position: 'fixed', - width: defaultWidth, - padding: '10px 10px 10px 20px', - zIndex: 9998, - WebkitBoxSizing: '', - MozBoxSizing: '', - boxSizing: '', - height: 'auto', - display: 'inline-block', - border: '0', - fontSize: '14px', - WebkitFontSmoothing: 'antialiased', - fontFamily: '"Roboto","Helvetica Neue",Arial,sans-serif', - fontWeight: '400', - color: '#FFFFFF' - }, - - tl: { - top: '0px', - bottom: 'auto', - left: '0px', - right: 'auto' - }, - - tr: { - top: '0px', - bottom: 'auto', - left: 'auto', - right: '0px' - }, - - tc: { - top: '0px', - bottom: 'auto', - margin: '0 auto', - left: '50%', - marginLeft: -(defaultWidth / 2) - }, - - bl: { - top: 'auto', - bottom: '0px', - left: '0px', - right: 'auto' - }, - - br: { - top: 'auto', - bottom: '0px', - left: 'auto', - right: '0px' - }, - - bc: { - top: 'auto', - bottom: '0px', - margin: '0 auto', - left: '50%', - marginLeft: -(defaultWidth / 2) - } - }, - - NotificationItem: { - DefaultStyle: { - position: 'relative', - width: '100%', - cursor: 'pointer', - borderRadius: '4px', - fontSize: '14px', - margin: '10px 0 0', - padding: '10px', - display: 'block', - WebkitBoxSizing: 'border-box', - MozBoxSizing: 'border-box', - boxSizing: 'border-box', - opacity: 0, - transition: 'all 0.5s ease-in-out', - WebkitTransform: 'translate3d(0, 0, 0)', - transform: 'translate3d(0, 0, 0)', - willChange: 'transform, opacity', - - isHidden: { - opacity: 0 - }, - - isVisible: { - opacity: 1 - } - }, - - success: { - borderTop: 0, - backgroundColor: '#a1e82c', - WebkitBoxShadow: 0, - MozBoxShadow: 0, - boxShadow: 0 - }, - - error: { - borderTop: 0, - backgroundColor: '#fc727a', - WebkitBoxShadow: 0, - MozBoxShadow: 0, - boxShadow: 0 - }, - - warning: { - borderTop: 0, - backgroundColor: '#ffbc67', - WebkitBoxShadow: 0, - MozBoxShadow: 0, - boxShadow: 0 - }, - - info: { - borderTop: 0, - backgroundColor: '#63d8f1', - WebkitBoxShadow: 0, - MozBoxShadow: 0, - boxShadow: 0 - } - }, - - Title: { - DefaultStyle: { - fontSize: '30px', - margin: '0', - padding: 0, - fontWeight: 'bold', - color: '#FFFFFF', - display: 'block', - left: '15px', - position: 'absolute', - top: '50%', - marginTop: '-15px' - } - }, - - MessageWrapper: { - DefaultStyle: { - marginLeft: '55px', - marginRight: '30px', - padding: '0 12px 0 0', - color: '#FFFFFF', - maxWidthwidth: '89%' - } - }, - - Dismiss: { - DefaultStyle: { - fontFamily: 'inherit', - fontSize: '21px', - color: '#000', - float: 'right', - position: 'absolute', - right: '10px', - top: '50%', - marginTop: '-13px', - backgroundColor: '#FFFFFF', - display: 'block', - borderRadius: '50%', - opacity: '.4', - lineHeight: '11px', - width: '25px', - height: '25px', - outline: '0 !important', - textAlign: 'center', - padding: '6px 3px 3px 3px', - fontWeight: '300', - marginLeft: '65px' - }, - - success: { - // color: '#f0f5ea', - // backgroundColor: '#a1e82c' - }, - - error: { - // color: '#f4e9e9', - // backgroundColor: '#fc727a' - }, - - warning: { - // color: '#f9f6f0', - // backgroundColor: '#ffbc67' - }, - - info: { - // color: '#e8f0f4', - // backgroundColor: '#63d8f1' - } - }, - - Action: { - DefaultStyle: { - background: '#ffffff', - borderRadius: '2px', - padding: '6px 20px', - fontWeight: 'bold', - margin: '10px 0 0 0', - border: 0 - }, - - success: { - backgroundColor: '#a1e82c', - color: '#ffffff' - }, - - error: { - backgroundColor: '#fc727a', - color: '#ffffff' - }, - - warning: { - backgroundColor: '#ffbc67', - color: '#ffffff' - }, - - info: { - backgroundColor: '#63d8f1', - color: '#ffffff' - } - }, - - ActionWrapper: { - DefaultStyle: { - margin: 0, - padding: 0 - } - } -}; - -// -// // -// // // For tables -// // -// -const thArray = ['ID', 'Name', 'Salary', 'Country', 'City']; -const tdArray = [ - ['1', 'Dakota Rice', '$36,738', 'Niger', 'Oud-Turnhout'], - ['2', 'Minerva Hooper', '$23,789', 'Curaçao', 'Sinaai-Waas'], - ['3', 'Sage Rodriguez', '$56,142', 'Netherlands', 'Baileux'], - ['4', 'Philip Chaney', '$38,735', 'Korea, South', 'Overland Park'], - ['5', 'Doris Greene', '$63,542', 'Malawi', 'Feldkirchen in Kärnten'], - ['6', 'Mason Porter', '$78,615', 'Chile', 'Gloucester'] -]; - -// -// // -// // // For icons -// // -// -const iconsArray = [ - 'pe-7s-album', - 'pe-7s-arc', - 'pe-7s-back-2', - 'pe-7s-bandaid', - 'pe-7s-car', - 'pe-7s-diamond', - 'pe-7s-door-lock', - 'pe-7s-eyedropper', - 'pe-7s-female', - 'pe-7s-gym', - 'pe-7s-hammer', - 'pe-7s-headphones', - 'pe-7s-helm', - 'pe-7s-hourglass', - 'pe-7s-leaf', - 'pe-7s-magic-wand', - 'pe-7s-male', - 'pe-7s-map-2', - 'pe-7s-next-2', - 'pe-7s-paint-bucket', - 'pe-7s-pendrive', - 'pe-7s-photo', - 'pe-7s-piggy', - 'pe-7s-plugin', - 'pe-7s-refresh-2', - 'pe-7s-rocket', - 'pe-7s-settings', - 'pe-7s-shield', - 'pe-7s-smile', - 'pe-7s-usb', - 'pe-7s-vector', - 'pe-7s-wine', - 'pe-7s-cloud-upload', - 'pe-7s-cash', - 'pe-7s-close', - 'pe-7s-bluetooth', - 'pe-7s-cloud-download', - 'pe-7s-way', - 'pe-7s-close-circle', - 'pe-7s-id', - 'pe-7s-angle-up', - 'pe-7s-wristwatch', - 'pe-7s-angle-up-circle', - 'pe-7s-world', - 'pe-7s-angle-right', - 'pe-7s-volume', - 'pe-7s-angle-right-circle', - 'pe-7s-users', - 'pe-7s-angle-left', - 'pe-7s-user-female', - 'pe-7s-angle-left-circle', - 'pe-7s-up-arrow', - 'pe-7s-angle-down', - 'pe-7s-switch', - 'pe-7s-angle-down-circle', - 'pe-7s-scissors', - 'pe-7s-wallet', - 'pe-7s-safe', - 'pe-7s-volume2', - 'pe-7s-volume1', - 'pe-7s-voicemail', - 'pe-7s-video', - 'pe-7s-user', - 'pe-7s-upload', - 'pe-7s-unlock', - 'pe-7s-umbrella', - 'pe-7s-trash', - 'pe-7s-tools', - 'pe-7s-timer', - 'pe-7s-ticket', - 'pe-7s-target', - 'pe-7s-sun', - 'pe-7s-study', - 'pe-7s-stopwatch', - 'pe-7s-star', - 'pe-7s-speaker', - 'pe-7s-signal', - 'pe-7s-shuffle', - 'pe-7s-shopbag', - 'pe-7s-share', - 'pe-7s-server', - 'pe-7s-search', - 'pe-7s-film', - 'pe-7s-science', - 'pe-7s-disk', - 'pe-7s-ribbon', - 'pe-7s-repeat', - 'pe-7s-refresh', - 'pe-7s-add-user', - 'pe-7s-refresh-cloud', - 'pe-7s-paperclip', - 'pe-7s-radio', - 'pe-7s-note2', - 'pe-7s-print', - 'pe-7s-network', - 'pe-7s-prev', - 'pe-7s-mute', - 'pe-7s-power', - 'pe-7s-medal', - 'pe-7s-portfolio', - 'pe-7s-like2', - 'pe-7s-plus', - 'pe-7s-left-arrow', - 'pe-7s-play', - 'pe-7s-key', - 'pe-7s-plane', - 'pe-7s-joy', - 'pe-7s-photo-gallery', - 'pe-7s-pin', - 'pe-7s-phone', - 'pe-7s-plug', - 'pe-7s-pen', - 'pe-7s-right-arrow', - 'pe-7s-paper-plane', - 'pe-7s-delete-user', - 'pe-7s-paint', - 'pe-7s-bottom-arrow', - 'pe-7s-notebook', - 'pe-7s-note', - 'pe-7s-next', - 'pe-7s-news-paper', - 'pe-7s-musiclist', - 'pe-7s-music', - 'pe-7s-mouse', - 'pe-7s-more', - 'pe-7s-moon', - 'pe-7s-monitor', - 'pe-7s-micro', - 'pe-7s-menu', - 'pe-7s-map', - 'pe-7s-map-marker', - 'pe-7s-mail', - 'pe-7s-mail-open', - 'pe-7s-mail-open-file', - 'pe-7s-magnet', - 'pe-7s-loop', - 'pe-7s-look', - 'pe-7s-lock', - 'pe-7s-lintern', - 'pe-7s-link', - 'pe-7s-like', - 'pe-7s-light', - 'pe-7s-less', - 'pe-7s-keypad', - 'pe-7s-junk', - 'pe-7s-info', - 'pe-7s-home', - 'pe-7s-help2', - 'pe-7s-help1', - 'pe-7s-graph3', - 'pe-7s-graph2', - 'pe-7s-graph1', - 'pe-7s-graph', - 'pe-7s-global', - 'pe-7s-gleam', - 'pe-7s-glasses', - 'pe-7s-gift', - 'pe-7s-folder', - 'pe-7s-flag', - 'pe-7s-filter', - 'pe-7s-file', - 'pe-7s-expand1', - 'pe-7s-exapnd2', - 'pe-7s-edit', - 'pe-7s-drop', - 'pe-7s-drawer', - 'pe-7s-download', - 'pe-7s-display2', - 'pe-7s-display1', - 'pe-7s-diskette', - 'pe-7s-date', - 'pe-7s-cup', - 'pe-7s-culture', - 'pe-7s-crop', - 'pe-7s-credit', - 'pe-7s-copy-file', - 'pe-7s-config', - 'pe-7s-compass', - 'pe-7s-comment', - 'pe-7s-coffee', - 'pe-7s-cloud', - 'pe-7s-clock', - 'pe-7s-check', - 'pe-7s-chat', - 'pe-7s-cart', - 'pe-7s-camera', - 'pe-7s-call', - 'pe-7s-calculator', - 'pe-7s-browser', - 'pe-7s-box2', - 'pe-7s-box1', - 'pe-7s-bookmarks', - 'pe-7s-bicycle', - 'pe-7s-bell', - 'pe-7s-battery', - 'pe-7s-ball', - 'pe-7s-back', - 'pe-7s-attention', - 'pe-7s-anchor', - 'pe-7s-albums', - 'pe-7s-alarm', - 'pe-7s-airplay' -]; - -// -// // -// // // // For dashboard's charts -// // -// -// Data for Pie Chart -var dataPie = { - labels: ['40%', '20%', '40%'], - series: [40, 20, 40] -}; -var legendPie = { - names: ['Open', 'Bounce', 'Unsubscribe'], - types: ['info', 'danger', 'warning'] -}; - -// Data for Line Chart -var dataSales = { - labels: ['9:00AM', '12:00AM', '3:00PM', '6:00PM', '9:00PM', '12:00PM', '3:00AM', '6:00AM'], - series: [ - [287, 385, 490, 492, 554, 586, 698, 695], - [67, 152, 143, 240, 287, 335, 435, 437], - [23, 113, 67, 108, 190, 239, 307, 308] - ] -}; -var optionsSales = { - low: 0, - high: 800, - showArea: false, - height: '245px', - axisX: { - showGrid: false - }, - lineSmooth: true, - showLine: true, - showPoint: true, - fullWidth: true, - chartPadding: { - right: 50 - } -}; -var responsiveSales = [ - [ - 'screen and (max-width: 640px)', - { - axisX: { - labelInterpolationFnc: function (value) { - return value[0]; - } - } - } - ] -]; -var legendSales = { - names: ['Open', 'Click', 'Click Second Time'], - types: ['info', 'danger', 'warning'] -}; -// Data for Bar Chart -var dataBar = { - labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - series: [ - [542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895], - [412, 243, 280, 580, 453, 353, 300, 364, 368, 410, 636, 695] - ] -}; -var optionsBar = { - seriesBarDistance: 10, - axisX: { - showGrid: false - }, - height: '245px' -}; -var responsiveBar = [ - [ - 'screen and (max-width: 640px)', - { - seriesBarDistance: 5, - axisX: { - labelInterpolationFnc: function (value) { - return value[0]; - } - } - } - ] -]; -var legendBar = { - names: ['Tesla Model S', 'BMW 5 Series'], - types: ['info', 'danger'] -}; - -//Berkeleytime -var enrollment = [ - { name: 'Phase 1', percent: 50 }, - { name: 'Phase 1.5', percent: 56 }, - { name: 'Phase 2', percent: 70 }, - { name: 'Phase 2.5', percent: 90 }, - { name: 'Adjustment', percent: 95 }, - { name: 'Current', percent: 100 } -]; - -var optionsEnrollment = { - low: 0, - high: 100, - showArea: false, - height: '245px', - axisX: { - showGrid: false - }, - lineSmooth: true, - showLine: true, - showPoint: true, - fullWidth: true, - chartPadding: { - right: 50 - } -}; -var responsiveEnrollment = [ - [ - 'screen and (max-width: 640px)', - { - axisX: { - labelInterpolationFnc: function (value) { - return value[0]; - } - } - } - ] -]; - -var grades = [ - { name: 'A+', classA: 20, classB: 2 }, - { name: 'A', classA: 56, classB: 2 }, - { name: 'A-', classA: 1, classB: 2 }, - { name: 'B+', classA: 3, classB: 2 }, - { name: 'B', classA: 20, classB: 2 }, - { name: 'B-', classA: 10, classB: 2 }, - { name: 'C+', classA: 0, classB: 2 }, - { name: 'C', classA: 0, classB: 2 }, - { name: 'C-', classA: 0, classB: 2 }, - { name: 'D+', classA: 0, classB: 2 }, - { name: 'D', classA: 0, classB: 2 }, - { name: 'D-', classA: 0, classB: 2 }, - { name: 'F', classA: 0, classB: 2 } -]; - -var colors = ['#4EA6FB', '#6AE086', '#ED5186', '#F9E152']; - -const vars = { - style, // For notifications (App container and Notifications view) - thArray, - tdArray, // For tables (TableList view) - iconsArray, // For icons (Icons view) - dataPie, - legendPie, - dataSales, - optionsSales, - responsiveSales, // For charts (Dashboard view) - legendSales, - dataBar, - optionsBar, - responsiveBar, - legendBar, // For charts (Dashboard view) - colors, - enrollment, - optionsEnrollment, - responsiveEnrollment, - grades, - -}; - -export default vars; From aa5864108371cda3ee659ded5118280c4c8d914b Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 19:04:27 -0800 Subject: [PATCH 23/27] fixing range while we are at it --- frontend/src/utils/range.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/range.ts b/frontend/src/utils/range.ts index a84fda9f1..1655d2fb2 100644 --- a/frontend/src/utils/range.ts +++ b/frontend/src/utils/range.ts @@ -4,5 +4,5 @@ export function range(a: number, b: number, step = 1): number[] { return Array(b - a) .fill(0) - .map((_, i) => i + a); + .map((_, i) => step * i + a); } From 91fa811bccae886f09b3735abc63f202eb51a82e Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 13 Nov 2023 19:26:29 -0800 Subject: [PATCH 24/27] Update sorting.ts --- frontend/src/lib/courses/sorting.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/courses/sorting.ts b/frontend/src/lib/courses/sorting.ts index 6b5a75f5f..8a7195455 100644 --- a/frontend/src/lib/courses/sorting.ts +++ b/frontend/src/lib/courses/sorting.ts @@ -7,8 +7,8 @@ type CompareFn = (courseA: CourseOverviewFragment, courseB: CourseOverviewFragme * Comparator for department name. Essentially alphabetical order. */ export const compareDepartmentName: CompareFn = (courseA, courseB) => { - const courseATitle = `${courseA.abbreviation} ${courseA.courseNumber}`; - const courseBTitle = `${courseB.abbreviation} ${courseB.courseNumber}`; + const courseATitle = `${courseA.abbreviation} ${courseA.courseNumber.replace(/^[A-Za-z]/, '')}`; + const courseBTitle = `${courseB.abbreviation} ${courseB.courseNumber.replace(/^[A-Za-z]/, '')}`; return courseATitle.localeCompare(courseBTitle); }; From 53c851e40519d65b471afbfecd54c8027b3553e7 Mon Sep 17 00:00:00 2001 From: akmazian Date: Sat, 25 Nov 2023 13:55:55 -0800 Subject: [PATCH 25/27] Footer: removed Facebook link --- frontend/src/components/Common/Footer.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/components/Common/Footer.tsx b/frontend/src/components/Common/Footer.tsx index 0d48d8ff7..4c166d9a3 100644 --- a/frontend/src/components/Common/Footer.tsx +++ b/frontend/src/components/Common/Footer.tsx @@ -36,9 +36,6 @@ export default function Footer() { Discord - - Facebook -
From 28f53b387300c53a99c6a753534c8ef058bdca9f Mon Sep 17 00:00:00 2001 From: akmazian Date: Sat, 25 Nov 2023 13:56:04 -0800 Subject: [PATCH 26/27] Redirect: removed unused file --- frontend/src/Routes.tsx | 4 +--- frontend/src/views/RedirectLink.tsx | 21 --------------------- 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 frontend/src/views/RedirectLink.tsx diff --git a/frontend/src/Routes.tsx b/frontend/src/Routes.tsx index 71a8516f2..160d4ce7c 100644 --- a/frontend/src/Routes.tsx +++ b/frontend/src/Routes.tsx @@ -19,7 +19,6 @@ const RemoteScheduler = () => import('./app/Scheduler/RemoteSchedulerPage'); const ViewSchedule = () => import('./app/Scheduler/ViewSchedule'); const PrivacyPolicy = () => import('./views/PrivacyPolicy'); const TermsOfService = () => import('./views/TermsOfService'); -const RedirectLink = () => import('./views/RedirectLink'); // const Apply = () => import('./views/Apply'); function ScheduleRedirect() { @@ -48,8 +47,7 @@ const router = createBrowserRouter([ { path: '/schedule/:scheduleId', lazy: ViewSchedule }, { path: '/error', Component: Error }, { path: '/legal/privacy', lazy: PrivacyPolicy }, - { path: '/legal/terms', lazy: TermsOfService }, - { path: '/redirect', lazy: RedirectLink }, + { path: '/legal/terms', lazy: TermsOfService } // { path: '/apply', lazy: Apply } ] }, diff --git a/frontend/src/views/RedirectLink.tsx b/frontend/src/views/RedirectLink.tsx deleted file mode 100644 index ee2eeb2be..000000000 --- a/frontend/src/views/RedirectLink.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useNavigate, useLocation } from 'react-router-dom'; - -// We must have an allowlist of redirects to prevent an attacker from arbitrarily opening external sites. -const allowedRedirects = new Map([ - ['workshop-facebook', 'https://www.facebook.com/events/314954970047731'], - [ - 'workshop-register', - 'https://docs.google.com/forms/d/e/1FAIpQLSf92FiqIwMc5et1ZSI_Rj1NGi3Y7Rx2kyMl8uQLSX1QzDIsuQ/viewform?usp=sf_link' - ] -]); - -export function Component() { - const params = new URLSearchParams(useLocation().search); - const site = params.get('site'); - if (site != null && allowedRedirects.has(site)) { - window.open(allowedRedirects.get(site), '_blank'); - } - const navigate = useNavigate(); - navigate(-1); - return null; -} From 7bbc0459a0fd81bff44cf445282a1de6d3b3dea0 Mon Sep 17 00:00:00 2001 From: akmazian Date: Mon, 27 Nov 2023 20:07:03 -0800 Subject: [PATCH 27/27] minor import fix --- frontend/src/components/GraphCard/EnrollmentGraphCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx index 1438d183c..02b65a46d 100644 --- a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx +++ b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Col, Container, Row } from 'react-bootstrap'; import { useDispatch, useSelector } from 'react-redux'; import { fetchEnrollData } from 'redux/enrollment/actions'; -import colors from 'utils/colors.js'; +import colors from 'utils/colors.ts'; import EnrollmentInfoCard from '../EnrollmentInfoCard/EnrollmentInfoCard.jsx'; import EnrollmentGraph from '../Graphs/EnrollmentGraph.jsx'; import GraphEmpty from '../Graphs/GraphEmpty.jsx';