Skip to content
Open
Show file tree
Hide file tree
Changes from 13 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
2 changes: 2 additions & 0 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { TooltipProvider } from 'react-tooltip';
import { Outlet } from 'react-router-dom';

import { classes } from '../../utils/misc';
import Feedback from '../Feedback';
Expand Down Expand Up @@ -66,6 +67,7 @@ export default function App(): React.ReactElement {
then it displays an error screen. */}
<AppDataLoader>
<AppContent />
<Outlet />
</AppDataLoader>
</AppNavigation>
<Feedback />
Expand Down
41 changes: 41 additions & 0 deletions src/components/Breadcrumb/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';

import { classes } from '../../utils/misc';

import './stylesheet.scss';

export type BreadcrumbItem = {
label: string;
link?: string;
};

export type BreadcrumbProps = {
items: BreadcrumbItem[];
};

export default function Breadcrumb({
items,
}: BreadcrumbProps): React.ReactElement {
return (
<nav className={classes('Breadcrumb')}>
<ul className="list">
{items.map((item, index) => (
<li key={index} className="item">
{item.link ? (
<a href={item.link} className="link">
{item.label}
</a>
) : (
<span className="label">{item.label}</span>
)}
{index < items.length - 1 && (
<FontAwesomeIcon icon={faChevronRight} className="separator" />
)}
</li>
))}
</ul>
</nav>
);
}
35 changes: 35 additions & 0 deletions src/components/Breadcrumb/stylesheet.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import '../../variables.scss';

.Breadcrumb {
display: flex;
align-items: center;
font-size: 1em;
padding: 8px 12px;

.list {
display: flex;
list-style: none;
margin: 0;
padding: 0;

.item {
display: flex;
align-items: center;

.link {
color: $course-page-link-color;
text-decoration: none;
font-weight: 500;
}

.label {
font-weight: 500;
}

.separator {
margin: 0 6px;
font-size: 0.75em;
}
}
}
}
37 changes: 37 additions & 0 deletions src/components/MetricsCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

import { classes } from '../../utils/misc';

import './stylesheet.scss';

export type Metric = {
label: string;
value: string;
unit?: string;
};

export type MetricsCardProps = {
metrics: Metric[];
};

export default function MetricsCard({
metrics,
}: MetricsCardProps): React.ReactElement {
return (
<div className={classes('MetricsCard')}>
<ul className="metrics-list">
{metrics.map((metric, index) => (
<li key={index} className="metric-item">
<div className="metric-value">
{metric.value}
{metric.unit && (
<span className="metric-unit"> {metric.unit}</span>
)}
</div>
<div className="metric-label">{metric.label}</div>
</li>
))}
</ul>
</div>
);
}
44 changes: 44 additions & 0 deletions src/components/MetricsCard/stylesheet.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@import '../../variables.scss';

.MetricsCard {
display: flex;
padding: 16px;
border-radius: 8px;
font-family: 'Roboto', sans-serif;

.metrics-list {
display: flex;
list-style: none;
padding: 0;
margin: 0;

.metric-item {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 40px;
padding-right: 40px;
border-right: 1px solid $color-border;

&:last-child {
border-right: none;
}

.metric-value {
font-size: 1.25em;
}

.metric-unit {
font-size: 0.875em;
font-weight: normal;
}

.metric-label {
font-size: 0.875em;
margin-top: 4px;
@include dark(color, $seat-info-dark-label);
@include light(color, $seat-info-light-label);
}
}
}
}
149 changes: 149 additions & 0 deletions src/components/ProfessorInfoCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useContext } from 'react';
import { faPlus, faCheck } from '@fortawesome/free-solid-svg-icons';
import useSWR from 'swr';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

import { ScheduleContext } from '../../contexts';
import { Course, Section } from '../../data/beans';
import ActionRow from '../ActionRow';
import { formatTime, getRandomColor } from '../../utils/misc';
import { Schedule } from '../../data/types';
import { OccupiedInfo } from '../../data/beans/Section';

import './stylesheet.scss';

export type ProfessorInfoCardProps = {
professorName: string;
course: Course;
};

type SeatData = {
inClass: OccupiedInfo | null;
waitlist: OccupiedInfo | null;
};

function SectionRow({
section,
term,
isPinned,
onAdd,
}: {
section: Section;
term: string;
isPinned: boolean;
onAdd: () => void;
}): React.ReactElement {
const meeting = section.meetings[0];

const { data: seatData, isLoading } = useSWR<SeatData>(
['seating', section.crn, term],
() => section.getSeatData(term)
);

const formatSeatData = (info: OccupiedInfo | null): string => {
if (isLoading) return 'Loading...';
if (!info) return 'N/A';
return `${info.occupied} / ${info.total}`;
};

return (
<ActionRow key={section.crn} className="section-row" label="" actions={[]}>
<div className="section-content">
<div className="left-group">
<div className="row-cell action-cell">
<button
type="button"
className="action-button"
onClick={(): void => (isPinned ? undefined : onAdd())}
>
<FontAwesomeIcon
icon={isPinned ? faCheck : faPlus}
className="action-icon"
/>
</button>
</div>

<div className="row-cell">{section.crn}</div>
<div className="row-cell">{section.id}</div>
<div className="row-cell">{meeting?.days.join('') || 'TBA'}</div>
<div className="row-cell">
{meeting?.period
? `${formatTime(meeting.period.start)}-${formatTime(
meeting.period.end
)}`
: 'TBA'}
</div>
</div>
<div className="right-group">
<div className="row-cell">
{formatSeatData(seatData?.inClass ?? null)}
</div>
<div className="row-cell">
{formatSeatData(seatData?.waitlist ?? null)}
</div>
<div className="row-cell">{meeting?.where || 'TBA'}</div>
</div>
</div>
</ActionRow>
);
}

export default function ProfessorInfoCard({
professorName,
course,
}: ProfessorInfoCardProps): React.ReactElement {
const sections: Section[] = course.sections.filter((section: Section) => {
return section.instructors[0] === professorName;
});
const [{ pinnedCrns, desiredCourses, colorMap, palette }, { patchSchedule }] =
useContext(ScheduleContext);

const handleAddSection = (section: Section): void => {
const updates: Partial<Schedule> = {
pinnedCrns: [...pinnedCrns, section.crn],
};
if (!desiredCourses.includes(course.id)) {
updates.desiredCourses = [...desiredCourses, course.id];
const color = getRandomColor(palette);
updates.colorMap = { ...colorMap, [course.id]: color };
}
patchSchedule(updates);
};

return (
<div className="ProfessorInfo">
<div className="professor-header">
<p className="professor-name">{professorName}</p>
</div>

<div className="sections-container">
<div className="sections-header">
<div className="left-group">
<div className="row-cell" />
<div className="row-cell">CRN</div>
<div className="row-cell">Sect.</div>
<div className="row-cell">Day</div>
<div className="row-cell">Time</div>
</div>
<div className="right-group">
<div className="row-cell">Seats Filled</div>
<div className="row-cell">Waitlist</div>
<div className="row-cell">Location</div>
</div>
</div>

{sections.map((section) => {
return (
<SectionRow
key={section.crn}
section={section}
term={course.term}
isPinned={pinnedCrns.includes(section.crn)}
onAdd={(): void => handleAddSection(section)}
/>
);
})}
</div>
</div>
);
}
Loading
Loading