Squashed commit of the following:
commitc46e4a51c9Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Mon Feb 19 21:37:46 2024 -0600 change from reducer pattern to state variables, remove chartData from state commit36bcdd2522Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Mon Feb 19 21:15:41 2024 -0600 change grade distribution colors to match updated figma commit11a50df88dMerge:c16b301b4c96a9Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Mon Feb 19 17:57:13 2024 -0600 Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup commitc16b301ff0Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Mon Feb 19 17:47:21 2024 -0600 Kinda complete the handlers commit1ac1d9095aAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 17:36:59 2024 -0600 Bunch of renaming commit925829ad41Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 17:24:53 2024 -0600 Fix syllabi url Remove unused variable and unnecessary args to url commitf2e5d51eb3Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 17:24:22 2024 -0600 Add TODO replace current grade colors with a tailwind palette commit747ee44440Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 01:26:51 2024 -0600 Minor tweaks change style in header commitddfe952a32Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 01:26:38 2024 -0600 Add Grade Distribution Stuff commitc27bf3c390Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sun Feb 18 01:26:13 2024 -0600 Modify story to use proper course info commit7afdbac1b8Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 16:37:01 2024 -0600 description stuff done commit1a89432276Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 15:26:32 2024 -0600 Rename CoursePopup Old one to "Old", remove "2" from new one commit4c2b31e61aAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 15:23:01 2024 -0600 add todo for calendar button commit11b7a51dedAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 15:22:18 2024 -0600 add course button onclick handlers commitf2dfcec838Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 14:52:38 2024 -0600 some unocss updates commitf9f375514bAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 13:00:46 2024 -0600 Add rmp callback commit122fc6dbddAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 13:00:16 2024 -0600 Change test course to 314 commit19b124b3bdAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 12:19:21 2024 -0600 complete CourseHeaderAndActions Component added course buttons, using proper subcomponents now. commit2eea01fc74Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 11:22:12 2024 -0600 use chip component in header commit9cb13c8fd1Merge:a62b7189392085Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 11:21:12 2024 -0600 Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup commita62b718c43Merge:43d26757b7b858Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 10:57:24 2024 -0600 Merge branch 'hackathon' into abhinavchadaga/course-catalog-popup commit43d2675be5Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Sat Feb 17 10:54:49 2024 -0600 some work on course popup update the stories and create the header component commit31bcef3099Merge:874f8d5fa1d737Author: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Wed Feb 14 14:33:16 2024 -0600 Merge branch 'main' into abhinavchadaga/course-catalog-popup pulling from main commit874f8d56cbAuthor: Abhinav Chadaga <abhinav.chadaga@utexas.edu> Date: Wed Feb 14 14:30:24 2024 -0600 some work
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
@use 'src/views/styles/colors.module.scss';
|
||||
@use 'src/views/styles/elevation.module.scss';
|
||||
|
||||
.chartContainer {
|
||||
height: 250px;
|
||||
margin: 20px;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
|
||||
.selectContainer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin-top: -8px;
|
||||
justify-content: center;
|
||||
|
||||
select {
|
||||
z-index: elevation.$MAX_Z_INDEX;
|
||||
padding: 4px;
|
||||
font-family: 'Inter';
|
||||
border-radius: 8px;
|
||||
border-color: colors.$charcoal;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.highcharts-background) {
|
||||
fill: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.textContainer {
|
||||
margin: 20px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.text {
|
||||
padding: 12px;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
|
||||
// add some vertical padding to each element
|
||||
> * {
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Course, Semester } from '@shared/types/Course';
|
||||
import { Distribution, LetterGrade } from '@shared/types/Distribution';
|
||||
import Highcharts from 'highcharts';
|
||||
import HighchartsReact from 'highcharts-react-official';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import Card from '@views/components/common/Card/Card';
|
||||
import Icon from '@views/components/common/Icon/Icon';
|
||||
import Spinner from '@views/components/common/Spinner/Spinner';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import {
|
||||
NoDataError,
|
||||
queryAggregateDistribution,
|
||||
querySemesterDistribution,
|
||||
} from '@views/lib/database/queryDistribution';
|
||||
import colors from '@views/styles/colors.module.scss';
|
||||
import styles from './GradeDistribution.module.scss';
|
||||
|
||||
enum DataStatus {
|
||||
LOADING = 'LOADING',
|
||||
FOUND = 'FOUND',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
ERROR = 'ERROR',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
course: Course;
|
||||
}
|
||||
|
||||
const GRADE_COLORS: Record<LetterGrade, string> = {
|
||||
A: colors.turtle_pond,
|
||||
'A-': colors.turtle_pond,
|
||||
'B+': colors.cactus,
|
||||
B: colors.cactus,
|
||||
'B-': colors.cactus,
|
||||
'C+': colors.sunshine,
|
||||
C: colors.sunshine,
|
||||
'C-': colors.sunshine,
|
||||
'D+': colors.tangerine,
|
||||
D: colors.tangerine,
|
||||
'D-': colors.tangerine,
|
||||
F: colors.speedway_brick,
|
||||
};
|
||||
|
||||
/**
|
||||
* A chart to fetch and display the grade distribution for a course
|
||||
* @returns
|
||||
*/
|
||||
export default function GradeDistribution({ course }: Props) {
|
||||
const ref = useRef<HighchartsReact.RefObject>(null);
|
||||
const [semesters, setSemesters] = useState<Semester[]>([]);
|
||||
const [selectedSemester, setSelectedSemester] = useState<Semester | null>(null);
|
||||
const [distribution, setDistribution] = useState<Distribution | null>(null);
|
||||
const [status, setStatus] = useState<DataStatus>(DataStatus.LOADING);
|
||||
|
||||
const [chartOptions, setChartOptions] = useState<Highcharts.Options>({
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
subtitle: {
|
||||
text: undefined,
|
||||
},
|
||||
legend: {
|
||||
enabled: false,
|
||||
},
|
||||
xAxis: {
|
||||
title: {
|
||||
text: 'Grades',
|
||||
},
|
||||
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'],
|
||||
crosshair: true,
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: 'Students',
|
||||
},
|
||||
},
|
||||
chart: {
|
||||
style: {
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: '600',
|
||||
},
|
||||
spacingBottom: 25,
|
||||
spacingTop: 25,
|
||||
height: 250,
|
||||
},
|
||||
credits: {
|
||||
enabled: false,
|
||||
},
|
||||
accessibility: {
|
||||
enabled: false,
|
||||
},
|
||||
tooltip: {
|
||||
headerFormat: '<span style="font-size:small; font-weight:bold">{point.key}</span><table>',
|
||||
pointFormat:
|
||||
'<td style="color:{black};padding:0;font-size:small; font-weight:bold;"><b>{point.y:.0f} Students</b></td>',
|
||||
footerFormat: '</table>',
|
||||
shared: true,
|
||||
useHTML: true,
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
pointPadding: 0.2,
|
||||
borderWidth: 0,
|
||||
},
|
||||
series: {
|
||||
animation: {
|
||||
duration: 700,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'column',
|
||||
name: 'Grades',
|
||||
data: Array.from({ length: 12 }, () => 0),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const updateChart = (distribution: Distribution) => {
|
||||
setChartOptions(options => ({
|
||||
...options,
|
||||
series: [
|
||||
{
|
||||
type: 'column',
|
||||
name: 'Grades',
|
||||
data: Object.entries(distribution).map(([grade, count]) => ({
|
||||
y: count,
|
||||
color: GRADE_COLORS[grade as LetterGrade],
|
||||
})),
|
||||
},
|
||||
],
|
||||
}));
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryAggregateDistribution(course)
|
||||
.then(([distribution, semesters]) => {
|
||||
setSemesters(semesters);
|
||||
updateChart(distribution);
|
||||
setStatus(DataStatus.FOUND);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof NoDataError) {
|
||||
return setStatus(DataStatus.NOT_FOUND);
|
||||
}
|
||||
return setStatus(DataStatus.ERROR);
|
||||
});
|
||||
}, [course]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let distribution: Distribution;
|
||||
if (selectedSemester) {
|
||||
distribution = await querySemesterDistribution(course, selectedSemester);
|
||||
} else {
|
||||
[distribution] = await queryAggregateDistribution(course);
|
||||
}
|
||||
updateChart(distribution);
|
||||
setStatus(DataStatus.FOUND);
|
||||
})().catch(err => {
|
||||
if (err instanceof NoDataError) {
|
||||
return setStatus(DataStatus.NOT_FOUND);
|
||||
}
|
||||
return setStatus(DataStatus.ERROR);
|
||||
});
|
||||
}, [selectedSemester, course]);
|
||||
|
||||
const handleSelectSemester = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const index = parseInt(event.target.value, 10);
|
||||
if (index === 0) {
|
||||
setSelectedSemester(null);
|
||||
} else {
|
||||
setSelectedSemester(semesters[index - 1]);
|
||||
}
|
||||
};
|
||||
|
||||
if (status === DataStatus.FOUND) {
|
||||
return (
|
||||
<Card className={styles.chartContainer}>
|
||||
{semesters.length > 0 && (
|
||||
<div className={styles.selectContainer}>
|
||||
<select onChange={handleSelectSemester}>
|
||||
<option value={0}>Aggregate</option>
|
||||
{semesters.map((semester, index) => (
|
||||
<option key={semester.season + semester.year} value={index + 1}>
|
||||
{semester.season} {semester.year}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<HighchartsReact ref={ref} highcharts={Highcharts} options={chartOptions} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={styles.textContainer}>
|
||||
{status === DataStatus.LOADING && <Spinner />}
|
||||
{status === DataStatus.ERROR && (
|
||||
<Card className={styles.text}>
|
||||
<Text color='speedway_brick' /* size='medium' weight='semi_bold' */>
|
||||
There was an error fetching the grade distribution data
|
||||
</Text>
|
||||
<Icon color='speedway_brick' /* size='large' */ name='sentiment_dissatisfied' />
|
||||
</Card>
|
||||
)}
|
||||
{status === DataStatus.NOT_FOUND && (
|
||||
<Card className={styles.text}>
|
||||
<Text color='charcoal' /* size='medium' weight='semi_bold' */>
|
||||
No grade distribution data was found for this course
|
||||
</Text>
|
||||
<Icon color='charcoal' /* size='x_large' */ name='search_off' />
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user