Skip to content

Commit

Permalink
Update review panel. Make stats work on the plan panel.
Browse files Browse the repository at this point in the history
  • Loading branch information
AaDalal committed Feb 15, 2024
1 parent d0d8e9e commit d695fa5
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 134 deletions.
51 changes: 28 additions & 23 deletions frontend/degree-plan/components/FourYearPlan/Stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, { useState, useRef, useEffect } from "react";
import styled from '@emotion/styled';
import { CircularProgressbar } from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css';
import { Fulfillment } from "@/types";

const getColor = (num: number, reverse: boolean) => {
if (isNaN(num)) {
return "#76bf96";
return "#d6d6d6";
}
num = Number(num.toFixed(1));
if (num < 2) {
Expand All @@ -30,9 +31,9 @@ const ScoreRow = ({ score, label }: { score: number, label: string }) => {
return (
<>
<ScoreCircle
value={score * 25}
value={isNaN(score) ? 0 : score * 25}
strokeWidth={12}
text={score.toFixed(1)}
text={isNaN(score) ? "N/A" : score.toFixed(1)}
styles={{
path: {
stroke: getColor(score, false),
Expand All @@ -56,31 +57,35 @@ const Stack = styled.div`
align-items: center;
`;

const getAvg = (courses) => {
if (courses.length == 0) return [0, 0, 0, 0];
let courseQualitySum = 0;
let instructorQualitySum = 0;
let difficultySum = 0;
let workRequired = 0;
for (const course in courses) {
courseQualitySum += course.course_quality;
instructorQualitySum += course.instructor_quality;
difficultySum += course.difficulty;
workRequired += course.work_required;
type StatsType = "course_quality" | "instructor_quality" | "difficulty" | "work_required";
const StatsKeys: StatsType[] = ["course_quality", "instructor_quality", "difficulty", "work_required"];
const getAverages = (fulfillments: Fulfillment[]) => {
const counts = { course_quality: 0, instructor_quality: 0, difficulty: 0, work_required: 0 };
const sums = { course_quality: 0, instructor_quality: 0, difficulty: 0, work_required: 0 };
for (const f of fulfillments) {
for (const key of StatsKeys) {
sums[key] += f.course[key] || 0;
counts[key] += f.course[key] ? 1 : 0;
}
}
return [courseQualitySum / courses.length,
instructorQualitySum / courses.length,
difficultySum / courses.length,
workRequired / courses.length];
const avgs = {} as Record<StatsType, number>;
for (const key of StatsKeys) {
if (counts[key] == 0) avgs[key] = NaN;
else avgs[key] = sums[key] / counts[key];
}
return avgs;
}

const Stats = ({ courses, className } : { courses: any, className: string }) => {

const Stats = ({ courses, className } : { courses: Fulfillment[], className: string }) => {
const { course_quality, instructor_quality, difficulty, work_required } = getAverages(courses) as Record<StatsType, number>;

return (
<Stack className={className}>
<ScoreRow label={'Course Quality'} score={3.3}/>
<ScoreRow label={'Instructor Quality'} score={3.2}/>
<ScoreRow label={'Difficulty'} score={2.1}/>
<ScoreRow label={'Work Required'} score={1.0}/>
<ScoreRow label={'Course Quality'} score={course_quality}/>
<ScoreRow label={'Instructor Quality'} score={instructor_quality}/>
<ScoreRow label={'Difficulty'} score={difficulty}/>
<ScoreRow label={'Work Required'} score={work_required}/>
</Stack>
)
}
Expand Down
15 changes: 2 additions & 13 deletions frontend/degree-plan/components/Infobox/CourseInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Popover, PopoverTitle } from "./common/Popover";
import { toNormalizedSemester } from "./util/helpers";

import ReactTooltip from "react-tooltip";
import { GrayIcon } from "../bulma_derived_components";
import { CloseButton } from "./common/CloseButton";

const activityMap = {
REC: "Recitation",
Expand Down Expand Up @@ -215,15 +215,6 @@ const Spacer = styled.div`
height: 0.6rem;
`;

const CloseIcon = styled(GrayIcon)`
pointer-events: auto;
margin-left: 0.5rem;
& :hover {
color: #707070;
}
`

export const CourseHeader = ({
close,
aliases,
Expand Down Expand Up @@ -258,9 +249,7 @@ export const CourseHeader = ({
>
<PCRLogo src="/images/pcr-logo.png" />
</a>
<CloseIcon onClick={close}>
<i class="fas fa-times fa-md"></i>
</CloseIcon>
<CloseButton />
</Actions>
</div>
{data.last_offered_sem_if_superceded && (
Expand Down
19 changes: 10 additions & 9 deletions frontend/degree-plan/components/Infobox/ReviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import Draggable from 'react-draggable';
import useSWR from 'swr';
import styled from '@emotion/styled';
import InfoBox from './index'
import { PropsWithChildren, useContext, useEffect, useState } from 'react';
import { PropsWithChildren, useContext, useEffect, useRef, useState } from 'react';
import { createContext } from 'react';

export const ReviewPanelTrigger = ({ full_code, children }: PropsWithChildren<{full_code: Course["full_code"]}>) => {
const ref = useRef<HTMLDivElement>(null);
const { setPosition, set_full_code, isPermanent, setIsPermanent } = useContext(ReviewPanelContext);
const [isHovered, setIsHovered] = useState(false);
useEffect(() => {
if (isHovered) set_full_code(full_code);
else setTimeout(() => { if (!isHovered) close()}, 300);
}, [isHovered])

return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
ref={ref}
onDoubleClick={() => {
set_full_code(full_code)
if (!ref.current) return;
const { x, y } = ref.current.getBoundingClientRect();
if (!isPermanent) setPosition({ x, y});
}}
className="review-panel-trigger"
>
{children}
Expand All @@ -35,7 +36,7 @@ interface ReviewPanelContextType {

export const ReviewPanelContext = createContext<ReviewPanelContextType>({
position: {x: 0, y: 0},
setPosition: ([x, y]) => {}, // placeholder
setPosition: ({x, y}) => {}, // placeholder
full_code: null,
set_full_code: (course) => {}, // placeholder
isPermanent: false,
Expand Down
19 changes: 19 additions & 0 deletions frontend/degree-plan/components/Infobox/common/CloseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GrayIcon } from '@/components/bulma_derived_components';
import styled from '@emotion/styled'

const CloseIcon = styled(GrayIcon)`
pointer-events: auto;
margin-left: 0.5rem;
& :hover {
color: #707070;
}
`

const CloseButton = ({ close }) => (
<CloseIcon onClick={close}>
<i className="fas fa-times fa-md"></i>
</CloseIcon>
)

export default CloseButton;
20 changes: 20 additions & 0 deletions frontend/degree-plan/components/Infobox/common/ErrorBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PropsWithChildren } from "react";
import { GrayIcon } from "@/components/bulma_derived_components";

interface ErrorBoxProps {
detail?: string;
}

export const ErrorBox = ({ children, detail }: PropsWithChildren<ErrorBoxProps>) => (
<div style={{ textAlign: "center", padding: 45 }}>
<i
className="fa fa-exclamation-circle"
style={{ fontSize: "150px", color: "#aaa" }}
/>
<h1 style={{ fontSize: "1.5em", marginTop: 15 }}>{children}</h1>
<small>
{detail} If you think this is an error, contact Penn Labs at{" "}
<a href="mailto:[email protected]">[email protected]</a>.
</small>
</div>
);
88 changes: 53 additions & 35 deletions frontend/degree-plan/components/Infobox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import styled from "styled-components";
import { lato } from "@/fonts";
import Ratings from "./InfoRatings";
import { CourseDescription, CourseHeader } from "./CourseInfo";
import { ErrorBox } from "./common/ErrorBox";


const InfoBoxCSS = styled.div`
height: 100%;
.box {
font-size: 15px;
margin: 0px;
Expand Down Expand Up @@ -227,6 +230,14 @@ const InfoBoxCSS = styled.div`
}
`

const ErrorWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
flex-direction: column;
`

/**
* Information box on the left most side, containing scores and descriptions
* of course or professor.
Expand Down Expand Up @@ -267,49 +278,56 @@ const InfoBox = ({
avgDifficulty != null ||
avgWorkRequired != null;
console.log("data", data)
if (!data) {
return <h1>Loading data...</h1>;
}
return (
<InfoBoxCSS className={lato.className}>
<div className="box">
<div id="banner-info" data-type="course">
<CourseHeader
close={close}
aliases={aliases}
code={code}
data={data}
name={name}
notes={notes}
instructors={instructors}
liveData={liveData}
/>
</div>
{hasReviews && (
<div id="banner-score">
<Ratings
value="Average"
instructor={avgInstructorQuality}
course={avgCourseQuality}
difficulty={avgDifficulty}
work={avgWorkRequired}
num_sections={numSections}
/>
<Ratings
value="Recent"
instructor={recentInstructorQuality}
course={recentCourseQuality}
difficulty={recentDifficulty}
work={recentWorkRequired}
num_sections={numSectionsRecent}
{code ?
<div className="box">
<div id="banner-info" data-type="course">
<CourseHeader
close={close}
aliases={aliases}
code={code}
data={data}
name={name}
notes={notes}
instructors={instructors}
liveData={liveData}
/>
</div>
)}
<CourseDescription description={description} />
</div>
{hasReviews && (
<div id="banner-score">
<Ratings
value="Average"
instructor={avgInstructorQuality}
course={avgCourseQuality}
difficulty={avgDifficulty}
work={avgWorkRequired}
num_sections={numSections}
/>
<Ratings
value="Recent"
instructor={recentInstructorQuality}
course={recentCourseQuality}
difficulty={recentDifficulty}
work={recentWorkRequired}
num_sections={numSectionsRecent}
/>
</div>
)}
<CourseDescription description={description} />
</div>
: <ErrorWrapper>
<ErrorBox style={{ marginTop: "auto", marginBottom: "auto" }}>Course not found</ErrorBox>
</ErrorWrapper>
}
</InfoBoxCSS>
);
};
Expand Down
9 changes: 6 additions & 3 deletions frontend/degree-plan/components/Requirements/QObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import grammar from "@/util/q_object_grammar"
import { Icon } from "../bulma_derived_components";
import { BaseCourseContainer } from "../FourYearPlan/CoursePlanned";
import assert from "assert";
import { ReviewPanelTrigger } from "../Infobox/ReviewPanel";

type ConditionKey = "full_code" | "semester" | "attributes__code__in" | "department__code" | "full_code__startswith" | "code__gte" | "code__lte" | "department__code__in"
interface Condition {
Expand Down Expand Up @@ -39,9 +40,11 @@ const CourseOption = ({ course, chosenOptions, setChosenOptions, semester }: Cou
}))

return (
<BaseCourseContainer ref={drag}>
{course.split("-").join(" ")}{semester ? ` (${semester})` : ""}
</BaseCourseContainer>
<ReviewPanelTrigger>
<BaseCourseContainer ref={drag}>
{course.split("-").join(" ")}{semester ? ` (${semester})` : ""}
</BaseCourseContainer>
</ReviewPanelTrigger>
)
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/degree-plan/components/Requirements/ReqPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const EmptyPanelContainer = styled.div`
`;

const EmptyPanelImage = styled.img`
max-width: min(60%, 40vw);
max-width: min(60%, 20rem);
`;

const EmptyPanel = () => {
Expand Down Expand Up @@ -78,9 +78,9 @@ const ReqPanel = ({activeDegreePlan, isLoading, highlightReqId, setSearchClosed,
if (degree.id === activeDegreeId) setActiveDegreeId(undefined);
},
create: () => updateDegreeplan(
{degree_ids: [...(activeDegreePlan?.degree_ids || []), 520]}, // TODO: this is a placeholder, we need to add a new degree
{degree_ids: [...(activeDegreePlan?.degree_ids || []), 1900 ]}, // TODO: this is a placeholder, we need to add a new degree
activeDegreePlan?.id
)?.then(() => setActiveDegreeId(520)),
)?.then((res) => { if (res?.id) setActiveDegreeId(res.id); }),
}}
/>
</PanelHeader>
Expand Down
Loading

0 comments on commit d695fa5

Please sign in to comment.