Skip to content

Commit

Permalink
Merge pull request #658 from asuc-octo/save
Browse files Browse the repository at this point in the history
Saved courses revamp
  • Loading branch information
mathhulk authored Nov 27, 2023
2 parents cd54ab4 + ecebf1d commit 1a4c7c3
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 47 deletions.
1 change: 0 additions & 1 deletion frontend/src/Berkeleytime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import useDimensions from 'react-cool-dimensions';
import easterEgg from 'utils/easterEgg';
import Routes from './Routes';
import { fetchEnrollContext, fetchGradeContext } from 'redux/actions';
import { IconoirProvider } from 'iconoir-react';

const Berkeleytime = () => {
const dispatch = useDispatch();
Expand Down
31 changes: 20 additions & 11 deletions frontend/src/app/Catalog/CatalogList/CatalogListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ type CatalogListItemProps = {
handleCourseSelect: (course: CourseFragment) => void;
isSelected: boolean;
};
style: CSSProperties;
style?: CSSProperties;
simple?: boolean;
};

const CatalogListItem = ({ style, data }: CatalogListItemProps) => {
const CatalogListItem = ({ style, data, simple }: CatalogListItemProps) => {
const { course, handleCourseSelect, isSelected } = data;
const [{ course: currentCourse }] = useCatalog();

Expand Down Expand Up @@ -63,20 +64,28 @@ const CatalogListItem = ({ style, data }: CatalogListItemProps) => {
{isSaved ? <BookmarkSaved /> : <BookmarkUnsaved />}
</div>
)}
<span className={`${styles[course.letterAverage[0]]}`}>
{course.letterAverage !== '' ? course.letterAverage : ''}
</span>
{!simple && (
<span className={`${styles[course.letterAverage[0]]}`}>
{course.letterAverage !== '' ? course.letterAverage : ''}
</span>
)}
</div>
</div>
<div className={styles.itemStats}>
<span className={colorEnrollment(course.enrolledPercentage)}>
{formatEnrollment(course.enrolledPercentage)} enrolled
</span>
<span>{course.units ? formatUnits(course.units) : 'N/A'}</span>
</div>
{!simple && (
<div className={styles.itemStats}>
<span className={colorEnrollment(course.enrolledPercentage)}>
{formatEnrollment(course.enrolledPercentage)} enrolled
</span>
<span>{course.units ? formatUnits(course.units) : 'N/A'}</span>
</div>
)}
</div>
</div>
);
};

CatalogListItem.defaultProps = {
simple: false
};

export default memo(CatalogListItem, areEqual);
79 changes: 78 additions & 1 deletion frontend/src/app/Catalog/CatalogView/CatalogView.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
padding-right: 10px;
// Add size of fixed header.
padding-top: 30px;

}

&[data-modal='false'] {
Expand Down Expand Up @@ -343,3 +342,81 @@
color: $bt-base-text;
background: #f8f8f8;
}

.saveRoot {
display: flex;
flex-direction: row;
padding: 0 20px;
grid-area: view;
overflow: hidden;
}

@include media(mobile) {
.saveRoot {
display: none;
}
}

.saveContainer {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: scroll;
overflow-x: hidden;
margin: 20px 0px;

&:first-child {
padding-right: 10px;
// border-right: 1.5px solid #eaeaea;
}

&:last-child {
padding-left: 10px;
}
}

.header {
// margin-bottom: 20px;
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: baseline;
gap: 30px;
padding: 6px 23px;

span {
font-size: 20px;
font-weight: 600;
}

border-bottom: 1.5px solid #eaeaea;

button {
color: #2f80ed;
transition: 0.2s;
border: none;
background: none;
padding: 0 8px;
border-radius: 4px;
font-size: 16px;
&:hover {
background: hsla(0, 0%, 92.2%, 0.6196078431);
}
}
}

.seperator {
display: flex;
width: 1.5px;
margin: 20px 0px;
background: #eaeaea;
}

.emptyView {
display: flex;
align-items: center;
justify-content: center;
color: $bt-light-text;
padding: 20px 0;
text-align: center;
}
152 changes: 122 additions & 30 deletions frontend/src/app/Catalog/CatalogView/CatalogView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import book from 'assets/svg/catalog/book.svg';
import launch from 'assets/svg/catalog/launch.svg';
import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow.svg';
import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils';
import { PlaylistType, useGetCourseForNameLazyQuery } from 'graphql';
import { CourseFragment, PlaylistType, useGetCourseForNameLazyQuery } from 'graphql';
import { useNavigate, useParams } from 'react-router';
import Skeleton from 'react-loading-skeleton';
import ReadMore from './ReadMore';
Expand All @@ -15,14 +15,36 @@ import { sortPills } from '../service';
import CourseTabs from './Tabs';

import styles from './CatalogView.module.scss';
import { CatalogSlug } from '../types';
import { CatalogSlug, FilterOption } from '../types';
import Meta from 'components/Common/Meta';
import { courseToName } from 'lib/courses/course';
import { useUser } from 'graphql/hooks/user';
import CatalogListItem from '../CatalogList/CatalogListItem';

const skeleton = [...Array(8).keys()];

// Debug saved courses with dummy array since logging in doesn't work in DEV.
// const dummy = [
// {
// id: 'Q291cnNlVHlwZToyMTcxOQ==',
// abbreviation: 'MATH',
// courseNumber: '56',
// description:
// 'This is a first course in Linear Algebra. Core topics include: algebra and geometry of vectors and matrices; systems of linear equations and Gaussian elimination; eigenvalues and eigenvectors; Gram-Schmidt and least squares; symmetric matrices and quadratic forms; singular value decomposition and other factorizations. Time permitting, additional topics may include: Markov chains and Perron-Frobenius, dimensionality reduction, or linear programming. This course differs from Math 54 in that it does not cover Differential Equations, but focuses on Linear Algebra motivated by first applications in Data Science and Statistics.',
// title: 'Linear Algebra ',
// gradeAverage: -1,
// letterAverage: 'A',
// openSeats: 0,
// enrolledPercentage: 1,
// enrolled: 292,
// enrolledMax: 292,
// units: '4.0',
// __typename: 'CourseType'
// }
// ];

const CatalogView = () => {
const [{ course }, dispatch] = useCatalog();
const [{ course, filters, recentCourses }, dispatch] = useCatalog();
const navigate = useNavigate();
const { abbreviation, courseNumber, semester } = useParams<CatalogSlug>();

Expand All @@ -44,26 +66,29 @@ const CatalogView = () => {
}
});

const { user } = useUser();
const savedClasses = useMemo(() => user?.savedClasses ?? [], [user?.savedClasses]);

useEffect(() => {
const [sem, year] = semester?.split(' ') ?? [null, null];
const [sem, year] = semester?.split(' ') ?? [undefined, undefined];

type coursePayload = {
abbreviation: string;
courseNumber: string;
semester: string;
year: string;
};

const variables = {
abbreviation: abbreviation ?? null,
courseNumber: courseNumber ?? null,
semester: sem?.toLowerCase() ?? null,
abbreviation: abbreviation,
courseNumber: courseNumber,
semester: sem?.toLowerCase(),
year: year
};

// Only fetch the course if every parameter has a value.
if (Object.values(variables).every((value) => value !== null))
getCourse({
variables: variables as {
abbreviation: string;
courseNumber: string;
semester: string;
year: string;
}
});
if (Object.values(variables).every((value) => value !== undefined))
getCourse({ variables: variables as coursePayload });
}, [getCourse, abbreviation, courseNumber, semester]);

useEffect(() => {
Expand Down Expand Up @@ -92,22 +117,23 @@ const CatalogView = () => {
const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`;

return (
<div className={`${styles.root}`} data-modal={typeof courseNumber === 'string'}>
<>
<Meta
title={course ? `Catalog | ${courseToName(course)}` : 'Catalog'}
description={course?.description}
/>
<button
className={styles.modalButton}
onClick={() => {
navigate(`/catalog/${semester}`, { replace: true });
}}
>
<BackArrow />
Close
</button>
{course && (
<>
{course ? (
<div className={`${styles.root}`} data-modal={typeof courseNumber === 'string'}>
<button
className={styles.modalButton}
onClick={() => {
navigate(`/catalog/${semester}`, { replace: true });
dispatch({ type: 'setCourse', course: null });
}}
>
<BackArrow />
Close
</button>
<h3>
{course.abbreviation} {course.courseNumber}
</h3>
Expand Down Expand Up @@ -178,9 +204,75 @@ const CatalogView = () => {
</>
</ReadMore>
<CourseTabs />
</>
</div>
) : (
<div className={styles.saveRoot}>
<div className={styles.saveContainer}>
<div className={styles.header}>
<span>Recent</span>
<button onClick={() => dispatch({ type: 'clearRecents' })}>Clear</button>
</div>
{recentCourses.length > 0 ? (
<div>
{recentCourses.map((course) => (
<CatalogListItem
simple
key={course.id}
data={{
course: course as CourseFragment,
handleCourseSelect: (course) => {
dispatch({ type: 'setCourse', course });
navigate({
pathname: `/catalog/${(filters.semester as FilterOption)?.value?.name}/${
course.abbreviation
}/${course.courseNumber}`,
search: location.search
});
},
isSelected: false
}}
/>
))}
</div>
) : (
<div className={styles.emptyView}>Recently viewed courses will appear here!</div>
)}
</div>
<div className={styles.saveContainer}>
<div className={styles.header}>
<span>Saved</span>
</div>
{savedClasses.length > 0 ? (
<div>
{savedClasses.map((course) => (
<CatalogListItem
simple
key={course.id}
data={{
course: course as CourseFragment,
handleCourseSelect: (course) => {
dispatch({ type: 'setCourse', course });
navigate({
pathname: `/catalog/${(filters.semester as FilterOption)?.value?.name}/${
course.abbreviation
}/${course.courseNumber}`,
search: location.search
});
},
isSelected: false
}}
/>
))}
</div>
) : (
<div className={styles.emptyView}>
Click on the bookmarks in the course list while signed-in to save courses!
</div>
)}
</div>
</div>
)}
</div>
</>
);
};

Expand Down
5 changes: 4 additions & 1 deletion frontend/src/app/Catalog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export type CatalogContext = {
courses: CourseOverviewFragment[];
course: CourseFragment | null;
courseIndex: Fuse<CourseInfo> | null;
recentCourses: CourseFragment[];
};

export type CatalogAction =
Expand All @@ -88,6 +89,8 @@ export type CatalogAction =
| { type: 'filter'; filters: Partial<CatalogContext['filters']> }
| { type: 'setCourseList'; allCourses: CatalogContext['courses'] }
| { type: 'reset'; filters?: Partial<CatalogContext['filters']> }
| { type: 'setPill'; pillItem: PlaylistType };
| { type: 'setPill'; pillItem: PlaylistType }
| { type: 'clearRecents' };


export type CatalogActions = CatalogAction[keyof CatalogAction];
Loading

0 comments on commit 1a4c7c3

Please sign in to comment.