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 }[];
+};
diff --git a/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx b/frontend/src/components/GraphCard/EnrollmentGraphCard.jsx
index 6fa4a9563..02b65a46d 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 vars from '../../utils/variables';
-
+import { fetchEnrollData } from 'redux/enrollment/actions';
+import colors from 'utils/colors.ts';
+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);
@@ -144,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 5ce7e5f05..41fc6b2f8 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 vars from '../../utils/variables';
-
+import { fetchGradeData } from 'redux/grades/actions';
+import colors from 'utils/colors';
+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);
@@ -117,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}
/>
@@ -153,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/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!',
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/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);
};
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: [
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/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/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/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.js b/frontend/src/redux/enrollment/reducers.ts
similarity index 70%
rename from frontend/src/redux/reducers/enrollment.js
rename to frontend/src/redux/enrollment/reducers.ts
index 51f55b560..8fbdb0749 100644
--- a/frontend/src/redux/reducers/enrollment.js
+++ b/frontend/src/redux/enrollment/reducers.ts
@@ -4,11 +4,13 @@ import {
UPDATE_ENROLL_DATA,
UPDATE_ENROLL_SELECTED,
ENROLL_REMOVE_COURSE,
- ENROLL_RESET
-} from '../actionTypes';
+ ENROLL_RESET,
+ EnrollAction,
+ EnrollmentState
+} from './types';
-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/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.js b/frontend/src/redux/grades/reducers.ts
similarity index 60%
rename from frontend/src/redux/reducers/grade.js
rename to frontend/src/redux/grades/reducers.ts
index 2085274d7..39618dad7 100644
--- a/frontend/src/redux/reducers/grade.js
+++ b/frontend/src/redux/grades/reducers.ts
@@ -1,15 +1,17 @@
+import { GRADE } from './types';
import {
- UPDATE_GRADE_CONTEXT,
GRADE_ADD_COURSE,
- UPDATE_GRADE_DATA,
- UPDATE_GRADE_SELECTED,
GRADE_REMOVE_COURSE,
- GRADE_RESET
-} from '../actionTypes';
-import vars from '../../utils/variables';
+ GRADE_RESET,
+ GradeAction,
+ GradeState,
+ UPDATE_GRADE_CONTEXT,
+ UPDATE_GRADE_DATA,
+ UPDATE_GRADE_SELECTED
+} from './types';
-const initialState = {
- context: {},
+const initialState: GradeState = {
+ context: { courses: [] },
selectedCourses: [],
gradesData: [],
graphData: [],
@@ -19,10 +21,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 +42,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 +51,13 @@ 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 = Object.values(GRADE).map((letterGrade) => ({
+ name: letterGrade,
+ ...gradesData.reduce((grades, grade) => {
+ grades[grade.id] = (grade[letterGrade].numerator / grade.denominator) * 100;
+ return grades;
+ }, {} as Record)
+ }));
return {
...state,
gradesData,
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 70f531ab6..846c68047 100644
--- a/frontend/src/redux/store.ts
+++ b/frontend/src/redux/store.ts
@@ -1,11 +1,12 @@
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';
+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)));
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;
+};
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/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);
}
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';
diff --git a/frontend/src/utils/variables.js b/frontend/src/utils/variables.js
deleted file mode 100644
index 39c7e0f8f..000000000
--- a/frontend/src/utils/variables.js
+++ /dev/null
@@ -1,640 +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 possibleGrades = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F', 'P', 'NP'];
-
-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,
- possibleGrades
-};
-
-export default vars;
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;
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;
-}
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';