Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"private": true,
"homepage": "https://gt-scheduler.org/",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.5",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
Expand Down
Binary file added public/buzz_happy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 25 additions & 6 deletions src/components/App/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ import {
AppMobileNavDisplay,
SchedulerPageType,
} from './navigation';
import { classes } from '../../utils/misc';
import { AccountContextValue } from '../../contexts/account';
import { classes, shouldDisplayRatings } from '../../utils/misc';
import { AccountContext, AccountContextValue } from '../../contexts/account';
import { Term } from '../../types';
import CourseDetails from '../CourseDetails';
import SectionDetails from '../SectionDetails';
import RatingsPage from '../RatingsPage';
import RateBanner from '../RateBanner';
import useScreenWidth from '../../hooks/useScreenWidth';
import { DESKTOP_BREAKPOINT } from '../../constants';

export const WEB_NAV_TABS = ['Scheduler', 'Map', 'Finals'];

Expand All @@ -30,21 +34,33 @@ export const WEB_NAV_TABS = ['Scheduler', 'Map', 'Finals'];
* This component is memoized, so it only re-renders when its context changes.
*/
function AppContentBase(): React.ReactElement {
const { currentTab, setTab, openDrawer, currentSchedulerPage } =
useContext(AppNavigationContext);
const {
currentTab,
ratingsOverrideTerm,
setTab,
openDrawer,
currentSchedulerPage,
} = useContext(AppNavigationContext);
const captureRef = useRef<HTMLDivElement>(null);
const { type } = useContext(AccountContext);
const mobile = !useScreenWidth(DESKTOP_BREAKPOINT);

return (
<>
<AppMobileNav captureRef={captureRef} />
<Header
minimal={currentTab === 'Ratings'}
currentTab={currentTab}
onChangeTab={setTab}
onToggleMenu={openDrawer}
tabs={WEB_NAV_TABS}
captureRef={captureRef}
/>
<SurveyBanner />
{currentTab !== 'Ratings' && <SurveyBanner />}
{/* TODO: remove after testing */}
{currentTab !== 'Ratings' && shouldDisplayRatings(!mobile, type) && (
<RateBanner />
)}
<ErrorBoundary
// ErrorBoundary.fallback is a normal render prop, not a component.
// eslint-disable-next-line react/no-unstable-nested-components
Expand Down Expand Up @@ -74,12 +90,15 @@ function AppContentBase(): React.ReactElement {
))}
{currentTab === 'Map' && <Map />}
{currentTab === 'Finals' && <Finals />}
{currentTab === 'Ratings' && (
<RatingsPage overrideTerm={ratingsOverrideTerm} />
)}
{/* Fake calendar used to capture screenshots */}
<div className="capture-container" ref={captureRef}>
<Calendar className="fake-calendar" capture overlayCrns={[]} />
</div>
</ErrorBoundary>
<Attribution />
{currentTab !== 'Ratings' && <Attribution />}
</>
);
}
Expand Down
25 changes: 21 additions & 4 deletions src/components/App/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export type SchedulerPageState =

export type AppNavigationContextValue = {
currentTab: string;
setTab: (next: string) => void;
setTab: (next: string, options?: { overrideTerm?: string }) => void;
ratingsOverrideTerm: string | undefined;
isDrawerOpen: boolean;
openDrawer: () => void;
closeDrawer: () => void;
Expand All @@ -45,6 +46,7 @@ export const AppNavigationContext =
message: 'empty AppNavigationContext.setTab value being used',
});
},
ratingsOverrideTerm: undefined,
isDrawerOpen: false,
openDrawer: (): void => {
throw new ErrorWithFields({
Expand Down Expand Up @@ -77,13 +79,26 @@ export function AppNavigation({
children,
}: AppNavigationProps): React.ReactElement {
const mobile = !useScreenWidth(DESKTOP_BREAKPOINT);

// Allow top-level tab-based navigation
const [currentTab, setTab] = useState<string>('Scheduler');
const [currentTab, setCurrentTab] = useState<string>('Scheduler');
const [ratingsOverrideTerm, setRatingsOverrideTerm] = useState<
string | undefined
>(undefined);

const setTab = useCallback(
(next: string, options?: { overrideTerm?: string }): void => {
setCurrentTab(next);
// Clear the override whenever we leave the Ratings tab, and set it when
// navigating to Ratings with an explicit prior term.
setRatingsOverrideTerm(
next === 'Ratings' ? options?.overrideTerm : undefined
);
},
[]
);

const [currentSchedulerPage, setCurrentSchedulerPage] =
useState<SchedulerPageState>({ type: SchedulerPageType.CALENDAR });

// Handle the status of the drawer being open on mobile
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const openDrawer = useCallback(() => setIsDrawerOpen(true), []);
Expand All @@ -100,6 +115,7 @@ export function AppNavigation({
() => ({
currentTab,
setTab,
ratingsOverrideTerm,
isDrawerOpen,
openDrawer,
closeDrawer,
Expand All @@ -109,6 +125,7 @@ export function AppNavigation({
[
currentTab,
setTab,
ratingsOverrideTerm,
isDrawerOpen,
openDrawer,
closeDrawer,
Expand Down
3 changes: 2 additions & 1 deletion src/components/CourseInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export default function CourseInfo({
value: !isRatingsLoaded
? 'Loading...'
: courseRatings
? courseRatings.averageWorkload.toFixed(1)
? (courseRatings.averageWorkload / 60).toFixed(1)
: null,
unit: 'hrs/week',
},
Expand Down Expand Up @@ -321,6 +321,7 @@ export default function CourseInfo({
course.professorRatings?.[slugify(instructorName)] ?? null
}
isRatingsLoaded={isProfessorRatingsLoaded}
isGpaLoaded={isLoaded}
course={course}
displaySectionInfo={selectedTermKey === term}
/>
Expand Down
91 changes: 91 additions & 0 deletions src/components/CourseRatingCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from 'react';

import { getSemesterName } from '../../utils/semesters';
import Section from '../../data/beans/Section';
import CourseRatingEntry from '../CourseRatingEntry';

import './stylesheet.scss';

type CourseRatingCardProps = {
term: string;
selectedSections: Section[];
unselectedSections: Section[];
onAddCourse: (section: Section) => void;
onDeleteCourse: (crn: string) => void;
showEmptyWarning?: boolean;
setShowEmptyWarning?: (value: boolean) => void;
};

export default function CourseRatingCard({
term,
selectedSections,
unselectedSections,
onAddCourse,
onDeleteCourse,
showEmptyWarning = false,
setShowEmptyWarning,
}: CourseRatingCardProps): React.ReactElement {
const [editableEntries, setEditableEntries] = useState<number[]>([]);

const handleAddEditable = (): void => {
if (setShowEmptyWarning) setShowEmptyWarning(false);
setEditableEntries((prev) => [...prev, Date.now()]);
};

const handleCompleteEditable = (key: number, section: Section): void => {
onAddCourse(section);
handleDeleteEditable(key);
};

const handleDeleteEditable = (key: number): void => {
setEditableEntries((prev) => prev.filter((k) => k !== key));
};

const handleDeleteSelected = (crn: string): void => {
onDeleteCourse(crn);
};

return (
<div className="course-rating-card">
<div className="card-header">
To start,
<br />
{selectedSections.length === 0
? `Add the courses you took for ${getSemesterName(term)}`
: `Are these the courses you took for ${getSemesterName(term)}?`}
</div>

<div className="entries-scroll">
{selectedSections.map((section) => (
<CourseRatingEntry
key={section.crn}
section={section}
unselectedSections={unselectedSections}
editable={false}
onDelete={(): void => handleDeleteSelected(section.crn)}
/>
))}

{editableEntries.map((key) => (
<CourseRatingEntry
key={key}
unselectedSections={unselectedSections}
editable
onComplete={(section): void => handleCompleteEditable(key, section)}
onDelete={(): void => handleDeleteEditable(key)}
/>
))}
</div>

{showEmptyWarning && (
<div className="add-course-warning">
* You have to add a course to start
</div>
)}

<button type="button" className="add-course" onClick={handleAddEditable}>
+ Add a course
</button>
</div>
);
}
50 changes: 50 additions & 0 deletions src/components/CourseRatingCard/stylesheet.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@import '../../variables';

.course-rating-card {
display: flex;
flex-direction: column;
border-radius: 8px;
width: 100%;
gap: 2rem;
max-height: 75vh;
overflow: hidden;

.add-course-warning {
color: $error-message;
font-size: 16px;
}

.card-header {
display: flex;
text-align: start;
font-weight: bold;
font-size: 24px;
flex-shrink: 0;
}

.entries-scroll {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 2rem;
padding-right: 0.5rem;
}

.add-course {
color: #66b3ff;
background: none;
border: none;
cursor: pointer;
align-self: flex-start;
flex-shrink: 0;

&:hover {
text-decoration: underline;
}
}

*::-webkit-scrollbar {
display: none;
}
}
Loading