From 668f324c9c3ab46d965ff7177c850bd139905215 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Wed, 8 Mar 2023 17:11:37 -0800 Subject: [PATCH 01/11] working on links --- .../app/Catalog/CatalogView/CatalogView.tsx | 55 + .../src/components/ClassCards/ClassCard.jsx | 1 - .../components/ClassCards/ClassCardList.jsx | 1 + frontend/src/graphql/graphql.ts | 2219 +++++++++++++++++ frontend/src/views/About.tsx | 2 +- 5 files changed, 2276 insertions(+), 2 deletions(-) create mode 100644 frontend/src/graphql/graphql.ts diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index d370ddbca..72aa36949 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -16,6 +16,7 @@ import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useSelector } from 'react-redux'; import SectionTable from './SectionTable'; +import { getLatestSemester, Semester } from 'utils/playlists/semesters'; interface CatalogViewProps { coursePreview: CourseFragment | null; @@ -25,6 +26,28 @@ interface CatalogViewProps { const skeleton = [...Array(8).keys()]; +var dict = new Map([ + ['Field Work', 'FLD'], + ['Session', 'SES'], + ['Colloquium', 'COL'], + ['Recitation', 'REC'], + ['Internship', 'INT'], + ['Studio', 'STD'], + ['Demonstration', 'dem'], + ['Web-based Discussion', 'WBD'], + ['Discussion', 'DIS'], + ['Tutorial', 'TUT'], + ['Clinic', 'CLN'], + ['Independent Study', 'IND'], + ['Self-paced', 'SLF'], + ['Seminar', 'SEM'], + ['Lecture', 'LEC'], + ['Web-based Lecture', 'WBL'], + ['Web-Based Lecture', 'WBL'], + ['Directed Group Study', 'GRP'], + ['Laboratory', 'LAB'], + ]); + const CatalogView = (props: CatalogViewProps) => { const { coursePreview, setCurrentFilters, setCurrentCourse } = props; const { abbreviation, courseNumber, semester } = useParams<{ @@ -105,6 +128,30 @@ const CatalogView = (props: CatalogViewProps) => { sections = sortSections(edges.map((e) => e.node)); } + console.log(semester) + if (false && sections !== null) { + sections = sortSections(sections); + for (var i = 0; i < sections.length; i++) { + var stre = ''; + let temp = semester.split(' '); + var punctuation = ','; + var regex = new RegExp('[' + punctuation + ']', 'g'); + var rmc = courseRef.abbreviation.replace(regex, ''); + stre = `https://classes.berkeley.edu/content/${semester[1]} + -${semester[0]} + -${rmc} + -${courseRef.courseNumber} + -${sections[i].sectionNumber} + -${dict.get(sections[i].kind)} + -${sections[i].sectionNumber}`; + stre = stre.replace(/\s+/g, ''); + + links.push(stre); + } + } + + + // return [playlists ?? skeleton, sections ?? [], semesters]; return [playlists ?? skeleton, sections ?? null]; }, [course]); @@ -136,6 +183,14 @@ const CatalogView = (props: CatalogViewProps) => { const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; + // const playlists = course?.playlistSet.edges.map((e) => e?.node!); + + // let sections = course?.sectionSet.edges.map((e) => e?.node!); + let links:any = []; + console.log(sections) + + + return (
{course && ( diff --git a/frontend/src/components/ClassCards/ClassCard.jsx b/frontend/src/components/ClassCards/ClassCard.jsx index f5971c5c7..d449717ad 100644 --- a/frontend/src/components/ClassCards/ClassCard.jsx +++ b/frontend/src/components/ClassCards/ClassCard.jsx @@ -40,7 +40,6 @@ function ClassCard(props) {
{title}
{`${semester} • ${faculty}`}
- {isMobile ? : null} diff --git a/frontend/src/components/ClassCards/ClassCardList.jsx b/frontend/src/components/ClassCards/ClassCardList.jsx index ba5b4d7b2..f68ed90d8 100644 --- a/frontend/src/components/ClassCards/ClassCardList.jsx +++ b/frontend/src/components/ClassCards/ClassCardList.jsx @@ -7,6 +7,7 @@ import vars from '../../variables/Variables'; class ClassCardList extends PureComponent { render() { const { selectedCourses, removeCourse, additionalInfo, type, isMobile } = this.props; + return ( diff --git a/frontend/src/graphql/graphql.ts b/frontend/src/graphql/graphql.ts new file mode 100644 index 000000000..7d94fe355 --- /dev/null +++ b/frontend/src/graphql/graphql.ts @@ -0,0 +1,2219 @@ +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +export type Maybe = T | null; +export type Exact = { [K in keyof T]: T[K] }; +/** All built-in and custom scalars, mapped to their actual values */ +export interface Scalars { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + /** + * The `Date` scalar type represents a Date + * value as specified by + * [iso8601](https://en.wikipedia.org/wiki/ISO_8601). + */ + Date: any; + /** + * The `DateTime` scalar type represents a DateTime + * value as specified by + * [iso8601](https://en.wikipedia.org/wiki/ISO_8601). + */ + DateTime: any; + /** + * The `GenericScalar` scalar type represents a generic + * GraphQL scalar value that could be: + * String, Boolean, Int, Float, List or Object. + */ + GenericScalar: any; + /** + * Allows use of a JSON String for input / output from the GraphQL schema. + * + * Use of this type is *not recommended* as you lose the benefits of having a defined, static + * schema (one of the key benefits of GraphQL). + */ + JSONString: any; + /** + * The `Time` scalar type represents a Time value as + * specified by + * [iso8601](https://en.wikipedia.org/wiki/ISO_8601). + */ + Time: any; +} + +export interface BerkeleytimeUserType { + __typename?: 'BerkeleytimeUserType'; + id: Scalars['ID']; + user: UserType; + major: Scalars['String']; + savedClasses?: Maybe>>; + emailClassUpdate?: Maybe; + emailGradeUpdate?: Maybe; + emailEnrollmentOpening?: Maybe; + emailBerkeleytimeUpdate?: Maybe; + schedules: ScheduleTypeConnection; +} + + +export interface BerkeleytimeUserTypeSchedulesArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + +export interface CourseType extends Node { + __typename?: 'CourseType'; + /** The ID of the object. */ + id: Scalars['ID']; + title: Scalars['String']; + department: Scalars['String']; + abbreviation: Scalars['String']; + courseNumber: Scalars['String']; + description: Scalars['String']; + units?: Maybe; + crossListing: CourseTypeConnection; + prerequisites: Scalars['String']; + gradeAverage?: Maybe; + letterAverage: Scalars['String']; + hasEnrollment: Scalars['Boolean']; + enrolled: Scalars['Int']; + enrolledMax: Scalars['Int']; + enrolledPercentage: Scalars['Float']; + waitlisted: Scalars['Int']; + openSeats: Scalars['Int']; + lastUpdated: Scalars['DateTime']; + sectionSet: SectionTypeConnection; + gradeSet: GradeTypeConnection; + playlistSet: PlaylistTypeConnection; + berkeleytimeuserSet: Array; + schedulerSections: SectionSelectionTypeConnection; +} + + +export interface CourseTypeCrossListingArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + title?: Maybe; + department?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + description?: Maybe; + units?: Maybe; + crossListing?: Maybe>>; + prerequisites?: Maybe; + gradeAverage?: Maybe; + letterAverage?: Maybe; + hasEnrollment?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + enrolledPercentage?: Maybe; + waitlisted?: Maybe; + openSeats?: Maybe; + lastUpdated?: Maybe; + hasGrades?: Maybe; + inPlaylists?: Maybe; + idIn?: Maybe; +} + + +export interface CourseTypeSectionSetArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + year?: Maybe; + semester?: Maybe; + courseTitle?: Maybe; + sectionNumber?: Maybe; + ccn?: Maybe; + kind?: Maybe; + isPrimary?: Maybe; + associatedSections?: Maybe>>; + days?: Maybe; + startTime?: Maybe; + endTime?: Maybe; + finalDay?: Maybe; + finalEnd?: Maybe; + finalStart?: Maybe; + instructor?: Maybe; + disabled?: Maybe; + locationName?: Maybe; + instructionMode?: Maybe; + lastUpdated?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; +} + + +export interface CourseTypeGradeSetArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + semester?: Maybe; + year?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + sectionNumber?: Maybe; + instructor?: Maybe; + gradedTotal?: Maybe; + average?: Maybe; +} + + +export interface CourseTypePlaylistSetArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + category?: Maybe; + name?: Maybe; + semester?: Maybe; + year?: Maybe; + courses?: Maybe>>; +} + + +export interface CourseTypeSchedulerSectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + +export interface CourseTypeConnection { + __typename?: 'CourseTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `CourseType` and its cursor. */ +export interface CourseTypeEdge { + __typename?: 'CourseTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +export interface CreateSchedule { + __typename?: 'CreateSchedule'; + schedule?: Maybe; +} + + + +export interface DeleteUser { + __typename?: 'DeleteUser'; + success?: Maybe; +} + +/** + * Proxy for enrollment object. Using this instead of + * a DjangoObjectType gives us higher flexibility of the data. + */ +export interface EnrollmentData { + __typename?: 'EnrollmentData'; + day?: Maybe; + dateCreated?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + enrolledPercent?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; + waitlistedPercent?: Maybe; +} + +/** The return format of both queries */ +export interface EnrollmentInfo { + __typename?: 'EnrollmentInfo'; + course?: Maybe; + section?: Maybe>>; + telebears?: Maybe; + data?: Maybe>>; + enrolledMax?: Maybe; + enrolledPercentMax?: Maybe; + enrolledScaleMax?: Maybe; + waitlistedMax?: Maybe; + waitlistedPercentMax?: Maybe; + waitlistedScaleMax?: Maybe; +} + +export interface FormConfigType { + __typename?: 'FormConfigType'; + field?: Maybe; +} + + +export interface GradeType extends Node { + __typename?: 'GradeType'; + /** The ID of the object. */ + id: Scalars['ID']; + course: CourseType; + semester: Scalars['String']; + year: Scalars['String']; + abbreviation: Scalars['String']; + courseNumber: Scalars['String']; + sectionNumber: Scalars['String']; + instructor: Scalars['String']; + instructors: Array; + gradedTotal: Scalars['Int']; + average: Scalars['Float']; + distribution?: Maybe>>; + sectionGpa?: Maybe; + sectionLetter?: Maybe; + denominator?: Maybe; +} + +export interface GradeTypeConnection { + __typename?: 'GradeTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `GradeType` and its cursor. */ +export interface GradeTypeEdge { + __typename?: 'GradeTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + + +export interface LetterGradeType { + __typename?: 'LetterGradeType'; + letter?: Maybe; + numerator?: Maybe; + percent?: Maybe; + percentileHigh?: Maybe; + percentileLow?: Maybe; +} + +export interface Logout { + __typename?: 'Logout'; + success?: Maybe; +} + +export interface Mutation { + __typename?: 'Mutation'; + createSchedule?: Maybe; + updateSchedule?: Maybe; + removeSchedule?: Maybe; + updateUser?: Maybe; + saveClass?: Maybe; + removeClass?: Maybe; + /** Login mutation using graphql_jwt */ + login?: Maybe; + logout?: Maybe; + verifyToken?: Maybe; + refreshToken?: Maybe; + deleteUser?: Maybe; +} + + +export interface MutationCreateScheduleArgs { + name?: Maybe; + public?: Maybe; + selectedSections?: Maybe>>; + semester?: Maybe; + timeblocks?: Maybe>>; + totalUnits?: Maybe; + year?: Maybe; +} + + +export interface MutationUpdateScheduleArgs { + name?: Maybe; + public?: Maybe; + scheduleId?: Maybe; + selectedSections?: Maybe>>; + timeblocks?: Maybe>>; + totalUnits?: Maybe; +} + + +export interface MutationRemoveScheduleArgs { + scheduleId?: Maybe; +} + + +export interface MutationUpdateUserArgs { + emailBerkeleytimeUpdate?: Maybe; + emailClassUpdate?: Maybe; + emailEnrollmentOpening?: Maybe; + emailGradeUpdate?: Maybe; + major?: Maybe; +} + + +export interface MutationSaveClassArgs { + classId?: Maybe; +} + + +export interface MutationRemoveClassArgs { + classId?: Maybe; +} + + +export interface MutationLoginArgs { + tokenId?: Maybe; +} + + +export interface MutationVerifyTokenArgs { + token?: Maybe; +} + + +export interface MutationRefreshTokenArgs { + token?: Maybe; +} + +/** An object with an ID */ +export interface Node { + /** The ID of the object. */ + id: Scalars['ID']; +} + +/** Login mutation using graphql_jwt */ +export interface ObtainJsonWebToken { + __typename?: 'ObtainJSONWebToken'; + payload: Scalars['GenericScalar']; + refreshExpiresIn: Scalars['Int']; + user?: Maybe; + newUser?: Maybe; +} + +/** The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. */ +export interface PageInfo { + __typename?: 'PageInfo'; + /** When paginating forwards, are there more items? */ + hasNextPage: Scalars['Boolean']; + /** When paginating backwards, are there more items? */ + hasPreviousPage: Scalars['Boolean']; + /** When paginating backwards, the cursor to continue. */ + startCursor?: Maybe; + /** When paginating forwards, the cursor to continue. */ + endCursor?: Maybe; +} + +export interface PlaylistType extends Node { + __typename?: 'PlaylistType'; + /** The ID of the object. */ + id: Scalars['ID']; + category: Scalars['String']; + name: Scalars['String']; + semester: Scalars['String']; + year: Scalars['String']; + courses: CourseTypeConnection; +} + + +export interface PlaylistTypeCoursesArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + title?: Maybe; + department?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + description?: Maybe; + units?: Maybe; + crossListing?: Maybe>>; + prerequisites?: Maybe; + gradeAverage?: Maybe; + letterAverage?: Maybe; + hasEnrollment?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + enrolledPercentage?: Maybe; + waitlisted?: Maybe; + openSeats?: Maybe; + lastUpdated?: Maybe; + hasGrades?: Maybe; + inPlaylists?: Maybe; + idIn?: Maybe; +} + +export interface PlaylistTypeConnection { + __typename?: 'PlaylistTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `PlaylistType` and its cursor. */ +export interface PlaylistTypeEdge { + __typename?: 'PlaylistTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +export interface Query { + __typename?: 'Query'; + schedules?: Maybe>>; + schedule?: Maybe; + user?: Maybe; + allPlaylists?: Maybe; + /** The ID of the object */ + playlist?: Maybe; + allGrades?: Maybe; + /** The ID of the object */ + grade?: Maybe; + formConfig?: Maybe; + courseEnrollmentBySection?: Maybe; + courseEnrollmentBySemester?: Maybe; + /** The ID of the object */ + course?: Maybe; + allCourses?: Maybe; + /** The ID of the object */ + section?: Maybe; + allSections?: Maybe; + ping?: Maybe; +} + + +export interface QueryScheduleArgs { + id?: Maybe; +} + + +export interface QueryAllPlaylistsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + category?: Maybe; + name?: Maybe; + semester?: Maybe; + year?: Maybe; + courses?: Maybe>>; +} + + +export interface QueryPlaylistArgs { + id: Scalars['ID']; +} + + +export interface QueryAllGradesArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + semester?: Maybe; + year?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + sectionNumber?: Maybe; + instructor?: Maybe; + gradedTotal?: Maybe; + average?: Maybe; +} + + +export interface QueryGradeArgs { + id: Scalars['ID']; +} + + +export interface QueryCourseEnrollmentBySectionArgs { + sectionId?: Maybe; +} + + +export interface QueryCourseEnrollmentBySemesterArgs { + courseId?: Maybe; + semester?: Maybe; + year?: Maybe; +} + + +export interface QueryCourseArgs { + id: Scalars['ID']; +} + + +export interface QueryAllCoursesArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + title?: Maybe; + department?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + description?: Maybe; + units?: Maybe; + crossListing?: Maybe>>; + prerequisites?: Maybe; + gradeAverage?: Maybe; + letterAverage?: Maybe; + hasEnrollment?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + enrolledPercentage?: Maybe; + waitlisted?: Maybe; + openSeats?: Maybe; + lastUpdated?: Maybe; + hasGrades?: Maybe; + inPlaylists?: Maybe; + idIn?: Maybe; +} + + +export interface QuerySectionArgs { + id: Scalars['ID']; +} + + +export interface QueryAllSectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + year?: Maybe; + semester?: Maybe; + courseTitle?: Maybe; + sectionNumber?: Maybe; + ccn?: Maybe; + kind?: Maybe; + isPrimary?: Maybe; + associatedSections?: Maybe>>; + days?: Maybe; + startTime?: Maybe; + endTime?: Maybe; + finalDay?: Maybe; + finalEnd?: Maybe; + finalStart?: Maybe; + instructor?: Maybe; + disabled?: Maybe; + locationName?: Maybe; + instructionMode?: Maybe; + lastUpdated?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; +} + +export interface Refresh { + __typename?: 'Refresh'; + payload: Scalars['GenericScalar']; + refreshExpiresIn: Scalars['Int']; +} + +export interface RemoveClass { + __typename?: 'RemoveClass'; + user?: Maybe; +} + +export interface RemoveSchedule { + __typename?: 'RemoveSchedule'; + schedule?: Maybe; +} + +export interface SaveClass { + __typename?: 'SaveClass'; + user?: Maybe; +} + +export interface ScheduleType extends Node { + __typename?: 'ScheduleType'; + /** The ID of the object. */ + id: Scalars['ID']; + user: BerkeleytimeUserType; + name: Scalars['String']; + year: Scalars['String']; + semester: Scalars['String']; + dateCreated: Scalars['DateTime']; + dateModified: Scalars['DateTime']; + totalUnits: Scalars['String']; + public: Scalars['Boolean']; + selectedSections: SectionSelectionTypeConnection; + timeblocks: TimeBlockTypeConnection; +} + + +export interface ScheduleTypeSelectedSectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + + +export interface ScheduleTypeTimeblocksArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + +export interface ScheduleTypeConnection { + __typename?: 'ScheduleTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `ScheduleType` and its cursor. */ +export interface ScheduleTypeEdge { + __typename?: 'ScheduleTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +export interface SectionSelectionInput { + course: Scalars['ID']; + primary?: Maybe; + secondary?: Maybe>>; +} + +export interface SectionSelectionType extends Node { + __typename?: 'SectionSelectionType'; + /** The ID of the object. */ + id: Scalars['ID']; + schedule: ScheduleType; + course: CourseType; + primary?: Maybe; + secondary: SectionTypeConnection; +} + + +export interface SectionSelectionTypeSecondaryArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + year?: Maybe; + semester?: Maybe; + courseTitle?: Maybe; + sectionNumber?: Maybe; + ccn?: Maybe; + kind?: Maybe; + isPrimary?: Maybe; + associatedSections?: Maybe>>; + days?: Maybe; + startTime?: Maybe; + endTime?: Maybe; + finalDay?: Maybe; + finalEnd?: Maybe; + finalStart?: Maybe; + instructor?: Maybe; + disabled?: Maybe; + locationName?: Maybe; + instructionMode?: Maybe; + lastUpdated?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; +} + +export interface SectionSelectionTypeConnection { + __typename?: 'SectionSelectionTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `SectionSelectionType` and its cursor. */ +export interface SectionSelectionTypeEdge { + __typename?: 'SectionSelectionTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +export interface SectionType extends Node { + __typename?: 'SectionType'; + /** The ID of the object. */ + id: Scalars['ID']; + course: CourseType; + abbreviation: Scalars['String']; + courseNumber: Scalars['String']; + year: Scalars['String']; + semester: Scalars['String']; + courseTitle: Scalars['String']; + sectionNumber: Scalars['String']; + ccn: Scalars['String']; + kind: Scalars['String']; + isPrimary: Scalars['Boolean']; + associatedSections: SectionTypeConnection; + days: Scalars['String']; + startTime?: Maybe; + endTime?: Maybe; + finalDay: Scalars['String']; + finalEnd?: Maybe; + finalStart?: Maybe; + instructor: Scalars['String']; + disabled: Scalars['Boolean']; + locationName: Scalars['String']; + instructionMode: Scalars['String']; + lastUpdated: Scalars['DateTime']; + enrolled?: Maybe; + enrolledMax?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; + schedulerPrimarySections: SectionSelectionTypeConnection; + schedulerSecondarySections: SectionSelectionTypeConnection; + wordDays?: Maybe; +} + + +export interface SectionTypeAssociatedSectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; + course?: Maybe; + abbreviation?: Maybe; + courseNumber?: Maybe; + year?: Maybe; + semester?: Maybe; + courseTitle?: Maybe; + sectionNumber?: Maybe; + ccn?: Maybe; + kind?: Maybe; + isPrimary?: Maybe; + associatedSections?: Maybe>>; + days?: Maybe; + startTime?: Maybe; + endTime?: Maybe; + finalDay?: Maybe; + finalEnd?: Maybe; + finalStart?: Maybe; + instructor?: Maybe; + disabled?: Maybe; + locationName?: Maybe; + instructionMode?: Maybe; + lastUpdated?: Maybe; + enrolled?: Maybe; + enrolledMax?: Maybe; + waitlisted?: Maybe; + waitlistedMax?: Maybe; +} + + +export interface SectionTypeSchedulerPrimarySectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + + +export interface SectionTypeSchedulerSecondarySectionsArgs { + before?: Maybe; + after?: Maybe; + first?: Maybe; + last?: Maybe; +} + +export interface SectionTypeConnection { + __typename?: 'SectionTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `SectionType` and its cursor. */ +export interface SectionTypeEdge { + __typename?: 'SectionTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +/** Telebears JSON */ +export interface TelebearData { + __typename?: 'TelebearData'; + phase1Start?: Maybe; + phase1End?: Maybe; + phase2Start?: Maybe; + phase2End?: Maybe; + adjStart?: Maybe; +} + + +export interface TimeBlockInput { + name: Scalars['String']; + startTime: Scalars['Time']; + endTime: Scalars['Time']; + days: Scalars['String']; +} + +export interface TimeBlockType extends Node { + __typename?: 'TimeBlockType'; + /** The ID of the object. */ + id: Scalars['ID']; + name: Scalars['String']; + startTime: Scalars['Time']; + endTime: Scalars['Time']; + days: Scalars['String']; + schedule: ScheduleType; +} + +export interface TimeBlockTypeConnection { + __typename?: 'TimeBlockTypeConnection'; + /** Pagination data for this connection. */ + pageInfo: PageInfo; + /** Contains the nodes in this connection. */ + edges: Array>; +} + +/** A Relay edge containing a `TimeBlockType` and its cursor. */ +export interface TimeBlockTypeEdge { + __typename?: 'TimeBlockTypeEdge'; + /** The item at the end of the edge */ + node?: Maybe; + /** A cursor for use in pagination */ + cursor: Scalars['String']; +} + +export interface UpdateSchedule { + __typename?: 'UpdateSchedule'; + schedule?: Maybe; +} + +export interface UpdateUser { + __typename?: 'UpdateUser'; + user?: Maybe; +} + +export interface UserType { + __typename?: 'UserType'; + id: Scalars['ID']; + /** Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. */ + username: Scalars['String']; + firstName: Scalars['String']; + lastName: Scalars['String']; + email: Scalars['String']; +} + +export interface Verify { + __typename?: 'Verify'; + payload: Scalars['GenericScalar']; +} + +export type CourseFragment = ( + { __typename?: 'CourseType' } + & Pick + & { playlistSet: ( + { __typename?: 'PlaylistTypeConnection' } + & { edges: Array + )> } + )>> } + ), sectionSet: ( + { __typename?: 'SectionTypeConnection' } + & { edges: Array } + )>> } + ) } +); + +export type CourseOverviewFragment = ( + { __typename?: 'CourseType' } + & Pick +); + +export type FilterFragment = ( + { __typename?: 'PlaylistType' } + & Pick +); + +export type LectureFragment = ( + { __typename?: 'SectionType' } + & { associatedSections: ( + { __typename?: 'SectionTypeConnection' } + & { edges: Array } + )>> } + ) } + & SectionFragment +); + +export type ScheduleFragment = ( + { __typename?: 'ScheduleType' } + & Pick + & { user: ( + { __typename?: 'BerkeleytimeUserType' } + & { user: ( + { __typename?: 'UserType' } + & Pick + ) } + ), selectedSections: ( + { __typename?: 'SectionSelectionTypeConnection' } + & { edges: Array } + )>> } + ) } +); + +export type ScheduleOverviewFragment = ( + { __typename?: 'ScheduleType' } + & Pick + & { selectedSections: ( + { __typename?: 'SectionSelectionTypeConnection' } + & { edges: Array + ) } + )> } + )>> } + ) } +); + +export type SchedulerCourseFragment = ( + { __typename?: 'CourseType' } + & Pick + & { sectionSet: ( + { __typename?: 'SectionTypeConnection' } + & { edges: Array } + )>> } + ) } +); + +export type SectionFragment = ( + { __typename?: 'SectionType' } + & Pick +); + +export type SectionSelectionFragment = ( + { __typename?: 'SectionSelectionType' } + & Pick + & { course: ( + { __typename?: 'CourseType' } + & CourseOverviewFragment + ), primary?: Maybe<( + { __typename?: 'SectionType' } + & SectionFragment + )>, secondary: ( + { __typename?: 'SectionTypeConnection' } + & { edges: Array } + )>> } + ) } +); + +export type UserProfileFragment = ( + { __typename?: 'BerkeleytimeUserType' } + & Pick + & { user: ( + { __typename?: 'UserType' } + & Pick + ), savedClasses?: Maybe>>, schedules: ( + { __typename?: 'ScheduleTypeConnection' } + & { edges: Array } + )>> } + ) } +); + +export type CreateScheduleMutationVariables = Exact<{ + name: Scalars['String']; + selectedSections: Array> | Maybe; + timeblocks: Array> | Maybe; + totalUnits: Scalars['String']; + semester: Scalars['String']; + year: Scalars['String']; + public: Scalars['Boolean']; +}>; + + +export type CreateScheduleMutation = ( + { __typename?: 'Mutation' } + & { createSchedule?: Maybe<( + { __typename?: 'CreateSchedule' } + & { schedule?: Maybe<( + { __typename?: 'ScheduleType' } + & ScheduleFragment + )> } + )> } +); + +export type DeleteScheduleMutationVariables = Exact<{ + id: Scalars['ID']; +}>; + + +export type DeleteScheduleMutation = ( + { __typename?: 'Mutation' } + & { removeSchedule?: Maybe<( + { __typename?: 'RemoveSchedule' } + & { schedule?: Maybe<( + { __typename?: 'ScheduleType' } + & Pick + )> } + )> } +); + +export type DeleteUserMutationVariables = Exact<{ [key: string]: never; }>; + + +export type DeleteUserMutation = ( + { __typename?: 'Mutation' } + & { deleteUser?: Maybe<( + { __typename?: 'DeleteUser' } + & Pick + )> } +); + +export type LogoutMutationVariables = Exact<{ [key: string]: never; }>; + + +export type LogoutMutation = ( + { __typename?: 'Mutation' } + & { logout?: Maybe<( + { __typename?: 'Logout' } + & Pick + )> } +); + +export type SaveCourseMutationVariables = Exact<{ + courseId: Scalars['ID']; +}>; + + +export type SaveCourseMutation = ( + { __typename?: 'Mutation' } + & { saveClass?: Maybe<( + { __typename?: 'SaveClass' } + & { user?: Maybe<( + { __typename?: 'BerkeleytimeUserType' } + & Pick + & { savedClasses?: Maybe>> } + )> } + )> } +); + +export type UnsaveCourseMutationVariables = Exact<{ + courseId: Scalars['ID']; +}>; + + +export type UnsaveCourseMutation = ( + { __typename?: 'Mutation' } + & { removeClass?: Maybe<( + { __typename?: 'RemoveClass' } + & { user?: Maybe<( + { __typename?: 'BerkeleytimeUserType' } + & Pick + & { savedClasses?: Maybe>> } + )> } + )> } +); + +export type UpdateScheduleMutationVariables = Exact<{ + scheduleId: Scalars['ID']; + name: Scalars['String']; + selectedSections: Array> | Maybe; + timeblocks: Array> | Maybe; + totalUnits: Scalars['String']; + public: Scalars['Boolean']; +}>; + + +export type UpdateScheduleMutation = ( + { __typename?: 'Mutation' } + & { updateSchedule?: Maybe<( + { __typename?: 'UpdateSchedule' } + & { schedule?: Maybe<( + { __typename?: 'ScheduleType' } + & ScheduleFragment + )> } + )> } +); + +export type UpdateUserMutationVariables = Exact<{ + emailBerkeleytimeUpdate?: Maybe; + emailClassUpdate?: Maybe; + emailEnrollmentOpening?: Maybe; + emailGradeUpdate?: Maybe; + major?: Maybe; +}>; + + +export type UpdateUserMutation = ( + { __typename?: 'Mutation' } + & { updateUser?: Maybe<( + { __typename?: 'UpdateUser' } + & { user?: Maybe<( + { __typename?: 'BerkeleytimeUserType' } + & Pick + )> } + )> } +); + +export type LoginMutationVariables = Exact<{ + token: Scalars['String']; +}>; + + +export type LoginMutation = ( + { __typename?: 'Mutation' } + & { login?: Maybe<( + { __typename?: 'ObtainJSONWebToken' } + & Pick + & { user?: Maybe<( + { __typename?: 'BerkeleytimeUserType' } + & UserProfileFragment + )> } + )> } +); + +export type GetCourseForIdQueryVariables = Exact<{ + id: Scalars['ID']; + year?: Maybe; + semester?: Maybe; +}>; + + +export type GetCourseForIdQuery = ( + { __typename?: 'Query' } + & { course?: Maybe<( + { __typename?: 'CourseType' } + & CourseFragment + )> } +); + +export type GetCourseForNameQueryVariables = Exact<{ + abbreviation: Scalars['String']; + courseNumber: Scalars['String']; + year?: Maybe; + semester?: Maybe; +}>; + + +export type GetCourseForNameQuery = ( + { __typename?: 'Query' } + & { allCourses?: Maybe<( + { __typename?: 'CourseTypeConnection' } + & { edges: Array } + )>> } + )> } +); + +export type GetCoursesForFilterQueryVariables = Exact<{ + playlists: Scalars['String']; +}>; + + +export type GetCoursesForFilterQuery = ( + { __typename?: 'Query' } + & { allCourses?: Maybe<( + { __typename?: 'CourseTypeConnection' } + & { edges: Array } + )>> } + )> } +); + +export type GetFiltersQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetFiltersQuery = ( + { __typename?: 'Query' } + & { allPlaylists?: Maybe<( + { __typename?: 'PlaylistTypeConnection' } + & { edges: Array } + )>> } + )> } +); + +export type GetScheduleForIdQueryVariables = Exact<{ + id: Scalars['ID']; +}>; + + +export type GetScheduleForIdQuery = ( + { __typename?: 'Query' } + & { schedule?: Maybe<( + { __typename?: 'ScheduleType' } + & ScheduleFragment + )> } +); + +export type GetSchedulerCourseForIdQueryVariables = Exact<{ + id: Scalars['ID']; + year?: Maybe; + semester?: Maybe; +}>; + + +export type GetSchedulerCourseForIdQuery = ( + { __typename?: 'Query' } + & { course?: Maybe<( + { __typename?: 'CourseType' } + & SchedulerCourseFragment + )> } +); + +export type GetSemestersQueryVariables = Exact<{ + name?: Maybe; +}>; + + +export type GetSemestersQuery = ( + { __typename?: 'Query' } + & { allPlaylists?: Maybe<( + { __typename?: 'PlaylistTypeConnection' } + & { edges: Array } + )>> } + )> } +); + +export type GetUserQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetUserQuery = ( + { __typename?: 'Query' } + & { user?: Maybe<( + { __typename?: 'BerkeleytimeUserType' } + & UserProfileFragment + )> } +); + +export const SectionFragmentDoc = gql` + fragment Section on SectionType { + id + ccn + kind + instructor + startTime + endTime + enrolled + enrolledMax + locationName + waitlisted + waitlistedMax + days + wordDays + disabled + sectionNumber + isPrimary +} + `; +export const CourseFragmentDoc = gql` + fragment Course on CourseType { + title + units + waitlisted + openSeats + letterAverage + gradeAverage + lastUpdated + id + hasEnrollment + gradeAverage + enrolledPercentage + enrolledMax + courseNumber + department + description + enrolled + abbreviation + prerequisites + playlistSet { + edges { + node { + category + id + name + semester + year + } + } + } + sectionSet(year: $year, semester: $semester) { + edges { + node { + ...Section + } + } + } +} + ${SectionFragmentDoc}`; +export const FilterFragmentDoc = gql` + fragment Filter on PlaylistType { + id + name + category +} + `; +export const CourseOverviewFragmentDoc = gql` + fragment CourseOverview on CourseType { + id + abbreviation + courseNumber + description + title + gradeAverage + letterAverage + openSeats + enrolledPercentage + enrolled + enrolledMax + units +} + `; +export const SectionSelectionFragmentDoc = gql` + fragment SectionSelection on SectionSelectionType { + id + course { + ...CourseOverview + } + primary { + ...Section + } + secondary { + edges { + node { + ...Section + } + } + } +} + ${CourseOverviewFragmentDoc} +${SectionFragmentDoc}`; +export const ScheduleFragmentDoc = gql` + fragment Schedule on ScheduleType { + id + year + semester + name + totalUnits + dateCreated + dateModified + public + user { + user { + id + firstName + lastName + } + } + selectedSections { + edges { + node { + ...SectionSelection + } + } + } +} + ${SectionSelectionFragmentDoc}`; +export const LectureFragmentDoc = gql` + fragment Lecture on SectionType { + ...Section + associatedSections { + edges { + node { + ...Section + } + } + } +} + ${SectionFragmentDoc}`; +export const SchedulerCourseFragmentDoc = gql` + fragment SchedulerCourse on CourseType { + id + title + units + waitlisted + openSeats + enrolled + enrolledMax + courseNumber + department + description + abbreviation + sectionSet(isPrimary: true, year: $year, semester: $semester) { + edges { + node { + ...Lecture + } + } + } +} + ${LectureFragmentDoc}`; +export const ScheduleOverviewFragmentDoc = gql` + fragment ScheduleOverview on ScheduleType { + id + year + semester + name + totalUnits + dateCreated + dateModified + selectedSections { + edges { + node { + course { + abbreviation + courseNumber + units + } + } + } + } +} + `; +export const UserProfileFragmentDoc = gql` + fragment UserProfile on BerkeleytimeUserType { + id + major + user { + id + username + firstName + lastName + email + } + emailClassUpdate + emailGradeUpdate + emailEnrollmentOpening + emailBerkeleytimeUpdate + savedClasses { + ...CourseOverview + } + schedules { + edges { + node { + ...ScheduleOverview + } + } + } +} + ${CourseOverviewFragmentDoc} +${ScheduleOverviewFragmentDoc}`; +export const CreateScheduleDocument = gql` + mutation CreateSchedule($name: String!, $selectedSections: [SectionSelectionInput]!, $timeblocks: [TimeBlockInput]!, $totalUnits: String!, $semester: String!, $year: String!, $public: Boolean!) { + createSchedule( + name: $name + selectedSections: $selectedSections + timeblocks: $timeblocks + semester: $semester + year: $year + public: $public + totalUnits: $totalUnits + ) { + schedule { + ...Schedule + } + } +} + ${ScheduleFragmentDoc}`; +export type CreateScheduleMutationFn = Apollo.MutationFunction; + +/** + * __useCreateScheduleMutation__ + * + * To run a mutation, you first call `useCreateScheduleMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateScheduleMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createScheduleMutation, { data, loading, error }] = useCreateScheduleMutation({ + * variables: { + * name: // value for 'name' + * selectedSections: // value for 'selectedSections' + * timeblocks: // value for 'timeblocks' + * totalUnits: // value for 'totalUnits' + * semester: // value for 'semester' + * year: // value for 'year' + * public: // value for 'public' + * }, + * }); + */ +export function useCreateScheduleMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(CreateScheduleDocument, baseOptions); + } +export type CreateScheduleMutationHookResult = ReturnType; +export type CreateScheduleMutationResult = Apollo.MutationResult; +export type CreateScheduleMutationOptions = Apollo.BaseMutationOptions; +export const DeleteScheduleDocument = gql` + mutation DeleteSchedule($id: ID!) { + removeSchedule(scheduleId: $id) { + schedule { + id + } + } +} + `; +export type DeleteScheduleMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteScheduleMutation__ + * + * To run a mutation, you first call `useDeleteScheduleMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteScheduleMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteScheduleMutation, { data, loading, error }] = useDeleteScheduleMutation({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useDeleteScheduleMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(DeleteScheduleDocument, baseOptions); + } +export type DeleteScheduleMutationHookResult = ReturnType; +export type DeleteScheduleMutationResult = Apollo.MutationResult; +export type DeleteScheduleMutationOptions = Apollo.BaseMutationOptions; +export const DeleteUserDocument = gql` + mutation DeleteUser { + deleteUser { + success + } +} + `; +export type DeleteUserMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteUserMutation__ + * + * To run a mutation, you first call `useDeleteUserMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteUserMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteUserMutation, { data, loading, error }] = useDeleteUserMutation({ + * variables: { + * }, + * }); + */ +export function useDeleteUserMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(DeleteUserDocument, baseOptions); + } +export type DeleteUserMutationHookResult = ReturnType; +export type DeleteUserMutationResult = Apollo.MutationResult; +export type DeleteUserMutationOptions = Apollo.BaseMutationOptions; +export const LogoutDocument = gql` + mutation Logout { + logout { + success + } +} + `; +export type LogoutMutationFn = Apollo.MutationFunction; + +/** + * __useLogoutMutation__ + * + * To run a mutation, you first call `useLogoutMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useLogoutMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [logoutMutation, { data, loading, error }] = useLogoutMutation({ + * variables: { + * }, + * }); + */ +export function useLogoutMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(LogoutDocument, baseOptions); + } +export type LogoutMutationHookResult = ReturnType; +export type LogoutMutationResult = Apollo.MutationResult; +export type LogoutMutationOptions = Apollo.BaseMutationOptions; +export const SaveCourseDocument = gql` + mutation SaveCourse($courseId: ID!) { + saveClass(classId: $courseId) { + user { + id + savedClasses { + ...CourseOverview + } + } + } +} + ${CourseOverviewFragmentDoc}`; +export type SaveCourseMutationFn = Apollo.MutationFunction; + +/** + * __useSaveCourseMutation__ + * + * To run a mutation, you first call `useSaveCourseMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSaveCourseMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [saveCourseMutation, { data, loading, error }] = useSaveCourseMutation({ + * variables: { + * courseId: // value for 'courseId' + * }, + * }); + */ +export function useSaveCourseMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(SaveCourseDocument, baseOptions); + } +export type SaveCourseMutationHookResult = ReturnType; +export type SaveCourseMutationResult = Apollo.MutationResult; +export type SaveCourseMutationOptions = Apollo.BaseMutationOptions; +export const UnsaveCourseDocument = gql` + mutation UnsaveCourse($courseId: ID!) { + removeClass(classId: $courseId) { + user { + id + savedClasses { + ...CourseOverview + } + } + } +} + ${CourseOverviewFragmentDoc}`; +export type UnsaveCourseMutationFn = Apollo.MutationFunction; + +/** + * __useUnsaveCourseMutation__ + * + * To run a mutation, you first call `useUnsaveCourseMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUnsaveCourseMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [unsaveCourseMutation, { data, loading, error }] = useUnsaveCourseMutation({ + * variables: { + * courseId: // value for 'courseId' + * }, + * }); + */ +export function useUnsaveCourseMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(UnsaveCourseDocument, baseOptions); + } +export type UnsaveCourseMutationHookResult = ReturnType; +export type UnsaveCourseMutationResult = Apollo.MutationResult; +export type UnsaveCourseMutationOptions = Apollo.BaseMutationOptions; +export const UpdateScheduleDocument = gql` + mutation UpdateSchedule($scheduleId: ID!, $name: String!, $selectedSections: [SectionSelectionInput]!, $timeblocks: [TimeBlockInput]!, $totalUnits: String!, $public: Boolean!) { + updateSchedule( + scheduleId: $scheduleId + name: $name + selectedSections: $selectedSections + timeblocks: $timeblocks + totalUnits: $totalUnits + public: $public + ) { + schedule { + ...Schedule + } + } +} + ${ScheduleFragmentDoc}`; +export type UpdateScheduleMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateScheduleMutation__ + * + * To run a mutation, you first call `useUpdateScheduleMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateScheduleMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateScheduleMutation, { data, loading, error }] = useUpdateScheduleMutation({ + * variables: { + * scheduleId: // value for 'scheduleId' + * name: // value for 'name' + * selectedSections: // value for 'selectedSections' + * timeblocks: // value for 'timeblocks' + * totalUnits: // value for 'totalUnits' + * public: // value for 'public' + * }, + * }); + */ +export function useUpdateScheduleMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(UpdateScheduleDocument, baseOptions); + } +export type UpdateScheduleMutationHookResult = ReturnType; +export type UpdateScheduleMutationResult = Apollo.MutationResult; +export type UpdateScheduleMutationOptions = Apollo.BaseMutationOptions; +export const UpdateUserDocument = gql` + mutation UpdateUser($emailBerkeleytimeUpdate: Boolean, $emailClassUpdate: Boolean, $emailEnrollmentOpening: Boolean, $emailGradeUpdate: Boolean, $major: String) { + updateUser( + emailBerkeleytimeUpdate: $emailBerkeleytimeUpdate + emailClassUpdate: $emailClassUpdate + emailEnrollmentOpening: $emailEnrollmentOpening + emailGradeUpdate: $emailGradeUpdate + major: $major + ) { + user { + id + major + emailGradeUpdate + emailEnrollmentOpening + emailClassUpdate + emailBerkeleytimeUpdate + } + } +} + `; +export type UpdateUserMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateUserMutation__ + * + * To run a mutation, you first call `useUpdateUserMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateUserMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateUserMutation, { data, loading, error }] = useUpdateUserMutation({ + * variables: { + * emailBerkeleytimeUpdate: // value for 'emailBerkeleytimeUpdate' + * emailClassUpdate: // value for 'emailClassUpdate' + * emailEnrollmentOpening: // value for 'emailEnrollmentOpening' + * emailGradeUpdate: // value for 'emailGradeUpdate' + * major: // value for 'major' + * }, + * }); + */ +export function useUpdateUserMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(UpdateUserDocument, baseOptions); + } +export type UpdateUserMutationHookResult = ReturnType; +export type UpdateUserMutationResult = Apollo.MutationResult; +export type UpdateUserMutationOptions = Apollo.BaseMutationOptions; +export const LoginDocument = gql` + mutation Login($token: String!) { + login(tokenId: $token) { + newUser + refreshExpiresIn + payload + user { + ...UserProfile + } + } +} + ${UserProfileFragmentDoc}`; +export type LoginMutationFn = Apollo.MutationFunction; + +/** + * __useLoginMutation__ + * + * To run a mutation, you first call `useLoginMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useLoginMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [loginMutation, { data, loading, error }] = useLoginMutation({ + * variables: { + * token: // value for 'token' + * }, + * }); + */ +export function useLoginMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(LoginDocument, baseOptions); + } +export type LoginMutationHookResult = ReturnType; +export type LoginMutationResult = Apollo.MutationResult; +export type LoginMutationOptions = Apollo.BaseMutationOptions; +export const GetCourseForIdDocument = gql` + query GetCourseForId($id: ID!, $year: String, $semester: String) { + course(id: $id) { + ...Course + } +} + ${CourseFragmentDoc}`; + +/** + * __useGetCourseForIdQuery__ + * + * To run a query within a React component, call `useGetCourseForIdQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCourseForIdQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCourseForIdQuery({ + * variables: { + * id: // value for 'id' + * year: // value for 'year' + * semester: // value for 'semester' + * }, + * }); + */ +export function useGetCourseForIdQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetCourseForIdDocument, baseOptions); + } +export function useGetCourseForIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetCourseForIdDocument, baseOptions); + } +export type GetCourseForIdQueryHookResult = ReturnType; +export type GetCourseForIdLazyQueryHookResult = ReturnType; +export type GetCourseForIdQueryResult = Apollo.QueryResult; +export const GetCourseForNameDocument = gql` + query GetCourseForName($abbreviation: String!, $courseNumber: String!, $year: String, $semester: String) { + allCourses(abbreviation: $abbreviation, courseNumber: $courseNumber, first: 1) { + edges { + node { + ...Course + } + } + } +} + ${CourseFragmentDoc}`; + +/** + * __useGetCourseForNameQuery__ + * + * To run a query within a React component, call `useGetCourseForNameQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCourseForNameQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCourseForNameQuery({ + * variables: { + * abbreviation: // value for 'abbreviation' + * courseNumber: // value for 'courseNumber' + * year: // value for 'year' + * semester: // value for 'semester' + * }, + * }); + */ +export function useGetCourseForNameQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetCourseForNameDocument, baseOptions); + } +export function useGetCourseForNameLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetCourseForNameDocument, baseOptions); + } +export type GetCourseForNameQueryHookResult = ReturnType; +export type GetCourseForNameLazyQueryHookResult = ReturnType; +export type GetCourseForNameQueryResult = Apollo.QueryResult; +export const GetCoursesForFilterDocument = gql` + query GetCoursesForFilter($playlists: String!) { + allCourses(inPlaylists: $playlists) { + edges { + node { + ...CourseOverview + } + } + } +} + ${CourseOverviewFragmentDoc}`; + +/** + * __useGetCoursesForFilterQuery__ + * + * To run a query within a React component, call `useGetCoursesForFilterQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCoursesForFilterQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCoursesForFilterQuery({ + * variables: { + * playlists: // value for 'playlists' + * }, + * }); + */ +export function useGetCoursesForFilterQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetCoursesForFilterDocument, baseOptions); + } +export function useGetCoursesForFilterLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetCoursesForFilterDocument, baseOptions); + } +export type GetCoursesForFilterQueryHookResult = ReturnType; +export type GetCoursesForFilterLazyQueryHookResult = ReturnType; +export type GetCoursesForFilterQueryResult = Apollo.QueryResult; +export const GetFiltersDocument = gql` + query GetFilters { + allPlaylists { + edges { + node { + ...Filter + } + } + } +} + ${FilterFragmentDoc}`; + +/** + * __useGetFiltersQuery__ + * + * To run a query within a React component, call `useGetFiltersQuery` and pass it any options that fit your needs. + * When your component renders, `useGetFiltersQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetFiltersQuery({ + * variables: { + * }, + * }); + */ +export function useGetFiltersQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetFiltersDocument, baseOptions); + } +export function useGetFiltersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetFiltersDocument, baseOptions); + } +export type GetFiltersQueryHookResult = ReturnType; +export type GetFiltersLazyQueryHookResult = ReturnType; +export type GetFiltersQueryResult = Apollo.QueryResult; +export const GetScheduleForIdDocument = gql` + query GetScheduleForId($id: ID!) { + schedule(id: $id) { + ...Schedule + } +} + ${ScheduleFragmentDoc}`; + +/** + * __useGetScheduleForIdQuery__ + * + * To run a query within a React component, call `useGetScheduleForIdQuery` and pass it any options that fit your needs. + * When your component renders, `useGetScheduleForIdQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetScheduleForIdQuery({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useGetScheduleForIdQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetScheduleForIdDocument, baseOptions); + } +export function useGetScheduleForIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetScheduleForIdDocument, baseOptions); + } +export type GetScheduleForIdQueryHookResult = ReturnType; +export type GetScheduleForIdLazyQueryHookResult = ReturnType; +export type GetScheduleForIdQueryResult = Apollo.QueryResult; +export const GetSchedulerCourseForIdDocument = gql` + query GetSchedulerCourseForId($id: ID!, $year: String, $semester: String) { + course(id: $id) { + ...SchedulerCourse + } +} + ${SchedulerCourseFragmentDoc}`; + +/** + * __useGetSchedulerCourseForIdQuery__ + * + * To run a query within a React component, call `useGetSchedulerCourseForIdQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSchedulerCourseForIdQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSchedulerCourseForIdQuery({ + * variables: { + * id: // value for 'id' + * year: // value for 'year' + * semester: // value for 'semester' + * }, + * }); + */ +export function useGetSchedulerCourseForIdQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetSchedulerCourseForIdDocument, baseOptions); + } +export function useGetSchedulerCourseForIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetSchedulerCourseForIdDocument, baseOptions); + } +export type GetSchedulerCourseForIdQueryHookResult = ReturnType; +export type GetSchedulerCourseForIdLazyQueryHookResult = ReturnType; +export type GetSchedulerCourseForIdQueryResult = Apollo.QueryResult; +export const GetSemestersDocument = gql` + query GetSemesters($name: String) { + allPlaylists(category: "semester", name: $name) { + edges { + node { + ...Filter + } + } + } +} + ${FilterFragmentDoc}`; + +/** + * __useGetSemestersQuery__ + * + * To run a query within a React component, call `useGetSemestersQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSemestersQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSemestersQuery({ + * variables: { + * name: // value for 'name' + * }, + * }); + */ +export function useGetSemestersQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetSemestersDocument, baseOptions); + } +export function useGetSemestersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetSemestersDocument, baseOptions); + } +export type GetSemestersQueryHookResult = ReturnType; +export type GetSemestersLazyQueryHookResult = ReturnType; +export type GetSemestersQueryResult = Apollo.QueryResult; +export const GetUserDocument = gql` + query GetUser { + user { + ...UserProfile + } +} + ${UserProfileFragmentDoc}`; + +/** + * __useGetUserQuery__ + * + * To run a query within a React component, call `useGetUserQuery` and pass it any options that fit your needs. + * When your component renders, `useGetUserQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetUserQuery({ + * variables: { + * }, + * }); + */ +export function useGetUserQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetUserDocument, baseOptions); + } +export function useGetUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetUserDocument, baseOptions); + } +export type GetUserQueryHookResult = ReturnType; +export type GetUserLazyQueryHookResult = ReturnType; +export type GetUserQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/frontend/src/views/About.tsx b/frontend/src/views/About.tsx index 9a4eb9459..b713dff27 100644 --- a/frontend/src/views/About.tsx +++ b/frontend/src/views/About.tsx @@ -7,7 +7,7 @@ import CurrentContributors from '../components/About/CurrentContributors'; import PastContributors from '../components/About/PastContributors'; import AboutCarousel from '../components/About/AboutCarousel'; -import growth from 'assets/svg/about/growth.svg'; +import growth from '../assets/svg/about/growth.svg'; import curiosity from 'assets/svg/about/curiosity.svg'; import passion from 'assets/svg/about/passion.svg'; From 085e9785b14cdd32d53cb89b3d6ad8c5dd6a3ae2 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Sun, 26 Mar 2023 21:25:52 -0700 Subject: [PATCH 02/11] finished implementing tabs with styling. may be some warnings when fetching data that does not exist --- frontend/src/app/Catalog/Catalog.tsx | 2 +- .../CatalogView/CatalogView.module.scss | 48 ++- .../app/Catalog/CatalogView/CatalogView.tsx | 328 ++++++++++++++---- .../app/Catalog/CatalogView/SectionTable.tsx | 7 +- .../components/GraphCard/GradesGraphCard.jsx | 2 +- frontend/src/redux/actions.js | 8 +- frontend/src/redux/reducers/grade.js | 10 +- 7 files changed, 326 insertions(+), 79 deletions(-) diff --git a/frontend/src/app/Catalog/Catalog.tsx b/frontend/src/app/Catalog/Catalog.tsx index dbb50dfe7..c4677b7a5 100644 --- a/frontend/src/app/Catalog/Catalog.tsx +++ b/frontend/src/app/Catalog/Catalog.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { CurrentFilters, SortOption } from './types'; import catalogService from './service'; import styles from './Catalog.module.scss'; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index b599930fb..a9442fa60 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -1,7 +1,7 @@ @-webkit-keyframes fadeIn { from { opacity: 0; - } + } to { opacity: 1; } @@ -66,6 +66,7 @@ display: flex; } } + } .modalButton { @@ -108,6 +109,49 @@ font-weight: 700; color: $bt-base-text; } + +} + +.gradesBox { + margin-top: 1%; +} + +.tabContainer { + margin-right: -30px; + margin-left: -30px; + padding-left: 30px; + white-space: nowrap; + overflow: scroll; +} + +.tabButton { + width: auto; + text-overflow: ellipsis; + margin-right: 3%; + background-color: white; + border-width: 0px; + border-top: thin; + border-left: thin; + border-right: thin; + border-bottom: 5px solid white; +} + +.tabButtonAct { + width: auto; + text-overflow: ellipsis; + background-color: white; + margin-right: 3%; + border-top: thin; + border-left: thin; + border-right: thin; + border-bottom: 5px solid #579EFF; + border-width: medium; +} + + + +.tabButton:hover { + background-color: #D3D3D3; } .pills { @@ -257,4 +301,4 @@ img { width: 14px; } -} +} \ No newline at end of file diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 72aa36949..298bddc3b 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -12,16 +12,31 @@ import { useHistory, useParams } from 'react-router'; import { sortSections } from 'utils/sections/sort'; import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; - +import { Tabs, Tab, Col } from 'react-bootstrap'; import styles from './CatalogView.module.scss'; -import { useSelector } from 'react-redux'; +import { connect, useDispatch, useSelector, MapStateToProps } from 'react-redux'; import SectionTable from './SectionTable'; import { getLatestSemester, Semester } from 'utils/playlists/semesters'; +import EnrollmentGraphCard from 'components/GraphCard/EnrollmentGraphCard'; +import GradesGraphCard from 'components/GraphCard/GradesGraphCard'; +import { gradeReset, enrollReset, fetchGradeContext, fetchEnrollContext, fetchGradeFromUrl, fetchEnrollFromUrl, fetchGradeData, fetchEnrollData } from 'redux/actions'; +import GradesGraph from 'components/Graphs/GradesGraph'; +import axios from 'axios'; +import BTLoader from 'components/Common/BTLoader'; +import store from 'redux/store'; +import EnrollmentGraph from 'components/Graphs/EnrollmentGraph'; interface CatalogViewProps { coursePreview: CourseFragment | null; setCurrentCourse: Dispatch>; setCurrentFilters: Dispatch>; + gradesData:any; + enrollmentData:any; + gradesGraphData:any; + enrollGraphData:any; + gradesSelectedCourses:any; + enrollSelectedCourses:any; + enrollContext:any; } const skeleton = [...Array(8).keys()]; @@ -49,24 +64,34 @@ var dict = new Map([ ]); const CatalogView = (props: CatalogViewProps) => { - const { coursePreview, setCurrentFilters, setCurrentCourse } = props; + + const { coursePreview, setCurrentFilters, setCurrentCourse, gradesData, enrollmentData, gradesGraphData, enrollGraphData, gradesSelectedCourses, enrollSelectedCourses, enrollContext } = props; const { abbreviation, courseNumber, semester } = useParams<{ abbreviation: string; courseNumber: string; semester: string; }>(); + const dispatch = useDispatch(); + const [course, setCourse] = useState(coursePreview); const [isOpen, setOpen] = useState(false); + const history = useHistory(); - const legacyId = useSelector( + var legacyId = useSelector( (state: any) => state.enrollment?.context?.courses?.find( (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber )?.id ?? null ); + const enrollPath = legacyId + ? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all` + : `/enrollment`; + + const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; + const [getCourse, { data, loading }] = useGetCourseForNameLazyQuery({ onCompleted: (data) => { const course = data.allCourses.edges[0].node; @@ -108,8 +133,64 @@ const CatalogView = (props: CatalogViewProps) => { setOpen(true); } }, [coursePreview, data]); + + + useEffect(() => { + if (course !== null) { + if (false && legacyId !== null) { + dispatch(gradeReset()) + dispatch(enrollReset()) + dispatch(fetchEnrollContext()); + dispatch(fetchGradeContext()); + // let temp = semester.split(' '); + // dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); + // axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { + // dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + // }); + } else { + dispatch(gradeReset()) + dispatch(enrollReset()) + dispatch(fetchEnrollContext()); + dispatch(fetchGradeContext()); + } + } + }, [course?.courseNumber]); + + useEffect(() => { + legacyId = (store.getState() as any).enrollment?.context?.courses?.find( + (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber + )?.id ?? null + + if (legacyId !== null) { + let temp = semester.split(' '); + dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); + axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { + dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + }); + } + + }, [enrollContext]) + + useEffect(() => { + if (course !== null) { + if (gradesSelectedCourses?.length == 1) { + dispatch(fetchGradeData(gradesSelectedCourses)); + } + } + }, [gradesSelectedCourses]) + + useEffect(() => { + if (course !== null) { + if (enrollSelectedCourses?.length == 1) { + dispatch(fetchEnrollData(enrollSelectedCourses)); + } + } + }, [enrollSelectedCourses]) + - const [playlists, sections] = useMemo(() => { + + + const [playlists, sections, links] = useMemo(() => { let playlists = null; let sections = null; // let semesters = null; @@ -128,34 +209,32 @@ const CatalogView = (props: CatalogViewProps) => { sections = sortSections(edges.map((e) => e.node)); } - console.log(semester) - if (false && sections !== null) { + let links:any = []; + if (sections !== null) { sections = sortSections(sections); for (var i = 0; i < sections.length; i++) { var stre = ''; let temp = semester.split(' '); var punctuation = ','; var regex = new RegExp('[' + punctuation + ']', 'g'); - var rmc = courseRef.abbreviation.replace(regex, ''); - stre = `https://classes.berkeley.edu/content/${semester[1]} - -${semester[0]} + var rmc = abbreviation.replace(regex, ''); + stre = `https://classes.berkeley.edu/content/${temp[1]} + -${temp[0]} -${rmc} - -${courseRef.courseNumber} + -${courseNumber} -${sections[i].sectionNumber} -${dict.get(sections[i].kind)} -${sections[i].sectionNumber}`; stre = stre.replace(/\s+/g, ''); - links.push(stre); } } - - - - // return [playlists ?? skeleton, sections ?? [], semesters]; - return [playlists ?? skeleton, sections ?? null]; + + return [playlists ?? skeleton, sections ?? null, links ?? null]; }, [course]); + + const handlePill = (pillItem: PlaylistType) => { setCurrentFilters((prev) => { if (['haas', 'ls', 'engineering', 'university'].includes(pillItem.category)) { @@ -177,20 +256,116 @@ const CatalogView = (props: CatalogViewProps) => { }); }; - const enrollPath = legacyId - ? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all` - : `/enrollment`; - - const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; + - // const playlists = course?.playlistSet.edges.map((e) => e?.node!); - // let sections = course?.sectionSet.edges.map((e) => e?.node!); - let links:any = []; - console.log(sections) - + const [hoveredClass, setHoveredClass] = useState(false); + const [updateMobileHover, setUpdateMobileHover] = useState(true); + function update(course:any, grade:any) { + const gradesData = (store.getState() as any).grade.gradesData; + if (course && gradesData && gradesData.length > 0) { + const selectedGrades = gradesData.filter((c:any) => course.id === c.id)[0]; + const hoverTotal = { + ...course, + ...selectedGrades, + hoverGrade: grade + }; + + setHoveredClass(hoverTotal); + } + } + + // Handler function for updating GradesInfoCard on hover with single course + function updateGraphHover(data:any) { + const { isTooltipActive, activeLabel } = data; + const selectedCourses = (store.getState() as any).grade.selectedCourses; + + const noBarMobile = updateMobileHover && window.innerWidth < 768; + + // Update the selected course if no bar is clicked if in mobile + if (isTooltipActive && (selectedCourses.length === 1 || noBarMobile)) { + const selectedCourse = selectedCourses[0]; + const grade = activeLabel; + update(selectedCourse, grade); + } + + // Update mobile hover records if there actually is a bar (then we only want updateBarHover to run) + setUpdateMobileHover({ updateMobileHover: true }); + } + + const [tab, setTab] = useState(0) + const openTab = () => { + + if (tab == 0) { + return ( + <> + + {sections && sections.length > 0 ? ( +
+ +
+ ) : !loading ? ( + There are no class times for the selected course. + ) : null} + + ) + } else if (tab == 1) { + return ( + <> + + {gradesData?.length > 0 ? +
+ +
+ : +
no grade data found
+ } + + ) + } else { + return ( + <> + +
+ {enrollReset()}} + isMobile={window.innerWidth < 768 ? true : false} + /> +
+ + ) + } + } + return (
{course && ( @@ -271,48 +446,69 @@ const CatalogView = (props: CatalogViewProps) => {

-
Class Times - {semester ?? ''}
- {sections && sections.length > 0 ? ( - - ) : !loading ? ( - There are no class times for the selected course. - ) : null} - - {/* - Redesigned catalog sections - - */} - - {/* Good feature whenever we want... -
Past Offerings
-
- {pastSemesters ? ( - pastSemesters.map((req) => ( - - )) - ) : ( - - )} -
*/} + {loading ? + + : +
+ + {openTab()} + + {/* + Redesigned catalog sections + + */ + /* Good feature whenever we want... +
Past Offerings
+
+ {pastSemesters ? ( + pastSemesters.map((req) => ( + + )) + ) : ( + + )} +
*/} +
+ } )}
); }; -export default memo(CatalogView); +const mapStateToProps = (state:any) => { + const gradesData = state.grade.gradesData + const enrollmentData = state.enrollment.enrollmentData + const gradesGraphData = state.grade.graphData + const enrollGraphData = state.enrollment.graphData + const gradesSelectedCourses = state.grade.selectedCourses + const enrollSelectedCourses = state.enrollment.selectedCourses + const enrollContext = state.enrollment.context.courses + return { + gradesData, + enrollmentData, + gradesGraphData, + enrollGraphData, + gradesSelectedCourses, + enrollSelectedCourses, + enrollContext + }; +}; + +export default connect(mapStateToProps)(memo(CatalogView)); + diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index 65d39eec4..86393ef4d 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -37,9 +37,10 @@ function findInstructor(instr: string | null): CSSProperties { type Props = { sections: SectionFragment[] | null; + links: any[]; }; -const SectionTable = ({ sections }: Props) => { +const SectionTable = ({ sections, links }: Props) => { return (
@@ -56,10 +57,10 @@ const SectionTable = ({ sections }: Props) => { - {sections?.map((section) => { + {sections?.map((section, i) => { return ( - {section.kind} + {section.kind} {section.ccn} {section.instructor} {section.startTime && section.endTime ? ( diff --git a/frontend/src/components/GraphCard/GradesGraphCard.jsx b/frontend/src/components/GraphCard/GradesGraphCard.jsx index 294cefb3c..e56b76adc 100644 --- a/frontend/src/components/GraphCard/GradesGraphCard.jsx +++ b/frontend/src/components/GraphCard/GradesGraphCard.jsx @@ -195,4 +195,4 @@ const mapStateToProps = (state) => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(GradesGraphCard); +export default connect(mapStateToProps, mapDispatchToProps)(GradesGraphCard); \ No newline at end of file diff --git a/frontend/src/redux/actions.js b/frontend/src/redux/actions.js index 4b5822e70..643d7e079 100644 --- a/frontend/src/redux/actions.js +++ b/frontend/src/redux/actions.js @@ -153,7 +153,10 @@ export function fetchGradeData(classData) { }); dispatch(updateGradeData(gradesData)); }, - (error) => console.log('An error occurred.', error) + (error) => { + console.log('An error occurred.', error) + return false; + } ); } @@ -260,7 +263,7 @@ export function fetchGradeFromUrl(url, history) { } }); }, - (error) => console.log('An error occurred.', error) + (error) => {console.log('An error occurred.', error)} ) .then(() => { if (success) { @@ -296,6 +299,7 @@ export function fetchGradeFromUrl(url, history) { export function fetchEnrollContext() { return async (dispatch, getState) => { // Avoid fetching enrollment data twice. + if (getState().enrollment.context?.courses) { return; } diff --git a/frontend/src/redux/reducers/grade.js b/frontend/src/redux/reducers/grade.js index 45cd6650e..2e566a491 100644 --- a/frontend/src/redux/reducers/grade.js +++ b/frontend/src/redux/reducers/grade.js @@ -30,19 +30,21 @@ export default function grade(state = initialState, action) { } case GRADE_ADD_COURSE: { const { formattedCourse } = action.payload; - return Object.assign({}, state, { + return { + ...state, selectedCourses: [...state.selectedCourses, formattedCourse], usedColorIds: [...state.usedColorIds, formattedCourse.colorId] - }); + }; } 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); - return Object.assign({}, state, { + return { + ...state, selectedCourses: updatedCourses, usedColorIds: updatedColors - }); + }; } case UPDATE_GRADE_DATA: { const { gradesData } = action.payload; From 176840725c50b6ded369dc89272991002b7d0444 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Thu, 13 Apr 2023 02:26:28 -0700 Subject: [PATCH 03/11] fixed stuff according to jayden's suggestions --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 130 ++++++++++++++++++ .../CatalogView/CatalogView.module.scss | 50 ++++++- .../app/Catalog/CatalogView/CatalogView.tsx | 100 ++++++++++++-- .../app/Catalog/CatalogView/SectionTable.tsx | 7 +- frontend/src/app/Catalog/service.ts | 58 ++++++++ frontend/src/app/Catalog/types.ts | 37 +++++ 6 files changed, 364 insertions(+), 18 deletions(-) create mode 100644 frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx new file mode 100644 index 000000000..cf8d85d06 --- /dev/null +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -0,0 +1,130 @@ +import EnrollmentGraphCard from 'components/GraphCard/EnrollmentGraphCard'; +import GradesGraph from 'components/Graphs/GradesGraph'; +import { SectionFragment } from 'graphql'; +import { useState } from 'react'; +import { enrollReset } from 'redux/actions'; +import store from 'redux/store'; +import styles from './CatalogView.module.scss'; +import SectionTable from './SectionTable'; + +interface CatalogTabsProps { + tab: Number; + semester: string; + course: any; + sections: SectionFragment[] | null; + links: string[]; + loading: boolean; + gradesGraphData: any; + gradesData: any; + setTab: Function; +} + +const CatalogTabs = (props: CatalogTabsProps) => { + const { tab, semester, course, sections, links, loading, gradesGraphData, gradesData, setTab } = props; + + const [hoveredClass, setHoveredClass] = useState(false); + const [updateMobileHover, setUpdateMobileHover] = useState(true); + + + function update(course:any, grade:any) { + const gradesData = (store.getState() as any).grade.gradesData; + if (course && gradesData && gradesData.length > 0) { + const selectedGrades = gradesData.filter((c:any) => course.id === c.id)[0]; + const hoverTotal = { + ...course, + ...selectedGrades, + hoverGrade: grade + }; + + setHoveredClass(hoverTotal); + } + } + + // Handler function for updating GradesInfoCard on hover with single course + function updateGraphHover(data:any) { + const { isTooltipActive, activeLabel } = data; + const selectedCourses = (store.getState() as any).grade.selectedCourses; + + const noBarMobile = updateMobileHover && window.innerWidth < 768; + + // Update the selected course if no bar is clicked if in mobile + if (isTooltipActive && (selectedCourses.length === 1 || noBarMobile)) { + const selectedCourse = selectedCourses[0]; + const grade = activeLabel; + update(selectedCourse, grade); + } + + // Update mobile hover records if there actually is a bar (then we only want updateBarHover to run) + setUpdateMobileHover({ updateMobileHover: true }); + } + + if (tab == 0) { + return ( +
+ + {sections && sections.length > 0 ? ( +
+ +
+ ) : !loading ? ( + There are no class times for the selected course. + ) : null} +
+ ) + } else if (tab == 1) { + return ( + <> + + {gradesData?.length > 0 ? +
+ +
+ : +
no grade data found
+ } + + ) + } else { + return ( + <> + +
+ {enrollReset()}} + isMobile={window.innerWidth < 768 ? true : false} + /> +
+ + ) + } + +} + +export default CatalogTabs; \ No newline at end of file diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index bb576282e..a9442fa60 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -1,7 +1,7 @@ @-webkit-keyframes fadeIn { from { opacity: 0; - } + } to { opacity: 1; } @@ -57,7 +57,7 @@ z-index: 300; width: 100vw; // Add size of fixed header. - padding-top: 30px; + padding-top: 57.11px + 30px; &[data-modal='false'] { display: none; } @@ -66,6 +66,7 @@ display: flex; } } + } .modalButton { @@ -108,6 +109,49 @@ font-weight: 700; color: $bt-base-text; } + +} + +.gradesBox { + margin-top: 1%; +} + +.tabContainer { + margin-right: -30px; + margin-left: -30px; + padding-left: 30px; + white-space: nowrap; + overflow: scroll; +} + +.tabButton { + width: auto; + text-overflow: ellipsis; + margin-right: 3%; + background-color: white; + border-width: 0px; + border-top: thin; + border-left: thin; + border-right: thin; + border-bottom: 5px solid white; +} + +.tabButtonAct { + width: auto; + text-overflow: ellipsis; + background-color: white; + margin-right: 3%; + border-top: thin; + border-left: thin; + border-right: thin; + border-bottom: 5px solid #579EFF; + border-width: medium; +} + + + +.tabButton:hover { + background-color: #D3D3D3; } .pills { @@ -257,4 +301,4 @@ img { width: 14px; } -} +} \ No newline at end of file diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index d370ddbca..e7a185c81 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -14,8 +14,13 @@ import Skeleton from 'react-loading-skeleton'; import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; -import { useSelector } from 'react-redux'; -import SectionTable from './SectionTable'; +import { useDispatch, useSelector } from 'react-redux'; +import BTLoader from 'components/Common/BTLoader'; +import { enrollReset, fetchEnrollContext, fetchEnrollData, fetchEnrollFromUrl, fetchGradeContext, fetchGradeData, fetchGradeFromUrl, gradeReset } from 'redux/actions'; +import axios from 'axios'; +import store from 'redux/store'; +import CatalogTabs from './CatalogTabs'; +import { State } from '../types'; interface CatalogViewProps { coursePreview: CourseFragment | null; @@ -37,6 +42,8 @@ const CatalogView = (props: CatalogViewProps) => { const [isOpen, setOpen] = useState(false); const history = useHistory(); + const dispatch = useDispatch(); + const legacyId = useSelector( (state: any) => state.enrollment?.context?.courses?.find( @@ -86,7 +93,63 @@ const CatalogView = (props: CatalogViewProps) => { } }, [coursePreview, data]); - const [playlists, sections] = useMemo(() => { + const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); + + const enrollmentData = useSelector((state: State) => state.enrollment?.enrollmentData ?? null); + + const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); + + const enrollGraphData = useSelector((state: State) => state.enrollment.graphData ?? null); + + const gradesSelectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); + + const enrollSelectedCourses = useSelector((state: State) => state.enrollment.selectedCourses ?? null); + + const enrollContext = useSelector((state: State) => state.enrollment.context.courses ?? null); + + useEffect(() => { + if (course !== null) { + if (legacyId !== null) { + setTab(0) + dispatch(gradeReset()) + dispatch(enrollReset()) + dispatch(fetchEnrollContext()); + dispatch(fetchGradeContext()); + + let legacyId = (store.getState() as any).enrollment?.context?.courses?.find( + (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber + )?.id ?? null + + if (legacyId !== null) { + let temp = semester.split(' '); + dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); + axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { + dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + }); + } + } + } + }, [course?.courseNumber]); + + + useEffect(() => { + //console.log(gradesSelectedCourses) + if (course !== null) { + if (gradesSelectedCourses?.length == 1) { + dispatch(fetchGradeData(gradesSelectedCourses)); + } + } + }, [gradesSelectedCourses]) + + useEffect(() => { + if (course !== null) { + if (enrollSelectedCourses?.length == 1) { + dispatch(fetchEnrollData(enrollSelectedCourses)); + } + } + }, [enrollSelectedCourses]) + + const [playlists, sections, links] = useMemo(() => { let playlists = null; let sections = null; // let semesters = null; @@ -105,10 +168,14 @@ const CatalogView = (props: CatalogViewProps) => { sections = sortSections(edges.map((e) => e.node)); } + let links:string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); + // return [playlists ?? skeleton, sections ?? [], semesters]; - return [playlists ?? skeleton, sections ?? null]; + return [playlists ?? skeleton, sections ?? null, links ?? null]; }, [course]); + const [tab, setTab] = useState(0); + const handlePill = (pillItem: PlaylistType) => { setCurrentFilters((prev) => { if (['haas', 'ls', 'engineering', 'university'].includes(pillItem.category)) { @@ -134,7 +201,7 @@ const CatalogView = (props: CatalogViewProps) => { ? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all` : `/enrollment`; - const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; + const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`; return (
@@ -216,13 +283,22 @@ const CatalogView = (props: CatalogViewProps) => {

-
Class Times - {semester ?? ''}
- {sections && sections.length > 0 ? ( - - ) : !loading ? ( - There are no class times for the selected course. - ) : null} - + {loading ? + + : +
+ {} +
+ } {/* Redesigned catalog sections diff --git a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx index f3b70bf21..27b45be0d 100644 --- a/frontend/src/app/Catalog/CatalogView/SectionTable.tsx +++ b/frontend/src/app/Catalog/CatalogView/SectionTable.tsx @@ -37,9 +37,10 @@ function findInstructor(instr: string | null): CSSProperties { type Props = { sections: SectionFragment[] | null; + links: string[]; }; -const SectionTable = ({ sections }: Props) => { +const SectionTable = ({ sections, links }: Props) => { return (
@@ -56,10 +57,10 @@ const SectionTable = ({ sections }: Props) => { - {sections?.map((section) => { + {sections?.map((section, i) => { return ( - {section.kind} + {section.kind} {section.ccn} {section.instructor} {section.startTime && section.endTime ? ( diff --git a/frontend/src/app/Catalog/service.ts b/frontend/src/app/Catalog/service.ts index e7cb92bb0..b89244cdd 100644 --- a/frontend/src/app/Catalog/service.ts +++ b/frontend/src/app/Catalog/service.ts @@ -1,4 +1,5 @@ import { FilterFragment, GetFiltersQuery, PlaylistType } from 'graphql'; +import { sortSections } from 'utils/sections/sort'; import styles from './CatalogList/CatalogList.module.scss'; import { @@ -110,6 +111,62 @@ const processFilterData = (data?: GetFiltersQuery) => { return filters; }; + +/** + * @param sections The sections of the course. Every item in sections gets mapped to a url + * @param semester The semester of the course. String form. Used in url + * @param abbreviation The course abbreviation of the course. String form. Used in url + * @param courseNumber The course number of the course. String form. Used in url + * @returns A list of links to the official UC Berkeley catalog for each course listed on the catalog table + */ +const getLinks = (sections: any, semester: string, abbreviation: string, courseNumber: string) => { + + var dict = new Map([ + ['Field Work', 'FLD'], + ['Session', 'SES'], + ['Colloquium', 'COL'], + ['Recitation', 'REC'], + ['Internship', 'INT'], + ['Studio', 'STD'], + ['Demonstration', 'dem'], + ['Web-based Discussion', 'WBD'], + ['Discussion', 'DIS'], + ['Tutorial', 'TUT'], + ['Clinic', 'CLN'], + ['Independent Study', 'IND'], + ['Self-paced', 'SLF'], + ['Seminar', 'SEM'], + ['Lecture', 'LEC'], + ['Web-based Lecture', 'WBL'], + ['Web-Based Lecture', 'WBL'], + ['Directed Group Study', 'GRP'], + ['Laboratory', 'LAB'], + ]); + + let links:string[] = []; + if (sections !== null) { + sections = sortSections(sections); + for (var i = 0; i < sections.length; i++) { + var stre = ''; + let temp = semester.split(' '); + var punctuation = ','; + var regex = new RegExp('[' + punctuation + ']', 'g'); + var rmc = abbreviation.replace(regex, ''); + stre = `https://classes.berkeley.edu/content/${temp[1]} + -${temp[0]} + -${rmc} + -${courseNumber} + -${sections[i].sectionNumber} + -${dict.get(sections[i].kind)} + -${sections[i].sectionNumber}`; + stre = stre.replace(/\s+/g, ''); + links.push(stre); + } + } + return links +} + + /** * * @param filterItems an empty filter template @@ -217,6 +274,7 @@ export default { SORT_OPTIONS, INITIAL_FILTERS, processFilterData, + getLinks, putFilterOptions, sortByName, sortSemestersByLatest, diff --git a/frontend/src/app/Catalog/types.ts b/frontend/src/app/Catalog/types.ts index 9d6d9e94c..91794e4e3 100644 --- a/frontend/src/app/Catalog/types.ts +++ b/frontend/src/app/Catalog/types.ts @@ -56,3 +56,40 @@ export type FilterTemplate = { placeholder: string; }; }; + +export type State = { + grade: GradeState; + enrollment: EnrollmentState; + authReducer: any; + common: any; +} + +export type GradeState = { + context: GradeContext, + selectedCourses: any[], + gradesData: any[], + graphData: any[], + sections: any[], + selectPrimary: string; + selectSecondary: string; + usedColorIds: Number[]; +} + +export type GradeContext = { + courses: any[]; +} + +export type EnrollmentState = { + context: EnrollmentContext, + selectedCourses: any[], + enrollmentData: any[], + graphData: any[], + sections: any[], + selectPrimary: string; + selectSecondary: string; + usedColorIds: Number[]; +} + +export type EnrollmentContext = { + courses: any[]; +} \ No newline at end of file From 640745398859cee09c168b87b105b74d5e96067a Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Thu, 13 Apr 2023 02:29:38 -0700 Subject: [PATCH 04/11] some more changes --- .../app/Catalog/CatalogView/CatalogView.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index e7a185c81..f275dc5b9 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -95,19 +95,23 @@ const CatalogView = (props: CatalogViewProps) => { const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); + //may need in the future if graphs are rewritten const enrollmentData = useSelector((state: State) => state.enrollment?.enrollmentData ?? null); const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); + //may need in the future if graphs are rewritten const enrollGraphData = useSelector((state: State) => state.enrollment.graphData ?? null); const gradesSelectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); const enrollSelectedCourses = useSelector((state: State) => state.enrollment.selectedCourses ?? null); + //may need in the future if graphs are rewritten const enrollContext = useSelector((state: State) => state.enrollment.context.courses ?? null); useEffect(() => { + console.log(courseNumber) if (course !== null) { if (legacyId !== null) { setTab(0) @@ -116,6 +120,20 @@ const CatalogView = (props: CatalogViewProps) => { dispatch(fetchEnrollContext()); dispatch(fetchGradeContext()); + if (legacyId !== null) { + let temp = semester.split(' '); + dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); + axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { + dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + }); + } + } else { + setTab(0) + dispatch(gradeReset()) + dispatch(enrollReset()) + dispatch(fetchEnrollContext()); + dispatch(fetchGradeContext()); + let legacyId = (store.getState() as any).enrollment?.context?.courses?.find( (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber )?.id ?? null From a6d3c3f4b45460048c6dd490372108e0da96f5e9 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Tue, 18 Apr 2023 18:42:17 -0700 Subject: [PATCH 05/11] fixed jadyn's suggestions part 2 --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 22 +-- .../app/Catalog/CatalogView/CatalogView.tsx | 126 ++++-------------- 2 files changed, 38 insertions(+), 110 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index cf8d85d06..69a2c2b04 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -1,29 +1,35 @@ import EnrollmentGraphCard from 'components/GraphCard/EnrollmentGraphCard'; import GradesGraph from 'components/Graphs/GradesGraph'; -import { SectionFragment } from 'graphql'; +import { CourseFragment, SectionFragment } from 'graphql'; +import catalogService from '../service'; import { useState } from 'react'; import { enrollReset } from 'redux/actions'; import store from 'redux/store'; import styles from './CatalogView.module.scss'; import SectionTable from './SectionTable'; +import { useSelector } from 'react-redux'; +import { State } from '../types'; interface CatalogTabsProps { - tab: Number; semester: string; - course: any; + course: CourseFragment | null; sections: SectionFragment[] | null; - links: string[]; loading: boolean; - gradesGraphData: any; - gradesData: any; - setTab: Function; + abbreviation: string; + courseNumber: string; } const CatalogTabs = (props: CatalogTabsProps) => { - const { tab, semester, course, sections, links, loading, gradesGraphData, gradesData, setTab } = props; + const { semester, course, sections, loading, abbreviation, courseNumber } = props; const [hoveredClass, setHoveredClass] = useState(false); const [updateMobileHover, setUpdateMobileHover] = useState(true); + const [tab, setTab] = useState(0); + + const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); + const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); + + const links:string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); function update(course:any, grade:any) { diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index e382e27ea..38f2e47d1 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -4,7 +4,6 @@ import chart from 'assets/svg/catalog/chart.svg'; import book from 'assets/svg/catalog/book.svg'; import launch from 'assets/svg/catalog/launch.svg'; import { ReactComponent as BackArrow } from 'assets/img/images/catalog/backarrow.svg'; -import catalogService from '../service'; import { applyIndicatorPercent, applyIndicatorGrade, formatUnits } from 'utils/utils'; import { CourseFragment, PlaylistType, useGetCourseForNameLazyQuery } from 'graphql'; import { CurrentFilters } from 'app/Catalog/types'; @@ -15,10 +14,10 @@ import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useDispatch, useSelector } from 'react-redux'; import BTLoader from 'components/Common/BTLoader'; -import { enrollReset, fetchEnrollContext, fetchEnrollData, fetchEnrollFromUrl, fetchGradeContext, fetchGradeData, fetchGradeFromUrl, gradeReset } from 'redux/actions'; +import { enrollReset, fetchEnrollData, fetchEnrollFromUrl, fetchGradeData, fetchGradeFromUrl, gradeReset } from 'redux/actions'; import axios from 'axios'; -import store from 'redux/store'; import CatalogTabs from './CatalogTabs'; +import catalogService from '../service'; import { State } from '../types'; interface CatalogViewProps { @@ -101,105 +100,35 @@ const CatalogView = (props: CatalogViewProps) => { } }, [coursePreview, data]); - - useEffect(() => { - if (course !== null) { - if (false && legacyId !== null) { - dispatch(gradeReset()) - dispatch(enrollReset()) - dispatch(fetchEnrollContext()); - dispatch(fetchGradeContext()); - // let temp = semester.split(' '); - // dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); - // axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { - // dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) - // }); - } else { - dispatch(gradeReset()) - dispatch(enrollReset()) - dispatch(fetchEnrollContext()); - dispatch(fetchGradeContext()); - } - } - }, [course?.courseNumber]); - - - const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); - - //may need in the future if graphs are rewritten - const enrollmentData = useSelector((state: State) => state.enrollment?.enrollmentData ?? null); - - const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); - - //may need in the future if graphs are rewritten - const enrollGraphData = useSelector((state: State) => state.enrollment.graphData ?? null); - const gradesSelectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); const enrollSelectedCourses = useSelector((state: State) => state.enrollment.selectedCourses ?? null); - - //may need in the future if graphs are rewritten - const enrollContext = useSelector((state: State) => state.enrollment.context.courses ?? null); useEffect(() => { - console.log(courseNumber) - if (course !== null) { - if (legacyId !== null) { - setTab(0) - dispatch(gradeReset()) - dispatch(enrollReset()) - dispatch(fetchEnrollContext()); - dispatch(fetchGradeContext()); - - if (legacyId !== null) { - let temp = semester.split(' '); - dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); - axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { - dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) - }); - } - } else { - setTab(0) - dispatch(gradeReset()) - dispatch(enrollReset()) - dispatch(fetchEnrollContext()); - dispatch(fetchGradeContext()); - - let legacyId = (store.getState() as any).enrollment?.context?.courses?.find( - (c: any) => c.abbreviation === abbreviation && c.course_number === courseNumber - )?.id ?? null - - if (legacyId !== null) { - let temp = semester.split(' '); - dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); - axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { - dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) - }); - } - } - } - }, [course?.courseNumber]); - + dispatch(gradeReset()) + dispatch(enrollReset()) + }, [course]) useEffect(() => { - //console.log(gradesSelectedCourses) - if (course !== null) { - if (gradesSelectedCourses?.length == 1) { - dispatch(fetchGradeData(gradesSelectedCourses)); + if (course?.courseNumber !== null && legacyId !== null) { + const temp = semester.split(' '); + if (enrollSelectedCourses?.length == 0 && gradesSelectedCourses?.length == 0) { + dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); + axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { + dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + }); } - } - }, [gradesSelectedCourses]) - - useEffect(() => { - if (course !== null) { if (enrollSelectedCourses?.length == 1) { dispatch(fetchEnrollData(enrollSelectedCourses)); } + if (gradesSelectedCourses?.length == 1) { + dispatch(fetchGradeData(gradesSelectedCourses)); + } } - }, [enrollSelectedCourses]) + }, [course, legacyId, gradesSelectedCourses, enrollSelectedCourses]) - const [playlists, sections, links] = useMemo(() => { + const [playlists, sections] = useMemo(() => { let playlists = null; let sections = null; // let semesters = null; @@ -218,15 +147,10 @@ const CatalogView = (props: CatalogViewProps) => { sections = sortSections(edges.map((e) => e.node)); } - - let links:string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); - // return [playlists ?? skeleton, sections ?? [], semesters]; - return [playlists ?? skeleton, sections ?? null, links ?? null]; + return [playlists ?? skeleton, sections ?? null]; }, [course]); - const [tab, setTab] = useState(0); - const handlePill = (pillItem: PlaylistType) => { setCurrentFilters((prev) => { if (['haas', 'ls', 'engineering', 'university'].includes(pillItem.category)) { @@ -333,16 +257,14 @@ const CatalogView = (props: CatalogViewProps) => { :
- {} + sections={sections} + loading={loading} + abbreviation={abbreviation} + courseNumber={courseNumber} + />} {/* Redesigned catalog sections From 6655338707db46f40a41dfccbd3e78a66fd9a419 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Wed, 19 Apr 2023 16:28:15 -0700 Subject: [PATCH 06/11] fixed format --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 76 ++++++++++++------- .../app/Catalog/CatalogView/CatalogView.tsx | 67 +++++++++------- 2 files changed, 86 insertions(+), 57 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index 69a2c2b04..961d821ba 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -29,13 +29,12 @@ const CatalogTabs = (props: CatalogTabsProps) => { const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); - const links:string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); - + const links: string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); - function update(course:any, grade:any) { + function update(course: any, grade: any) { const gradesData = (store.getState() as any).grade.gradesData; if (course && gradesData && gradesData.length > 0) { - const selectedGrades = gradesData.filter((c:any) => course.id === c.id)[0]; + const selectedGrades = gradesData.filter((c: any) => course.id === c.id)[0]; const hoverTotal = { ...course, ...selectedGrades, @@ -47,7 +46,7 @@ const CatalogTabs = (props: CatalogTabsProps) => { } // Handler function for updating GradesInfoCard on hover with single course - function updateGraphHover(data:any) { + function updateGraphHover(data: any) { const { isTooltipActive, activeLabel } = data; const selectedCourses = (store.getState() as any).grade.selectedCourses; @@ -63,40 +62,54 @@ const CatalogTabs = (props: CatalogTabsProps) => { // Update mobile hover records if there actually is a bar (then we only want updateBarHover to run) setUpdateMobileHover({ updateMobileHover: true }); } - + if (tab == 0) { return (
{sections && sections.length > 0 ? (
) : !loading ? ( - There are no class times for the selected course. + + There are no class times for the selected course. + ) : null}
- ) + ); } else if (tab == 1) { return ( <> - {gradesData?.length > 0 ? + {gradesData?.length > 0 ? (
{ graphEmpty={false} />
- : + ) : (
no grade data found
- } + )} - ) + ); } else { return ( - <> + <>
{enrollReset()}} + updateClassCardEnrollment={() => { + enrollReset(); + }} isMobile={window.innerWidth < 768 ? true : false} />
- ) + ); } - -} +}; -export default CatalogTabs; \ No newline at end of file +export default CatalogTabs; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 38f2e47d1..4bbf7ce05 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -14,7 +14,14 @@ import ReadMore from './ReadMore'; import styles from './CatalogView.module.scss'; import { useDispatch, useSelector } from 'react-redux'; import BTLoader from 'components/Common/BTLoader'; -import { enrollReset, fetchEnrollData, fetchEnrollFromUrl, fetchGradeData, fetchGradeFromUrl, gradeReset } from 'redux/actions'; +import { + enrollReset, + fetchEnrollData, + fetchEnrollFromUrl, + fetchGradeData, + fetchGradeFromUrl, + gradeReset +} from 'redux/actions'; import axios from 'axios'; import CatalogTabs from './CatalogTabs'; import catalogService from '../service'; @@ -28,10 +35,8 @@ interface CatalogViewProps { const skeleton = [...Array(8).keys()]; - const CatalogView = (props: CatalogViewProps) => { - - const { coursePreview, setCurrentFilters, setCurrentCourse} = props; + const { coursePreview, setCurrentFilters, setCurrentCourse } = props; const { abbreviation, courseNumber, semester } = useParams<{ abbreviation: string; courseNumber: string; @@ -99,15 +104,17 @@ const CatalogView = (props: CatalogViewProps) => { setOpen(true); } }, [coursePreview, data]); - + const gradesSelectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); - - const enrollSelectedCourses = useSelector((state: State) => state.enrollment.selectedCourses ?? null); + + const enrollSelectedCourses = useSelector( + (state: State) => state.enrollment.selectedCourses ?? null + ); useEffect(() => { - dispatch(gradeReset()) - dispatch(enrollReset()) - }, [course]) + dispatch(gradeReset()); + dispatch(enrollReset()); + }, [course]); useEffect(() => { if (course?.courseNumber !== null && legacyId !== null) { @@ -115,7 +122,11 @@ const CatalogView = (props: CatalogViewProps) => { if (enrollSelectedCourses?.length == 0 && gradesSelectedCourses?.length == 0) { dispatch(fetchGradeFromUrl(`/grades/0-${legacyId}-all-all`)); axios.get(`/api/enrollment/sections/${legacyId}/`).then((res) => { - dispatch(fetchEnrollFromUrl(`/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}`)) + dispatch( + fetchEnrollFromUrl( + `/enrollment/0-${legacyId}-${temp[0]}-${temp[1]}-${res.data[0].sections[0].section_id}` + ) + ); }); } if (enrollSelectedCourses?.length == 1) { @@ -125,8 +136,7 @@ const CatalogView = (props: CatalogViewProps) => { dispatch(fetchGradeData(gradesSelectedCourses)); } } - }, [course, legacyId, gradesSelectedCourses, enrollSelectedCourses]) - + }, [course, legacyId, gradesSelectedCourses, enrollSelectedCourses]); const [playlists, sections] = useMemo(() => { let playlists = null; @@ -252,19 +262,20 @@ const CatalogView = (props: CatalogViewProps) => {

- {loading ? - - : + {loading ? ( + + ) : (
- - {} + { + + } {/* Redesigned catalog sections @@ -297,13 +308,11 @@ const CatalogView = (props: CatalogViewProps) => { )}
*/}
- } + )} )}
); }; - -export default (memo(CatalogView)); - +export default memo(CatalogView); From 5ca658b40ef0125c94967fd08d63855d81bb71f9 Mon Sep 17 00:00:00 2001 From: Jaden Date: Wed, 19 Apr 2023 17:32:48 -0700 Subject: [PATCH 07/11] refactor a few things --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 155 +++++++++--------- .../CatalogView/CatalogView.module.scss | 37 ++--- .../app/Catalog/CatalogView/CatalogView.tsx | 29 ++-- 3 files changed, 101 insertions(+), 120 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index 961d821ba..03e635ffd 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -1,14 +1,15 @@ import EnrollmentGraphCard from 'components/GraphCard/EnrollmentGraphCard'; import GradesGraph from 'components/Graphs/GradesGraph'; -import { CourseFragment, SectionFragment } from 'graphql'; +import { CourseFragment, GradeType, SectionFragment } from 'graphql'; import catalogService from '../service'; import { useState } from 'react'; import { enrollReset } from 'redux/actions'; -import store from 'redux/store'; import styles from './CatalogView.module.scss'; import SectionTable from './SectionTable'; import { useSelector } from 'react-redux'; import { State } from '../types'; +import { current } from 'lib/contributors'; +import BTLoader from 'components/Common/BTLoader'; interface CatalogTabsProps { semester: string; @@ -19,20 +20,46 @@ interface CatalogTabsProps { courseNumber: string; } +const tabs = [ + { + title: 'Class Times' + }, + { + title: 'Grades' + }, + { + title: 'Enrollment' + } +]; + +{ + /* + + */ +} + const CatalogTabs = (props: CatalogTabsProps) => { const { semester, course, sections, loading, abbreviation, courseNumber } = props; - const [hoveredClass, setHoveredClass] = useState(false); - const [updateMobileHover, setUpdateMobileHover] = useState(true); + const [hoveredClass, setHoveredClass] = useState(false); + const [updateMobileHover, setUpdateMobileHover] = useState(true); const [tab, setTab] = useState(0); const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); + const selectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); const links: string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); - function update(course: any, grade: any) { - const gradesData = (store.getState() as any).grade.gradesData; + console.log('rerender'); + + function update(course: CourseFragment, grade: GradeType) { if (course && gradesData && gradesData.length > 0) { const selectedGrades = gradesData.filter((c: any) => course.id === c.id)[0]; const hoverTotal = { @@ -48,8 +75,6 @@ const CatalogTabs = (props: CatalogTabsProps) => { // Handler function for updating GradesInfoCard on hover with single course function updateGraphHover(data: any) { const { isTooltipActive, activeLabel } = data; - const selectedCourses = (store.getState() as any).grade.selectedCourses; - const noBarMobile = updateMobileHover && window.innerWidth < 768; // Update the selected course if no bar is clicked if in mobile @@ -63,46 +88,25 @@ const CatalogTabs = (props: CatalogTabsProps) => { setUpdateMobileHover({ updateMobileHover: true }); } - if (tab == 0) { - return ( -
- - {sections && sections.length > 0 ? ( + const renderTabs = (currentTab: number) => { + if (loading) { + return ; + } + + switch (currentTab) { + case 0: + return (
- + {sections && sections.length > 0 ? ( + + ) : ( + 'There are no class times for the selected course.' + )}
- ) : !loading ? ( - - There are no class times for the selected course. - - ) : null} -
- ); - } else if (tab == 1) { - return ( - <> - - {gradesData?.length > 0 ? ( + ); + + case 1: + return gradesData?.length > 0 ? (
{
) : (
no grade data found
- )} - - ); - } else { - return ( - <> - -
- { - enrollReset(); - }} - isMobile={window.innerWidth < 768 ? true : false} - /> -
- - ); - } + ))} + + {renderTabs(tab)} + + ); }; export default CatalogTabs; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss index a9442fa60..3134ef295 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.module.scss @@ -1,7 +1,7 @@ @-webkit-keyframes fadeIn { from { opacity: 0; - } + } to { opacity: 1; } @@ -66,7 +66,6 @@ display: flex; } } - } .modalButton { @@ -109,7 +108,6 @@ font-weight: 700; color: $bt-base-text; } - } .gradesBox { @@ -117,41 +115,28 @@ } .tabContainer { - margin-right: -30px; - margin-left: -30px; - padding-left: 30px; + display: flex; white-space: nowrap; - overflow: scroll; + overflow: auto; + margin-bottom: 20px; } .tabButton { width: auto; text-overflow: ellipsis; - margin-right: 3%; background-color: white; border-width: 0px; - border-top: thin; - border-left: thin; - border-right: thin; - border-bottom: 5px solid white; + border-bottom: 2px solid white; + padding: 6px 12px; + transition-duration: 0.3s; } -.tabButtonAct { - width: auto; - text-overflow: ellipsis; - background-color: white; - margin-right: 3%; - border-top: thin; - border-left: thin; - border-right: thin; - border-bottom: 5px solid #579EFF; - border-width: medium; +.selected { + border-bottom: 2px solid #579eff; } - - .tabButton:hover { - background-color: #D3D3D3; + background-color: $bt-button-background; } .pills { @@ -301,4 +286,4 @@ img { width: 14px; } -} \ No newline at end of file +} diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index 4bbf7ce05..c352f0d47 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -262,26 +262,19 @@ const CatalogView = (props: CatalogViewProps) => {

- {loading ? ( - - ) : ( -
- { - - } - - {/* + + {/* Redesigned catalog sections */ - /* Good feature whenever we want... + /* Good feature whenever we want...
Past Offerings
{pastSemesters ? ( @@ -307,8 +300,6 @@ const CatalogView = (props: CatalogViewProps) => { /> )}
*/} -
- )} )} From e44eee951b6e6f4c7819435da0a46520c794ea7d Mon Sep 17 00:00:00 2001 From: Jaden Date: Wed, 19 Apr 2023 23:15:04 -0700 Subject: [PATCH 08/11] clean up --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 29 +----------- frontend/src/app/Catalog/service.ts | 45 ++++++++++--------- 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index 03e635ffd..301e51529 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -8,7 +8,6 @@ import styles from './CatalogView.module.scss'; import SectionTable from './SectionTable'; import { useSelector } from 'react-redux'; import { State } from '../types'; -import { current } from 'lib/contributors'; import BTLoader from 'components/Common/BTLoader'; interface CatalogTabsProps { @@ -20,30 +19,6 @@ interface CatalogTabsProps { courseNumber: string; } -const tabs = [ - { - title: 'Class Times' - }, - { - title: 'Grades' - }, - { - title: 'Enrollment' - } -]; - -{ - /* - - */ -} - const CatalogTabs = (props: CatalogTabsProps) => { const { semester, course, sections, loading, abbreviation, courseNumber } = props; @@ -57,11 +32,9 @@ const CatalogTabs = (props: CatalogTabsProps) => { const links: string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); - console.log('rerender'); - function update(course: CourseFragment, grade: GradeType) { if (course && gradesData && gradesData.length > 0) { - const selectedGrades = gradesData.filter((c: any) => course.id === c.id)[0]; + const selectedGrades = gradesData.filter((c: CourseFragment) => course.id === c.id)[0]; const hoverTotal = { ...course, ...selectedGrades, diff --git a/frontend/src/app/Catalog/service.ts b/frontend/src/app/Catalog/service.ts index b89244cdd..fc07cd763 100644 --- a/frontend/src/app/Catalog/service.ts +++ b/frontend/src/app/Catalog/service.ts @@ -1,4 +1,4 @@ -import { FilterFragment, GetFiltersQuery, PlaylistType } from 'graphql'; +import { FilterFragment, GetFiltersQuery, PlaylistType, SectionFragment } from 'graphql'; import { sortSections } from 'utils/sections/sort'; import styles from './CatalogList/CatalogList.module.scss'; @@ -119,9 +119,15 @@ const processFilterData = (data?: GetFiltersQuery) => { * @param courseNumber The course number of the course. String form. Used in url * @returns A list of links to the official UC Berkeley catalog for each course listed on the catalog table */ -const getLinks = (sections: any, semester: string, abbreviation: string, courseNumber: string) => { +const getLinks = ( + sections: SectionFragment[] | null, + semester: string, + abbreviation: string, + courseNumber: string +) => { + if (!sections) return []; - var dict = new Map([ + const dict = new Map([ ['Field Work', 'FLD'], ['Session', 'SES'], ['Colloquium', 'COL'], @@ -140,32 +146,31 @@ const getLinks = (sections: any, semester: string, abbreviation: string, courseN ['Web-based Lecture', 'WBL'], ['Web-Based Lecture', 'WBL'], ['Directed Group Study', 'GRP'], - ['Laboratory', 'LAB'], - ]); + ['Laboratory', 'LAB'] + ]); - let links:string[] = []; - if (sections !== null) { + const links: string[] = []; + + if (sections) { sections = sortSections(sections); - for (var i = 0; i < sections.length; i++) { - var stre = ''; - let temp = semester.split(' '); - var punctuation = ','; - var regex = new RegExp('[' + punctuation + ']', 'g'); - var rmc = abbreviation.replace(regex, ''); - stre = `https://classes.berkeley.edu/content/${temp[1]} + for (let i = 0; i < sections.length; i++) { + const temp = semester.split(' '); + const regex = new RegExp('[' + ',' + ']', 'g'); + const rmc = abbreviation.replace(regex, ''); + + const res = `https://classes.berkeley.edu/content/${temp[1]} -${temp[0]} -${rmc} -${courseNumber} -${sections[i].sectionNumber} -${dict.get(sections[i].kind)} - -${sections[i].sectionNumber}`; - stre = stre.replace(/\s+/g, ''); - links.push(stre); + -${sections[i].sectionNumber}`.replace(/\s+/g, ''); + + links.push(res); } } - return links -} - + return links; +}; /** * From cec0c2d00189d81bd4a57bc35de14198ea21e3ed Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Wed, 26 Apr 2023 16:37:24 -0700 Subject: [PATCH 09/11] resolve merge conflicts --- frontend/src/views/About.tsx | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/frontend/src/views/About.tsx b/frontend/src/views/About.tsx index eda9b313b..36a98b020 100644 --- a/frontend/src/views/About.tsx +++ b/frontend/src/views/About.tsx @@ -8,31 +8,6 @@ import CurrentContributors from '../components/About/CurrentContributors'; import PastContributors from '../components/About/PastContributors'; import AboutCarousel from '../components/About/AboutCarousel'; -<<<<<<< HEAD -======= -import growth from '../assets/svg/about/growth.svg'; -import curiosity from 'assets/svg/about/curiosity.svg'; -import passion from 'assets/svg/about/passion.svg'; - -const values = [ - { - svg: growth, - name: 'Growth', - desc: 'You’ll grow your technical skills as you tackle real challenging design and engineering problems.' - }, - { - svg: curiosity, - name: 'Curiosity', - desc: 'We value team members that are curious about solving difficult problems and seek out solutions independently.' - }, - { - svg: passion, - name: 'Passion', - desc: 'Genuine commitment and dedication are critical to moving the Berkeleytime product forward.' - } -]; - ->>>>>>> 085e9785b14cdd32d53cb89b3d6ad8c5dd6a3ae2 const About: FC = () => (
From 6fb8f7f7774d1cefeb5f9b57ea5ac28601a3eb31 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Wed, 26 Apr 2023 17:14:29 -0700 Subject: [PATCH 10/11] change graph data missing message --- frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index 301e51529..700d34c74 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -97,7 +97,7 @@ const CatalogTabs = (props: CatalogTabsProps) => { />
) : ( -
no grade data found
+
There is no grade data found for the selected course.
); case 2: return ( From ee95817d22ac4817cc433f7b256d7543258923c2 Mon Sep 17 00:00:00 2001 From: Henric Zhang Date: Wed, 26 Apr 2023 19:05:48 -0700 Subject: [PATCH 11/11] fix language for no graphs --- .../app/Catalog/CatalogView/CatalogTabs.tsx | 23 ++++++++++++------- .../app/Catalog/CatalogView/CatalogView.tsx | 9 ++++---- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx index 700d34c74..2bea2458e 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx @@ -28,7 +28,9 @@ const CatalogTabs = (props: CatalogTabsProps) => { const gradesGraphData = useSelector((state: State) => state.grade.graphData ?? null); const gradesData = useSelector((state: State) => state.grade?.gradesData ?? null); + const enrollmentData = useSelector((state: State) => state.enrollment?.enrollmentData ?? null); const selectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null); + const enrollselectedCourses = useSelector((state: State) => state.enrollment.selectedCourses ?? null); const links: string[] = catalogService.getLinks(sections, semester, abbreviation, courseNumber); @@ -101,14 +103,19 @@ const CatalogTabs = (props: CatalogTabsProps) => { ); case 2: return ( -
- -
+ enrollselectedCourses?.length > 0 ? ( +
+ ` +
+ ) : ( +
There is no enrollment data found for the selected course.
+ ) + ); } }; diff --git a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx index c352f0d47..4f1821725 100644 --- a/frontend/src/app/Catalog/CatalogView/CatalogView.tsx +++ b/frontend/src/app/Catalog/CatalogView/CatalogView.tsx @@ -16,6 +16,7 @@ import { useDispatch, useSelector } from 'react-redux'; import BTLoader from 'components/Common/BTLoader'; import { enrollReset, + fetchEnrollContext, fetchEnrollData, fetchEnrollFromUrl, fetchGradeData, @@ -129,14 +130,14 @@ const CatalogView = (props: CatalogViewProps) => { ); }); } - if (enrollSelectedCourses?.length == 1) { - dispatch(fetchEnrollData(enrollSelectedCourses)); - } + // if (enrollSelectedCourses?.length == 1) { + // dispatch(fetchEnrollData(enrollSelectedCourses)); + // } if (gradesSelectedCourses?.length == 1) { dispatch(fetchGradeData(gradesSelectedCourses)); } } - }, [course, legacyId, gradesSelectedCourses, enrollSelectedCourses]); + }, [ legacyId, gradesSelectedCourses, enrollSelectedCourses, semester]); const [playlists, sections] = useMemo(() => { let playlists = null;