Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Color Changing Bug w/ Duplicate Courses #719

Merged
merged 11 commits into from
Oct 27, 2023
1 change: 0 additions & 1 deletion apps/antalmanac/src/components/Calendar/CalendarRoot.tsx
KevinWu098 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ class ScheduleCalendar extends PureComponent<ScheduleCalendarProps, ScheduleCale
return (
<div className={classes.container} style={isMobile ? { height: 'calc(100% - 50px)' } : undefined}>
<CalendarToolbar
onTakeScreenshot={this.handleTakeScreenshot}
currentScheduleIndex={this.state.currentScheduleIndex}
toggleDisplayFinalsSchedule={this.toggleDisplayFinalsSchedule}
showFinalsSchedule={this.state.showFinalsSchedule}
Expand Down
213 changes: 92 additions & 121 deletions apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import React, { useCallback, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import LazyLoad from 'react-lazyload';

import { Alert } from '@mui/material';
import { Alert, Box, IconButton } from '@mui/material';
import { Close } from '@mui/icons-material';
import { AACourse, AASection } from '@packages/antalmanac-types';
import { WebsocDepartment, WebsocSchool, WebsocAPIResponse, GE } from 'peterportal-api-next-types';
import { WebsocDepartment, WebsocSchool, WebsocAPIResponse } from 'peterportal-api-next-types';
import RightPaneStore from '../RightPaneStore';
import GeDataFetchProvider from '../SectionTable/GEDataFetchProvider';
import SectionTableLazyWrapper from '../SectionTable/SectionTableLazyWrapper';
Expand All @@ -16,14 +15,20 @@ import darkNoNothing from './static/dark-no_results.png';
import noNothing from './static/no_results.png';
import AppStore from '$stores/AppStore';
import { isDarkMode, queryWebsoc, queryWebsocMultiple } from '$lib/helpers';
import Grades from '$lib/grades';
import analyticsEnum from '$lib/analytics';

function flattenSOCObject(SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocDepartment | AACourse)[] {
const courseColors = AppStore.getAddedCourses().reduce((accumulator, { section }) => {
function getColors() {
const courseColors = AppStore.schedule.getCurrentCourses().reduce((accumulator, { section }) => {
accumulator[section.sectionCode] = section.color;
return accumulator;
}, {} as { [key: string]: string });

return courseColors;
}

const flattenSOCObject = (SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocDepartment | AACourse)[] => {
const courseColors = getColors();

return SOCObject.schools.reduce((accumulator: (WebsocSchool | WebsocDepartment | AACourse)[], school) => {
accumulator.push(school);

Expand All @@ -42,7 +47,7 @@ function flattenSOCObject(SOCObject: WebsocAPIResponse): (WebsocSchool | WebsocD
}, []);
};
const RecruitmentBanner = () => {
const [bannerVisibility, setBannerVisibility] = React.useState<boolean>(true);
const [bannerVisibility, setBannerVisibility] = useState(true);

// Display recruitment banner if more than 11 weeks (in ms) has passed since last dismissal
const recruitmentDismissalTime = window.localStorage.getItem('recruitmentDismissalTime');
Expand All @@ -53,7 +58,7 @@ const RecruitmentBanner = () => {
const displayRecruitmentBanner = bannerVisibility && !dismissedRecently && isSearchCS;

return (
<div style={{ position: 'fixed', bottom: 5, right: 5, zIndex: 999 }}>
<Box sx={{ position: 'fixed', bottom: 5, right: 5, zIndex: 999 }}>
{displayRecruitmentBanner ? (
<Alert
icon={false}
Expand All @@ -72,7 +77,7 @@ const RecruitmentBanner = () => {
setBannerVisibility(false);
}}
>
<CloseIcon fontSize="inherit" />
<Close fontSize="inherit" />
</IconButton>
}
>
Expand All @@ -84,8 +89,8 @@ const RecruitmentBanner = () => {
<br />
We have opportunities for experienced devs and those with zero experience!
</Alert>
) : null}{' '}
</div>
) : null}
</Box>
);
};

Expand Down Expand Up @@ -135,13 +140,26 @@ const SectionTableWrapped = (
return <div>{component}</div>;
};

export function CourseRenderPane() {
const ErrorMessage = () => {
return (
<Box sx={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<img
src={isDarkMode() ? darkNoNothing : noNothing}
alt="No Results Found"
style={{ objectFit: 'contain', width: '80%', height: '80%' }}
/>
</Box>
);
};

export default function CourseRenderPane() {
const [websocResp, setWebsocResp] = useState<WebsocAPIResponse>();
const [courseData, setCourseData] = useState<(WebsocSchool | WebsocDepartment | AACourse)[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [scheduleNames, setScheduleNames] = useState(AppStore.getScheduleNames());
const [courseData, setCourseData] = useState<(WebsocSchool | WebsocDepartment | AACourse)[]>([]);

const loadCourses = useCallback(async () => {
const loadCourses = async () => {
setLoading(true);

const formData = RightPaneStore.getFormData();
Expand All @@ -162,134 +180,87 @@ export function CourseRenderPane() {
division: formData.division,
};

const websocQueryParams = {
department: formData.deptValue,
term: formData.term,
ge: formData.ge,
courseNumber: formData.courseNumber,
sectionCodes: formData.sectionCode,
instructorName: formData.instructor,
units: formData.units,
endTime: formData.endTime,
startTime: formData.startTime,
fullCourses: formData.coursesFull,
building: formData.building,
room: formData.room,
division: formData.division,
};

const gradesQueryParams = {
department: formData.deptValue,
ge: formData.ge as GE,
};

try {
// Query websoc for course information and populate gradescache
const [websocJsonResp, _] = await Promise.all([
websocQueryParams.units.includes(',')
? queryWebsocMultiple(websocQueryParams, 'units')
: queryWebsoc(websocQueryParams),
Grades.populateGradesCache(gradesQueryParams),
KevinWu098 marked this conversation as resolved.
Show resolved Hide resolved
]);

let jsonResp;
if (params.units.includes(',')) {
jsonResp = await queryWebsocMultiple(params, 'units');
} else {
jsonResp = await queryWebsoc(params);
}
setLoading(false);
setError(false);
setCourseData(flattenSOCObject(websocJsonResp));
setWebsocResp(jsonResp);
setCourseData(flattenSOCObject(jsonResp));
} catch (error) {
setError(true);
} finally {
setLoading(false);
KevinWu098 marked this conversation as resolved.
Show resolved Hide resolved
setError(true);
}
}, []);
};

useEffect(() => {
loadCourses();
}, []);
const updateScheduleNames = () => {
setScheduleNames(AppStore.getScheduleNames());
};

useEffect(() => {
const updateScheduleNames = () => {
setScheduleNames(AppStore.getScheduleNames());
const changeColors = () => {
if (websocResp == null) {
return;
}
setCourseData(flattenSOCObject(websocResp));
};

AppStore.on('currentScheduleIndexChange', changeColors);

return () => {
AppStore.off('currentScheduleIndexChange', changeColors);
};
}, [websocResp]);

useEffect(() => {
loadCourses();
AppStore.on('scheduleNamesChange', updateScheduleNames);

return () => {
AppStore.off('scheduleNamesChange', updateScheduleNames);
};
}, []);

if (loading) {
return (
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<img src={isDarkMode() ? darkModeLoadingGif : loadingGif} alt="Loading courses" />
</div>
);
}

if (error) {
return (
<div
style={{
height: '100%',
overflowY: 'scroll',
position: 'relative',
}}
>
<div
style={{
return (
<>
{loading ? (
<Box
sx={{
height: '100%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<img src={isDarkMode() ? darkNoNothing : noNothing} alt="No Results Found" />
</div>
</div>
);
}

return (
<>
<RecruitmentBanner />
<div style={{ height: '100%', overflowY: 'scroll', position: 'relative' }}>
<div style={{ height: '50px', marginBottom: '5px' }} />
{courseData.length === 0 ? (
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<img src={isDarkMode() ? darkNoNothing : noNothing} alt="No Results Found" />
</div>
) : (
courseData.map((_, index: number) => {
let heightEstimate = 200;
if ((courseData[index] as AACourse).sections !== undefined)
heightEstimate = (courseData[index] as AACourse).sections.length * 60 + 20 + 40;

return (
<LazyLoad once key={index} overflow height={heightEstimate} offset={500}>
{SectionTableWrapped(index, { courseData, scheduleNames })}
</LazyLoad>
);
})
)}
</div>
<img src={isDarkMode() ? darkModeLoadingGif : loadingGif} alt="Loading courses" />
</Box>
) : error || courseData.length === 0 ? (
<ErrorMessage />
) : (
<>
<RecruitmentBanner />
<Box>
<Box sx={{ height: '50px', marginBottom: '5px' }} />
{courseData.map((_: WebsocSchool | WebsocDepartment | AACourse, index: number) => {
let heightEstimate = 200;
if ((courseData[index] as AACourse).sections !== undefined)
heightEstimate = (courseData[index] as AACourse).sections.length * 60 + 20 + 40;
return (
<LazyLoad once key={index} overflow height={heightEstimate} offset={500}>
{SectionTableWrapped(index, {
courseData: courseData,
scheduleNames: scheduleNames,
})}
</LazyLoad>
);
})}
</Box>
</>
)}
</>
);
}

export default CourseRenderPane;
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const ColorAndDelete = withStyles(styles)((props: ColorAndDeleteProps) =>
<Delete fontSize="small" />
</IconButton>
<ColorPicker
key={AppStore.getCurrentScheduleIndex()}
color={color}
isCustomEvent={false}
sectionCode={sectionCode}
Expand Down
19 changes: 16 additions & 3 deletions apps/antalmanac/src/stores/Schedules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class Schedules {
}

/**
* @return Reference of the course that matches the params
* @return A course that matches the params across all schedules
*/
getExistingCourse(sectionCode: string, term: string) {
for (const course of this.getAllCourses()) {
Expand All @@ -159,6 +159,18 @@ export class Schedules {
return undefined;
}

/**
* @return A course that matches the params in the current schedule
*/
getExistingCourseInSchedule(sectionCode: string, term: string) {
for (const course of this.getCurrentCourses()) {
if (course.section.sectionCode === sectionCode && term === course.term) {
return course;
}
}
return undefined;
}

/**
* Adds a course to a given schedule index
* Sets color to an unused color in set, also will not add class if already exists
Expand All @@ -172,7 +184,7 @@ export class Schedules {
this.addUndoState();
}

const existingSection = this.getExistingCourse(newCourse.section.sectionCode, newCourse.term);
const existingSection = this.getExistingCourseInSchedule(newCourse.section.sectionCode, newCourse.term);

const existsInSchedule = this.doesCourseExistInSchedule(
newCourse.section.sectionCode,
Expand Down Expand Up @@ -222,7 +234,8 @@ export class Schedules {
*/
changeCourseColor(sectionCode: string, term: string, newColor: string) {
this.addUndoState();
const course = this.getExistingCourse(sectionCode, term);

const course = this.getExistingCourseInSchedule(sectionCode, term);
if (course) {
course.section.color = newColor;
}
Expand Down