displaying bar graph, fetching distributions message
This commit is contained in:
27
package-lock.json
generated
27
package-lock.json
generated
@@ -11,6 +11,8 @@
|
|||||||
"chrome-extension-toolkit": "^0.0.23",
|
"chrome-extension-toolkit": "^0.0.23",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"highcharts": "^10.3.3",
|
||||||
|
"highcharts-react-official": "^3.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
@@ -8213,6 +8215,20 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/highcharts": {
|
||||||
|
"version": "10.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.3.tgz",
|
||||||
|
"integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw=="
|
||||||
|
},
|
||||||
|
"node_modules/highcharts-react-official": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-71IJZsLmEboYFjONpwC3NRsg6JKvtKYtS5Si3e6s6MLRSOFNOY8KILTkzvO36kjpeR/A0X3/kvvewE+GMPpkjw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"highcharts": ">=6.0.0",
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hook-std": {
|
"node_modules/hook-std": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
||||||
@@ -23388,6 +23404,17 @@
|
|||||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"highcharts": {
|
||||||
|
"version": "10.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.3.tgz",
|
||||||
|
"integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw=="
|
||||||
|
},
|
||||||
|
"highcharts-react-official": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-71IJZsLmEboYFjONpwC3NRsg6JKvtKYtS5Si3e6s6MLRSOFNOY8KILTkzvO36kjpeR/A0X3/kvvewE+GMPpkjw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"hook-std": {
|
"hook-std": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"chrome-extension-toolkit": "^0.0.23",
|
"chrome-extension-toolkit": "^0.0.23",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"highcharts": "^10.3.3",
|
||||||
|
"highcharts-react-official": "^3.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { SessionStore } from './storage/SessionStore';
|
|||||||
import browserActionHandler from './handler/browserActionHandler';
|
import browserActionHandler from './handler/browserActionHandler';
|
||||||
import hotReloadingHandler from './handler/hotReloadingHandler';
|
import hotReloadingHandler from './handler/hotReloadingHandler';
|
||||||
import tabManagementHandler from './handler/tabManagementHandler';
|
import tabManagementHandler from './handler/tabManagementHandler';
|
||||||
|
import courseDataHandler from './handler/courseDataHandler';
|
||||||
|
|
||||||
onServiceWorkerAlive();
|
onServiceWorkerAlive();
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
|||||||
...browserActionHandler,
|
...browserActionHandler,
|
||||||
...hotReloadingHandler,
|
...hotReloadingHandler,
|
||||||
...tabManagementHandler,
|
...tabManagementHandler,
|
||||||
|
...courseDataHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
messageListener.listen();
|
messageListener.listen();
|
||||||
|
|||||||
14
src/background/handler/courseDataHandler.ts
Normal file
14
src/background/handler/courseDataHandler.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
|
import CourseDataMessages from 'src/shared/messages/CourseDataMessages';
|
||||||
|
|
||||||
|
const courseDataHandler: MessageHandler<CourseDataMessages> = {
|
||||||
|
getDistribution({ data, sendResponse }) {
|
||||||
|
const { course } = data;
|
||||||
|
|
||||||
|
const dummyData = Array.from({ length: 18 }, () => Math.floor(Math.random() * 100));
|
||||||
|
|
||||||
|
sendResponse(dummyData);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default courseDataHandler;
|
||||||
9
src/shared/messages/CourseDataMessages.ts
Normal file
9
src/shared/messages/CourseDataMessages.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Course } from '../types/Course';
|
||||||
|
|
||||||
|
export default interface CourseDataMessages {
|
||||||
|
/**
|
||||||
|
* Gets the distribution of grades for the given course
|
||||||
|
* @returns an array of the number of students in each grade range from A+ to F, with the index of the array corresponding to the grade range
|
||||||
|
*/
|
||||||
|
getDistribution: (data: { course: Course }) => number[] | undefined;
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* This is a type with all the message definitions that can be sent TO specific tabs
|
* This is a type with all the message definitions that can be sent TO specific tabs
|
||||||
*/
|
*/
|
||||||
export default interface TAB_MESSAGES {
|
export default interface TAB_MESSAGES {}
|
||||||
reAnalyzePage: (data: { url: string }) => void;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import TAB_MESSAGES from './TabMessages';
|
|||||||
import BrowserActionMessages from './BrowserActionMessages';
|
import BrowserActionMessages from './BrowserActionMessages';
|
||||||
import HotReloadingMessages from './HotReloadingMessages';
|
import HotReloadingMessages from './HotReloadingMessages';
|
||||||
import TabManagementMessages from './TabManagementMessages';
|
import TabManagementMessages from './TabManagementMessages';
|
||||||
|
import CourseDataMessages from './CourseDataMessages';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a type with all the message definitions that can be sent TO the background script
|
* This is a type with all the message definitions that can be sent TO the background script
|
||||||
*/
|
*/
|
||||||
export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & HotReloadingMessages;
|
export type BACKGROUND_MESSAGES = BrowserActionMessages &
|
||||||
|
TabManagementMessages &
|
||||||
|
HotReloadingMessages &
|
||||||
|
CourseDataMessages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility object that can be used to send type-safe messages to the background script
|
* A utility object that can be used to send type-safe messages to the background script
|
||||||
*/
|
*/
|
||||||
export const bMessenger = createMessenger<BACKGROUND_MESSAGES>('background');
|
export const bMessenger = createMessenger<BACKGROUND_MESSAGES>('background');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility object that can be used to send type-safe messages to specific tabs
|
* A utility object that can be used to send type-safe messages to specific tabs
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export type Semester = {
|
|||||||
/**
|
/**
|
||||||
* The internal representation of a course for the extension
|
* The internal representation of a course for the extension
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Course {
|
export class Course {
|
||||||
/** Every course has a uniqueId within UT's registrar system corresponding to each course section */
|
/** Every course has a uniqueId within UT's registrar system corresponding to each course section */
|
||||||
uniqueId: number;
|
uniqueId: number;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import Card from '../../common/Card/Card';
|
||||||
import Popup from '../../common/Popup/Popup';
|
import Popup from '../../common/Popup/Popup';
|
||||||
import CourseDescription from './CourseDescription/CourseDescription';
|
import CourseDescription from './CourseDescription/CourseDescription';
|
||||||
import CourseHeader from './CourseHeader/CourseHeader';
|
import CourseHeader from './CourseHeader/CourseHeader';
|
||||||
import styles from './CoursePopup.module.scss';
|
import styles from './CoursePopup.module.scss';
|
||||||
|
import GradeDistribution from './GradeDistribution/GradeDistribution';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
course: Course;
|
course: Course;
|
||||||
@@ -19,6 +21,7 @@ export default function CoursePopup({ course, onClose }: Props) {
|
|||||||
<Popup className={styles.popup} overlay onClose={onClose}>
|
<Popup className={styles.popup} overlay onClose={onClose}>
|
||||||
<CourseHeader course={course} onClose={onClose} />
|
<CourseHeader course={course} onClose={onClose} />
|
||||||
<CourseDescription course={course} />
|
<CourseDescription course={course} />
|
||||||
|
<GradeDistribution course={course} />
|
||||||
</Popup>
|
</Popup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import HighchartsReact from 'highcharts-react-official';
|
||||||
|
import Highcharts from 'highcharts';
|
||||||
|
import Card from 'src/views/components/common/Card/Card';
|
||||||
|
import { bMessenger } from 'src/shared/messages';
|
||||||
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import styles from './GradeDistribution.module.scss';
|
||||||
|
import colors from 'src/views/styles/colors.module.scss';
|
||||||
|
|
||||||
|
enum DataStatus {
|
||||||
|
LOADING = 'LOADING',
|
||||||
|
DONE = 'DONE',
|
||||||
|
ERROR = 'ERROR',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
course: Course;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GradeDistribution({ course }: Props) {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credits: {
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const [status, setStatus] = useState<DataStatus>(DataStatus.LOADING);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
bMessenger.getDistribution({ course }).then(distribution => {
|
||||||
|
if (!distribution) {
|
||||||
|
return setStatus(DataStatus.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
setChartOptions(options => ({
|
||||||
|
...options,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
name: 'Grades',
|
||||||
|
data: distribution.map((y, i) => ({
|
||||||
|
y,
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
// color: i < 8 ? '#2ECC71' : i < 10 ? '#F1C40F' : '#E74C3C',
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
color: i < 8 ? colors.cactus : i < 10 ? colors.sunshine : colors.speedway_brick,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}, [course]);
|
||||||
|
|
||||||
|
if (!chartOptions.series?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={styles.container}>
|
||||||
|
<HighchartsReact highcharts={Highcharts} options={chartOptions} />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user