Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Catalogtabs #572

Closed
wants to merge 14 commits into from
2 changes: 1 addition & 1 deletion frontend/src/app/Catalog/Catalog.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
141 changes: 141 additions & 0 deletions frontend/src/app/Catalog/CatalogView/CatalogTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import EnrollmentGraphCard from 'components/GraphCard/EnrollmentGraphCard';
import GradesGraph from 'components/Graphs/GradesGraph';
import { CourseFragment, GradeType, SectionFragment } from 'graphql';
import catalogService from '../service';
import { useState } from 'react';
import { enrollReset } from 'redux/actions';
import styles from './CatalogView.module.scss';
import SectionTable from './SectionTable';
import { useSelector } from 'react-redux';
import { State } from '../types';
import BTLoader from 'components/Common/BTLoader';

interface CatalogTabsProps {
semester: string;
course: CourseFragment | null;
sections: SectionFragment[] | null;
loading: boolean;
abbreviation: string;
courseNumber: string;
}

const CatalogTabs = (props: CatalogTabsProps) => {
const { semester, course, sections, loading, abbreviation, courseNumber } = props;

const [hoveredClass, setHoveredClass] = useState<boolean>(false);
const [updateMobileHover, setUpdateMobileHover] = useState<boolean>(true);
const [tab, setTab] = useState<number>(0);

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);

function update(course: CourseFragment, grade: GradeType) {
if (course && gradesData && gradesData.length > 0) {
const selectedGrades = gradesData.filter((c: CourseFragment) => 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 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 renderTabs = (currentTab: number) => {
if (loading) {
return <BTLoader />;
}

switch (currentTab) {
case 0:
return (
<div className={styles.gradesBox}>
{sections && sections.length > 0 ? (
<SectionTable links={links} sections={sections} />
) : (
'There are no class times for the selected course.'
)}
</div>
);

case 1:
return gradesData?.length > 0 ? (
<div className={styles.gradesBox}>
<GradesGraph
graphData={gradesGraphData}
gradesData={gradesData}
updateBarHover={null}
updateGraphHover={updateGraphHover}
course={course?.abbreviation + ' ' + course?.courseNumber}
semester={'All Semesters'}
instructor={'All Instructors'}
selectedPercentiles={hoveredClass[hoveredClass.hoverGrade]}
denominator={hoveredClass.denominator}
color={0}
isMobile={window.innerWidth < 768 ? true : false}
graphEmpty={false}
/>
</div>
) : (
<div>There is no grade data found for the selected course.</div>
);
case 2:
return (
enrollselectedCourses?.length > 0 ? (
<div className={styles.gradesBox}>
`<EnrollmentGraphCard
id="gradesGraph"
title="Enrollment"
updateClassCardEnrollment={enrollReset}
isMobile={window.innerWidth < 768 ? true : false}
/>
</div>
) : (
<div>There is no enrollment data found for the selected course.</div>
)

);
}
};

return (
<div>
<nav className={styles.tabContainer}>
{['Class Times', 'Grades', 'Enrollment'].map((title, index) => (
<button
key={title}
className={`${styles.tabButton} ${tab === index && styles.selected}`}
onClick={() => setTab(index)}
>
{title}
</button>
))}
</nav>
{renderTabs(tab)}
</div>
);
};

export default CatalogTabs;
31 changes: 30 additions & 1 deletion frontend/src/app/Catalog/CatalogView/CatalogView.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
z-index: 300;
width: 100vw;
// Add size of fixed header.
padding-top: 30px;
padding-top: 57.11px + 30px;
&[data-modal='false'] {
display: none;
}
Expand Down Expand Up @@ -110,6 +110,35 @@
}
}

.gradesBox {
margin-top: 1%;
}

.tabContainer {
display: flex;
white-space: nowrap;
overflow: auto;
margin-bottom: 20px;
}

.tabButton {
width: auto;
text-overflow: ellipsis;
background-color: white;
border-width: 0px;
border-bottom: 2px solid white;
padding: 6px 12px;
transition-duration: 0.3s;
}

.selected {
border-bottom: 2px solid #579eff;
}

.tabButton:hover {
background-color: $bt-button-background;
}

.pills {
display: block;
position: relative;
Expand Down
141 changes: 94 additions & 47 deletions frontend/src/app/Catalog/CatalogView/CatalogView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@ 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';
import { useHistory, useParams } from 'react-router';
import { sortSections } from 'utils/sections/sort';
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,
fetchGradeData,
fetchGradeFromUrl,
gradeReset
} from 'redux/actions';
import axios from 'axios';
import CatalogTabs from './CatalogTabs';
import catalogService from '../service';
import { State } from '../types';

interface CatalogViewProps {
coursePreview: CourseFragment | null;
Expand All @@ -33,8 +44,11 @@ const CatalogView = (props: CatalogViewProps) => {
semester: string;
}>();

const dispatch = useDispatch();

const [course, setCourse] = useState<CourseFragment | null>(coursePreview);
const [isOpen, setOpen] = useState(false);

const history = useHistory();

const legacyId = useSelector(
Expand All @@ -44,6 +58,12 @@ const CatalogView = (props: CatalogViewProps) => {
)?.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;
Expand Down Expand Up @@ -86,6 +106,39 @@ const CatalogView = (props: CatalogViewProps) => {
}
}, [coursePreview, data]);

const gradesSelectedCourses = useSelector((state: State) => state.grade.selectedCourses ?? null);

const enrollSelectedCourses = useSelector(
(state: State) => state.enrollment.selectedCourses ?? null
);

useEffect(() => {
dispatch(gradeReset());
dispatch(enrollReset());
}, [course]);

useEffect(() => {
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}`
)
);
});
}
// if (enrollSelectedCourses?.length == 1) {
// dispatch(fetchEnrollData(enrollSelectedCourses));
// }
if (gradesSelectedCourses?.length == 1) {
dispatch(fetchGradeData(gradesSelectedCourses));
}
}
}, [ legacyId, gradesSelectedCourses, enrollSelectedCourses, semester]);

const [playlists, sections] = useMemo(() => {
let playlists = null;
let sections = null;
Expand Down Expand Up @@ -130,12 +183,6 @@ const CatalogView = (props: CatalogViewProps) => {
});
};

const enrollPath = legacyId
? `/enrollment/0-${legacyId}-${semester.replace(' ', '-')}-all`
: `/enrollment`;

const gradePath = legacyId ? `/grades/0-${legacyId}-all-all` : `/grades`;

return (
<div className={`${styles.root}`} data-modal={isOpen}>
{course && (
Expand Down Expand Up @@ -216,44 +263,44 @@ const CatalogView = (props: CatalogViewProps) => {
</p>
</>
</ReadMore>
<h5>Class Times - {semester ?? ''}</h5>
{sections && sections.length > 0 ? (
<SectionTable sections={sections} />
) : !loading ? (
<span>There are no class times for the selected course.</span>
) : null}

<CatalogTabs
semester={semester}
course={course}
sections={sections}
loading={loading}
abbreviation={abbreviation}
courseNumber={courseNumber}
/>
{/*
Redesigned catalog sections
<CatalogViewSections sections={sections} />
*/}

{/* Good feature whenever we want...
<h5>Past Offerings</h5>
<section className={styles.pills}>
{pastSemesters ? (
pastSemesters.map((req) => (
<button
className={styles.pill}
key={req.id}
onClick={() =>
history.push(`/catalog/${req.name}/${course.abbreviation}/${course.courseNumber}`)
}
>
{req.name}
</button>
))
) : (
<Skeleton
style={{ marginRight: '5px' }}
inline
count={10}
width={80}
height={28}
borderRadius={12}
/>
)}
</section> */}
Redesigned catalog sections
<CatalogViewSections sections={sections} />
*/
/* Good feature whenever we want...
<h5>Past Offerings</h5>
<section className={styles.pills}>
{pastSemesters ? (
pastSemesters.map((req) => (
<button
className={styles.pill}
key={req.id}
onClick={() =>
history.push(`/catalog/${req.name}/${course.abbreviation}/${course.courseNumber}`)
}
>
{req.name}
</button>
))
) : (
<Skeleton
style={{ marginRight: '5px' }}
inline
count={10}
width={80}
height={28}
borderRadius={12}
/>
)}
</section> */}
</>
)}
</div>
Expand Down
Loading