├── src
├── app
│ ├── components
│ │ ├── index.ts
│ │ ├── Footer
│ │ │ ├── index.tsx
│ │ │ └── style.css
│ │ ├── Lesson
│ │ │ └── index.tsx
│ │ ├── AutoTheme
│ │ │ └── index.tsx
│ │ ├── Header
│ │ │ └── index.tsx
│ │ ├── icons.tsx
│ │ ├── CourseList
│ │ │ └── index.tsx
│ │ ├── CurvePieChart
│ │ │ └── index.tsx
│ │ ├── CurveChart
│ │ │ └── index.tsx
│ │ └── Course
│ │ │ └── index.tsx
│ ├── constants
│ │ ├── index.ts
│ │ ├── stores.ts
│ │ ├── todos.ts
│ │ └── steps.ts
│ ├── stores
│ │ ├── index.ts
│ │ ├── RouterStore.ts
│ │ ├── GlobalStateStore.ts
│ │ ├── createStore.ts
│ │ ├── CoursesStore.ts
│ │ └── TodoStore.ts
│ ├── models
│ │ ├── index.ts
│ │ ├── TodoModel.ts
│ │ └── CourseModel.ts
│ ├── containers
│ │ ├── CourseListPage
│ │ │ └── index.tsx
│ │ ├── CoursePage
│ │ │ └── index.tsx
│ │ ├── Main
│ │ │ └── index.tsx
│ │ └── Root
│ │ │ └── index.tsx
│ └── index.tsx
├── assets
│ ├── favicon.ico
│ └── index.html
└── main.tsx
├── data
└── stats.json
├── docs
├── favicon.ico
├── 020c97dc8e0463259c2f9df929bb0c69.woff2
├── 14286f3ba79c6627433572dfa925202e.woff2
├── 2735a3a69b509faf3577afd25bdf552e.woff2
├── 288ad9c6e8b43cf02443a1f499bdf67e.woff
├── 28f9151055c950874d2c6803a39b425b.woff
├── 479970ffb74f2117317f9d24d9e317fe.woff2
├── 4df32891a5f2f98a363314f595482e08.woff
├── 51521a2a8da71e50d871ac6fd2187e87.woff2
├── 5cb7edfceb233100075dc9a1e12e8da3.woff
├── 60fa3c0614b8fb2f394fa29944c21540.woff
├── 7370c3679472e9560965ff48a4399d0b.woff2
├── 81f57861ed4ac74741f5671e1dff2fd9.woff
├── 87284894879f5b1c229cb49c8ff6decc.woff
├── 9b3766ef4a402ad3fdeef7501a456512.woff2
├── adcde98f1d584de52060ad7b16373da3.woff
├── b00849e00f4c2331cddd8ffb44a6720b.woff
├── bb1e4dc6333675d11ada2e857e7f95d7.woff
├── da0e717829e033a69dec97f1e155ae42.woff2
├── db4a2a231f52e497c0191e8966b0ee58.woff2
├── ebf6d1640ccddb99fb49f73c052c55a8.woff2
├── ef7c6637c68f269a882e73bcb57a7f6a.woff2
├── f8b1df51ba843179fa1cc9b53d58127a.woff2
├── f9e8e590b4e0f1ff83469bb2a55b8488.woff
├── fe65b8335ee19dd944289f9ed3178c78.woff
├── index.html
├── 088add6757803b6550ca.js
├── 088add6757803b6550ca.js.map
├── 2.37343606062d2e5d65aa.css
├── 2.37343606062d2e5d65aa.css.map
└── app.7eb610b1c7157386632c.js.map
├── types
└── global.d.ts
├── tsconfig.json
├── README.md
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── package.json
└── webpack.config.js
/src/app/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Footer";
2 | export * from "./Header";
3 |
--------------------------------------------------------------------------------
/src/app/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./stores";
2 | export * from "./todos";
3 |
--------------------------------------------------------------------------------
/data/stats.json:
--------------------------------------------------------------------------------
1 | {
2 | "courses": 624,
3 | "lessons": 1862,
4 | "scores": 75660
5 | }
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/favicon.ico
--------------------------------------------------------------------------------
/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/src/assets/favicon.ico
--------------------------------------------------------------------------------
/docs/020c97dc8e0463259c2f9df929bb0c69.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/020c97dc8e0463259c2f9df929bb0c69.woff2
--------------------------------------------------------------------------------
/docs/14286f3ba79c6627433572dfa925202e.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/14286f3ba79c6627433572dfa925202e.woff2
--------------------------------------------------------------------------------
/docs/2735a3a69b509faf3577afd25bdf552e.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/2735a3a69b509faf3577afd25bdf552e.woff2
--------------------------------------------------------------------------------
/docs/288ad9c6e8b43cf02443a1f499bdf67e.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/288ad9c6e8b43cf02443a1f499bdf67e.woff
--------------------------------------------------------------------------------
/docs/28f9151055c950874d2c6803a39b425b.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/28f9151055c950874d2c6803a39b425b.woff
--------------------------------------------------------------------------------
/docs/479970ffb74f2117317f9d24d9e317fe.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/479970ffb74f2117317f9d24d9e317fe.woff2
--------------------------------------------------------------------------------
/docs/4df32891a5f2f98a363314f595482e08.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/4df32891a5f2f98a363314f595482e08.woff
--------------------------------------------------------------------------------
/docs/51521a2a8da71e50d871ac6fd2187e87.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/51521a2a8da71e50d871ac6fd2187e87.woff2
--------------------------------------------------------------------------------
/docs/5cb7edfceb233100075dc9a1e12e8da3.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/5cb7edfceb233100075dc9a1e12e8da3.woff
--------------------------------------------------------------------------------
/docs/60fa3c0614b8fb2f394fa29944c21540.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/60fa3c0614b8fb2f394fa29944c21540.woff
--------------------------------------------------------------------------------
/docs/7370c3679472e9560965ff48a4399d0b.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/7370c3679472e9560965ff48a4399d0b.woff2
--------------------------------------------------------------------------------
/docs/81f57861ed4ac74741f5671e1dff2fd9.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/81f57861ed4ac74741f5671e1dff2fd9.woff
--------------------------------------------------------------------------------
/docs/87284894879f5b1c229cb49c8ff6decc.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/87284894879f5b1c229cb49c8ff6decc.woff
--------------------------------------------------------------------------------
/docs/9b3766ef4a402ad3fdeef7501a456512.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/9b3766ef4a402ad3fdeef7501a456512.woff2
--------------------------------------------------------------------------------
/docs/adcde98f1d584de52060ad7b16373da3.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/adcde98f1d584de52060ad7b16373da3.woff
--------------------------------------------------------------------------------
/docs/b00849e00f4c2331cddd8ffb44a6720b.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/b00849e00f4c2331cddd8ffb44a6720b.woff
--------------------------------------------------------------------------------
/docs/bb1e4dc6333675d11ada2e857e7f95d7.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/bb1e4dc6333675d11ada2e857e7f95d7.woff
--------------------------------------------------------------------------------
/docs/da0e717829e033a69dec97f1e155ae42.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/da0e717829e033a69dec97f1e155ae42.woff2
--------------------------------------------------------------------------------
/docs/db4a2a231f52e497c0191e8966b0ee58.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/db4a2a231f52e497c0191e8966b0ee58.woff2
--------------------------------------------------------------------------------
/docs/ebf6d1640ccddb99fb49f73c052c55a8.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/ebf6d1640ccddb99fb49f73c052c55a8.woff2
--------------------------------------------------------------------------------
/docs/ef7c6637c68f269a882e73bcb57a7f6a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/ef7c6637c68f269a882e73bcb57a7f6a.woff2
--------------------------------------------------------------------------------
/docs/f8b1df51ba843179fa1cc9b53d58127a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/f8b1df51ba843179fa1cc9b53d58127a.woff2
--------------------------------------------------------------------------------
/docs/f9e8e590b4e0f1ff83469bb2a55b8488.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/f9e8e590b4e0f1ff83469bb2a55b8488.woff
--------------------------------------------------------------------------------
/docs/fe65b8335ee19dd944289f9ed3178c78.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SurviveUMJI/ji-grade-analysis/HEAD/docs/fe65b8335ee19dd944289f9ed3178c78.woff
--------------------------------------------------------------------------------
/types/global.d.ts:
--------------------------------------------------------------------------------
1 | /** Global definitions for developement **/
2 |
3 | // for style loader
4 | declare module '*.css' {
5 | const styles: any;
6 | export = styles;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./TodoStore";
2 | export * from "./RouterStore";
3 | export * from "./CoursesStore";
4 | export * from "./GlobalStateStore";
5 | export * from "./createStore";
6 |
--------------------------------------------------------------------------------
/src/app/constants/stores.ts:
--------------------------------------------------------------------------------
1 | export const STORE_TODO = "todo";
2 | export const STORE_ROUTER = "router";
3 | export const STORE_COURSES = "courses";
4 | export const STORE_GLOBAL_STATE = "global_state";
5 |
--------------------------------------------------------------------------------
/src/app/models/index.ts:
--------------------------------------------------------------------------------
1 | import TodoModel from "./TodoModel";
2 | import { CourseModel, ScoreModel, LessonModel } from "./CourseModel";
3 |
4 | export { TodoModel };
5 | export { CourseModel, ScoreModel, LessonModel };
6 |
--------------------------------------------------------------------------------
/src/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JI Grade Analysis
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/stores/RouterStore.ts:
--------------------------------------------------------------------------------
1 | import { History } from "history";
2 | import {
3 | RouterStore as BaseRouterStore,
4 | syncHistoryWithStore,
5 | } from "mobx-react-router";
6 |
7 | export class RouterStore extends BaseRouterStore {
8 | constructor(history?: History) {
9 | super();
10 | if (history) {
11 | this.history = syncHistoryWithStore(history, this);
12 | }
13 | }
14 | }
15 |
16 | export default RouterStore;
17 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JI Grade Analysis
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/models/TodoModel.ts:
--------------------------------------------------------------------------------
1 | import { observable } from "mobx";
2 |
3 | export class TodoModel {
4 | readonly id: number;
5 | @observable public text: string;
6 | @observable public completed: boolean;
7 |
8 | constructor(text: string, completed: boolean = false) {
9 | this.id = TodoModel.generateId();
10 | this.text = text;
11 | this.completed = completed;
12 | }
13 |
14 | static nextId = 1;
15 | static generateId() {
16 | return this.nextId++;
17 | }
18 | }
19 |
20 | export default TodoModel;
21 |
--------------------------------------------------------------------------------
/src/app/constants/todos.ts:
--------------------------------------------------------------------------------
1 | export enum TodoFilter {
2 | ALL = 0,
3 | ACTIVE,
4 | COMPLETED,
5 | }
6 |
7 | export const TODO_FILTER_TYPES = [
8 | TodoFilter.ALL,
9 | TodoFilter.ACTIVE,
10 | TodoFilter.COMPLETED,
11 | ];
12 |
13 | export const TODO_FILTER_TITLES = {
14 | [TodoFilter.ALL]: "All",
15 | [TodoFilter.ACTIVE]: "Active",
16 | [TodoFilter.COMPLETED]: "Completed",
17 | };
18 |
19 | export const TODO_FILTER_LOCATION_HASH = {
20 | [TodoFilter.ALL]: "#",
21 | [TodoFilter.ACTIVE]: "#active",
22 | [TodoFilter.COMPLETED]: "#completed",
23 | };
24 |
--------------------------------------------------------------------------------
/src/app/constants/steps.ts:
--------------------------------------------------------------------------------
1 | import { Dictionary } from "lodash";
2 |
3 | export enum ReportStep {
4 | MAIN,
5 | INTRODUCTION,
6 | DATA_SHEET,
7 | }
8 |
9 | export const REPORT_STEP_TYPES = [
10 | ReportStep.MAIN,
11 | ReportStep.INTRODUCTION,
12 | ReportStep.DATA_SHEET,
13 | ];
14 |
15 | export const REPORT_STEP_LABELS: Dictionary = {
16 | [ReportStep.MAIN]: "Select a Lab",
17 | [ReportStep.INTRODUCTION]: "Introduction",
18 | [ReportStep.DATA_SHEET]: "Data Sheet",
19 | };
20 |
21 | export const REPORT_STEP_URL: Dictionary = {
22 | [ReportStep.MAIN]: "/",
23 | [ReportStep.INTRODUCTION]: "/introduction",
24 | [ReportStep.DATA_SHEET]: "/data_sheet",
25 | };
26 |
--------------------------------------------------------------------------------
/src/app/stores/GlobalStateStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from "mobx";
2 | import { Dictionary } from "lodash";
3 |
4 | export class GlobalStateStore {
5 | @observable public courseListSearchText: string;
6 | @observable public courseSearchText: Dictionary;
7 |
8 | constructor() {
9 | this.courseListSearchText = "";
10 | this.courseSearchText = {};
11 | }
12 |
13 | @action
14 | setCourseListSearchText = (searchText: string): void => {
15 | this.courseListSearchText = searchText;
16 | };
17 |
18 | @action
19 | setCourseSearchText = (course: string, searchText: string): void => {
20 | this.courseSearchText[course] = searchText;
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "target": "es5",
5 | "jsx": "react",
6 | "module": "es6",
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "declaration": false,
11 | "noImplicitAny": false,
12 | "noImplicitReturns": false,
13 | "noUnusedLocals": false,
14 | "removeComments": true,
15 | "strictNullChecks": false,
16 | "resolveJsonModule": true,
17 | "allowSyntheticDefaultImports": true,
18 | "outDir": "build",
19 | "lib": ["es6", "es7", "dom"],
20 | "baseUrl": "src",
21 | "paths": {
22 | "app/*": ["./app/*"]
23 | }
24 | },
25 | "exclude": ["dist", "build", "node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as ReactDOM from "react-dom";
3 | import { Provider } from "mobx-react";
4 |
5 | import { createHashHistory } from "history";
6 | // import {TodoModel} from 'app/models';
7 | import { createStores } from "app/stores";
8 | import { App } from "app";
9 |
10 | import "typeface-roboto";
11 |
12 | // default fixtures for TodoStore
13 | // const defaultTodos = [
14 | // new TodoModel('Use Mobx'),
15 | // new TodoModel('Use React', true),
16 | // ];
17 |
18 | // prepare MobX stores
19 | const history = createHashHistory();
20 | const rootStore = createStores(history, []);
21 |
22 | // render react DOM
23 | ReactDOM.render(
24 |
25 |
26 | ,
27 | document.getElementById("root")
28 | );
29 |
--------------------------------------------------------------------------------
/src/app/models/CourseModel.ts:
--------------------------------------------------------------------------------
1 | import { observable } from "mobx";
2 |
3 | export class ScoreModel {
4 | readonly courseCode: string;
5 | readonly lessonClassCode: string;
6 | readonly scores: number[];
7 | }
8 |
9 | export class LessonModel {
10 | readonly lessonClassCode: string;
11 | readonly courseCode: string;
12 | readonly lessonClassName: string;
13 | readonly termName: string;
14 | readonly studentNum: number;
15 | readonly scoreNum: number;
16 | readonly lecturers: string[];
17 | @observable public lecturersStr?: string;
18 | @observable public studentNumStr?: string;
19 | }
20 |
21 | export class CourseModel {
22 | readonly courseCode: string;
23 | readonly courseName: string;
24 | readonly courseNameEn: string;
25 | readonly lessons: Array;
26 | readonly terms: string;
27 | }
28 |
29 | export default CourseModel;
30 |
--------------------------------------------------------------------------------
/src/app/containers/CourseListPage/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { inject, observer } from "mobx-react";
4 | import { RouteComponentProps } from "react-router";
5 | import { CourseList } from "app/components/CourseList";
6 |
7 | import { STORE_ROUTER } from "app/constants";
8 | // import {
9 | // Container,
10 | // CssBaseline, Paper
11 | // } from '@material-ui/core';
12 |
13 | export interface CourseListPageProps extends RouteComponentProps {}
14 |
15 | export interface CourseListPageState {}
16 |
17 | @inject(STORE_ROUTER)
18 | @observer
19 | export class CourseListPage extends React.Component<
20 | CourseListPageProps,
21 | CourseListPageState
22 | > {
23 | constructor(props: CourseListPageProps, context: any) {
24 | super(props, context);
25 | this.state = {};
26 | }
27 |
28 | render() {
29 | return ;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/stores/createStore.ts:
--------------------------------------------------------------------------------
1 | import { History } from "history";
2 | import { TodoModel } from "app/models";
3 | import { TodoStore } from "./TodoStore";
4 | import { RouterStore } from "./RouterStore";
5 | import { CoursesStore } from "./CoursesStore";
6 | import { GlobalStateStore } from "./GlobalStateStore";
7 |
8 | import {
9 | STORE_TODO,
10 | STORE_ROUTER,
11 | STORE_COURSES,
12 | STORE_GLOBAL_STATE,
13 | } from "app/constants";
14 |
15 | export function createStores(history: History, defaultTodos?: TodoModel[]) {
16 | const todoStore = new TodoStore(defaultTodos);
17 | const routerStore = new RouterStore(history);
18 | const coursesStore = new CoursesStore();
19 | const globalStateStore = new GlobalStateStore();
20 | return {
21 | [STORE_TODO]: todoStore,
22 | [STORE_ROUTER]: routerStore,
23 | [STORE_COURSES]: coursesStore,
24 | [STORE_GLOBAL_STATE]: globalStateStore,
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JI Grade Analysis
2 |
3 | Analysis of anonymous grades retrieved by all JI students,
4 | among the courses with the same course id in different
5 | sections and academic years, aiming at forming a better
6 | and fairer GPA environment in JI.
7 |
8 | ## Built with
9 |
10 | - [Typescript](https://www.typescriptlang.org/) 3.2
11 | - [React](https://facebook.github.io/react/) 16.7
12 | - [React Router](https://github.com/ReactTraining/react-router) 4
13 | - [Mobx](https://github.com/mobxjs/mobx) 5
14 | - [Material UI](https://github.com/mui-org/material-ui) 4.6
15 | - [Material Table](https://github.com/mbrn/material-table)
16 | - [React Chart](https://github.com/DevExpress/devextreme-reactive)
17 |
18 | ## Setup
19 |
20 | ```
21 | $ yarn
22 | ```
23 |
24 | ## Running
25 |
26 | ```
27 | $ yarn start
28 | ```
29 |
30 | ## Build
31 |
32 | ```
33 | $ yarn run build
34 | ```
35 |
36 | ## Code Format
37 |
38 | ```
39 | $ yarn run prettier
40 | ```
41 |
42 | # License
43 |
44 | MIT
45 |
--------------------------------------------------------------------------------
/src/app/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Grid, Link } from "@material-ui/core";
3 | import packageData from "app/../../package.json";
4 |
5 | export interface FooterProps {}
6 |
7 | export interface FooterState {}
8 |
9 | export class Footer extends React.Component {
10 | render() {
11 | return (
12 |
30 | );
31 | }
32 | }
33 |
34 | export default Footer;
35 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Deploy to GitHub Pages
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: npm i -g yarn
28 | - run: yarn
29 | - run: yarn build
30 | - name: Deploy to GitHub Pages
31 | if: success()
32 | uses: crazy-max/ghaction-github-pages@v2
33 | with:
34 | target_branch: gh-pages
35 | build_dir: build
36 | keep_history: true
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime raw_data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # next.js build output
63 | .next
64 |
65 | .idea
66 | raw_data
67 |
--------------------------------------------------------------------------------
/src/app/stores/CoursesStore.ts:
--------------------------------------------------------------------------------
1 | import { observable } from "mobx";
2 | import _ from "lodash";
3 |
4 | import { CourseModel, LessonModel, ScoreModel } from "app/models";
5 |
6 | import courses from "ji-grade-analysis-data/courses.json";
7 | import lessons from "ji-grade-analysis-data/lessons.json";
8 | import scores from "ji-grade-analysis-data/scores.json";
9 | import { Dictionary } from "lodash";
10 |
11 | export class CoursesStore {
12 | @observable public courses: Array;
13 | @observable public coursesMap: Dictionary;
14 | @observable public lessonsMap: Dictionary;
15 | @observable public scoresMap: Dictionary;
16 |
17 | constructor() {
18 | this.courses = courses;
19 | this.coursesMap = _.keyBy(courses, "courseCode");
20 | this.lessonsMap = _.keyBy(lessons, "lessonClassCode");
21 | this.scoresMap = _.keyBy(scores, "lessonClassCode");
22 | /*scores.forEach(score => {
23 | const courseCode = score.courseCode;
24 | if (this.coursesMap.hasOwnProperty(courseCode)) {
25 | if (!this.coursesMap[courseCode].hasOwnProperty('sections')) {
26 | this.coursesMap[courseCode].sections = [];
27 | }
28 | this.coursesMap[courseCode].sections.push(score);
29 | console.log(score, this.coursesMap[courseCode].sections)
30 | }
31 | });*/
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/containers/CoursePage/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { inject, observer } from "mobx-react";
4 | import { RouteComponentProps } from "react-router";
5 | import { Course } from "app/components/Course";
6 |
7 | import { STORE_COURSES } from "app/constants";
8 | import { CoursesStore } from "app/stores/CoursesStore";
9 |
10 | // import {RouterStore} from 'app/stores';
11 |
12 | export interface CoursePageProps extends RouteComponentProps {}
13 |
14 | export interface CoursePageState {
15 | courseCode: string;
16 | available: boolean;
17 | }
18 |
19 | @inject(STORE_COURSES)
20 | @observer
21 | export class CoursePage extends React.Component<
22 | CoursePageProps,
23 | CoursePageState
24 | > {
25 | constructor(props: CoursePageProps, context: any) {
26 | super(props, context);
27 | const { match } = this.props;
28 | const courseStore = this.props[STORE_COURSES] as CoursesStore;
29 | const courseCode = match.params.courseCode.toUpperCase();
30 | this.state = {
31 | courseCode: courseCode,
32 | available: courseStore.coursesMap.hasOwnProperty(courseCode),
33 | };
34 | }
35 |
36 | render() {
37 | return this.state.available ? (
38 |
39 | ) : (
40 | Sorry, course {this.state.courseCode} is not available.
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/app/components/Lesson/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Grid } from "@material-ui/core";
3 | import { CurveChart } from "app/components/CurveChart";
4 |
5 | export interface ScoreData {
6 | grade: string;
7 | count: number;
8 | }
9 |
10 | export interface LessonProps {
11 | /* empty */
12 | scores: ScoreData[];
13 | lessonClassCode: string;
14 | chartType: string;
15 | hideUnknown: boolean;
16 | hideZero: boolean;
17 | }
18 |
19 | export interface LessonState {}
20 |
21 | export class Lesson extends React.Component {
22 | constructor(props: LessonProps, context: any) {
23 | super(props, context);
24 | // console.log(this.props.lessonClassCode)
25 | }
26 |
27 | /* getColor() {
28 | const colors = ['#fdca00', '#19335d'];
29 | // console.log(this.props.lessonClassCode);
30 | const digit = this.props.lessonClassCode.charCodeAt(
31 | this.props.lessonClassCode.length - 1);
32 | return colors[digit % 2];
33 | }*/
34 |
35 | render() {
36 | return (
37 |
38 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/docs/088add6757803b6550ca.js:
--------------------------------------------------------------------------------
1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c {
6 | const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
7 | const theme = React.useMemo(
8 | () =>
9 | createTheme({
10 | palette: {
11 | type: prefersDarkMode ? "dark" : "light",
12 | primary: {
13 | light: "#a6d4fa",
14 | main: "#90caf9",
15 | dark: "#648dae",
16 | },
17 | secondary: {
18 | light: "#f6a5c0",
19 | main: "#f48fb1",
20 | dark: "#aa647b",
21 | },
22 | error: {
23 | light: "#e57373",
24 | main: "#f44336",
25 | dark: "#d32f2f",
26 | },
27 | warning: {
28 | light: "#ffb74d",
29 | main: "#ff9800",
30 | dark: "#f57c00",
31 | },
32 | info: {
33 | light: "#64b5f6",
34 | main: "#2196f3",
35 | dark: "#1976d2",
36 | },
37 | success: {
38 | light: "#81c784",
39 | main: "#4caf50",
40 | dark: "#388e3c",
41 | },
42 | },
43 | }),
44 | [prefersDarkMode]
45 | );
46 | return {children};
47 | };
48 |
49 | export default AutoTheme;
50 |
--------------------------------------------------------------------------------
/src/app/stores/TodoStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from "mobx";
2 | import { TodoModel } from "app/models";
3 |
4 | export class TodoStore {
5 | constructor(fixtures: TodoModel[]) {
6 | this.todos = fixtures;
7 | }
8 |
9 | @observable public todos: Array;
10 |
11 | @computed
12 | get activeTodos() {
13 | return this.todos.filter((todo) => !todo.completed);
14 | }
15 |
16 | @computed
17 | get completedTodos() {
18 | return this.todos.filter((todo) => todo.completed);
19 | }
20 |
21 | @action
22 | addTodo = (item: Partial): void => {
23 | this.todos.push(new TodoModel(item.text, item.completed));
24 | };
25 |
26 | @action
27 | editTodo = (id: number, data: Partial): void => {
28 | this.todos = this.todos.map((todo) => {
29 | if (todo.id === id) {
30 | if (typeof data.completed == "boolean") {
31 | todo.completed = data.completed;
32 | }
33 | if (typeof data.text == "string") {
34 | todo.text = data.text;
35 | }
36 | }
37 | return todo;
38 | });
39 | };
40 |
41 | @action
42 | deleteTodo = (id: number): void => {
43 | this.todos = this.todos.filter((todo) => todo.id !== id);
44 | };
45 |
46 | @action
47 | completeAll = (): void => {
48 | this.todos = this.todos.map((todo) => ({ ...todo, completed: true }));
49 | };
50 |
51 | @action
52 | clearCompleted = (): void => {
53 | this.todos = this.todos.filter((todo) => !todo.completed);
54 | };
55 | }
56 |
57 | export default TodoStore;
58 |
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useEffect } from "react";
3 | import { hot } from "react-hot-loader/root";
4 | import { Router, Route, Switch } from "react-router";
5 | import statistics from "busuanzi-statistics";
6 |
7 | import Root from "app/containers/Root";
8 | import { Main } from "app/containers/Main";
9 | import { CourseListPage } from "app/containers/CourseListPage";
10 | import { CoursePage } from "app/containers/CoursePage";
11 |
12 | // render react DOM
13 | export const App = hot(({ history }) => {
14 | useEffect(() => {
15 | // do not delete this line
16 | console.log(statistics);
17 | // Anything in here is fired on component mount.
18 | // setTimeout(statistics, 0);
19 | /*const handleLocationChange = (location) => {
20 | // Do something with the location
21 | // console.log(location);
22 | // console.log("busuanzi")
23 | statistics();
24 | };
25 | const unsubscribeFromHistory = history.listen(handleLocationChange);
26 | handleLocationChange(history.location);*/
27 | return () => {
28 | // Anything in here is fired on component unmount.
29 | // if (unsubscribeFromHistory) unsubscribeFromHistory();
30 | };
31 | }, []);
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | });
44 |
--------------------------------------------------------------------------------
/src/app/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Grid, Typography } from "@material-ui/core";
3 | import { Link } from "react-router-dom";
4 | import { GitHubButton, GitHubButtonProvider } from "react-github-button";
5 | import "react-github-button/assets/style.less";
6 |
7 | export interface HeaderProps {
8 | namespace: string;
9 | repo: string;
10 | /* empty */
11 | }
12 |
13 | export interface HeaderState {
14 | /* empty */
15 | }
16 |
17 | export class Header extends React.Component {
18 | render() {
19 | return (
20 |
21 |
22 |
29 |
33 | UM-SJTU JI Grade Analysis
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | export default Header;
59 |
--------------------------------------------------------------------------------
/src/app/components/Footer/style.css:
--------------------------------------------------------------------------------
1 | .normal {
2 | color: #777;
3 | padding: 10px 15px;
4 | height: 20px;
5 | text-align: center;
6 | border-top: 1px solid #e6e6e6;
7 | }
8 |
9 | .normal:before {
10 | content: "";
11 | position: absolute;
12 | right: 0;
13 | bottom: 0;
14 | left: 0;
15 | height: 50px;
16 | overflow: hidden;
17 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
18 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
19 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
20 | }
21 |
22 | .filters {
23 | margin: 0;
24 | padding: 0;
25 | list-style: none;
26 | position: absolute;
27 | right: 0;
28 | left: 0;
29 | }
30 |
31 | .filters li {
32 | display: inline;
33 | }
34 |
35 | .filters li a {
36 | color: inherit;
37 | margin: 3px;
38 | padding: 3px 7px;
39 | text-decoration: none;
40 | border: 1px solid transparent;
41 | border-radius: 3px;
42 | }
43 |
44 | .filters li a.selected,
45 | .filters li a:hover {
46 | border-color: rgba(175, 47, 47, 0.1);
47 | }
48 |
49 | .filters li a.selected {
50 | border-color: rgba(175, 47, 47, 0.2);
51 | }
52 |
53 | .count {
54 | float: left;
55 | text-align: left;
56 | }
57 |
58 | .count strong {
59 | font-weight: 300;
60 | }
61 |
62 | .clearCompleted,
63 | html .clearCompleted:active {
64 | float: right;
65 | position: relative;
66 | line-height: 20px;
67 | text-decoration: none;
68 | cursor: pointer;
69 | visibility: hidden;
70 | position: relative;
71 | }
72 |
73 | .clearCompleted::after {
74 | visibility: visible;
75 | content: "Clear completed";
76 | position: absolute;
77 | right: 0;
78 | white-space: nowrap;
79 | }
80 |
81 | .clearCompleted:hover::after {
82 | text-decoration: underline;
83 | }
84 |
85 | @media (max-width: 430px) {
86 | .normal {
87 | height: 50px;
88 | }
89 | .filters {
90 | bottom: 10px;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/app/containers/Main/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { inject, observer } from "mobx-react";
4 | import { RouteComponentProps } from "react-router";
5 | import { Link as RouterLink } from "react-router-dom";
6 |
7 | import { STORE_ROUTER } from "app/constants";
8 | import { Button, Grid, Typography, Link } from "@material-ui/core";
9 | import statsData from "ji-grade-analysis-data/stats.json";
10 |
11 | export interface MainProps extends RouteComponentProps {}
12 |
13 | export interface MainState {}
14 |
15 | @inject(STORE_ROUTER)
16 | @observer
17 | export class Main extends React.Component {
18 | constructor(props: MainProps, context: any) {
19 | super(props, context);
20 | this.state = {};
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
27 | Analysis anonymous grades retrieved by all JI students, among the
28 | courses with the same course code in different sections and terms,
29 | aiming at forming a better and fairer GPA environment in JI.
30 |
31 |
32 | {statsData.scores} score data from{" "}
33 | {statsData.lessons} classes of{" "}
34 | {statsData.courses} courses have been
35 | analyzed.
36 |
37 |
38 |
47 |
48 |
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/containers/Root/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Container, CssBaseline, Grid, Paper } from "@material-ui/core";
3 | import Header from "app/components/Header";
4 | import Footer from "app/components/Footer";
5 | import AutoTheme from "app/components/AutoTheme";
6 |
7 | import {
8 | withStyles,
9 | createStyles,
10 | WithStyles,
11 | Theme,
12 | } from "@material-ui/core/styles";
13 | import { RouteComponentProps } from "react-router";
14 |
15 | const styles = (theme: Theme) =>
16 | createStyles({
17 | mainFeaturedPost: {
18 | backgroundColor: theme.palette.grey[800],
19 | color: theme.palette.common.white,
20 | marginBottom: theme.spacing(4),
21 | },
22 | mainFeaturedPostContent: {
23 | padding: `${theme.spacing(6)}px`,
24 | [theme.breakpoints.down("sm")]: {
25 | paddingRight: 0,
26 | paddingLeft: 0,
27 | },
28 | },
29 | });
30 |
31 | export interface RootProps
32 | extends RouteComponentProps,
33 | WithStyles {}
34 |
35 | export interface RootState {}
36 |
37 | class Root extends React.Component {
38 | renderDevTool() {
39 | if (process.env.NODE_ENV !== "production") {
40 | const DevTools = require("mobx-react-devtools").default;
41 | return ;
42 | }
43 | }
44 |
45 | render() {
46 | const { classes } = this.props;
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {this.props.children}
60 |
61 |
62 |
63 |
64 |
65 | {this.renderDevTool()}
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | const RootWithStyle = withStyles(styles)(Root);
73 | export default (props) => ;
74 |
--------------------------------------------------------------------------------
/src/app/components/icons.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { forwardRef } from "react";
3 | import AddBox from "@material-ui/icons/AddBox";
4 | import ArrowDownward from "@material-ui/icons/ArrowDownward";
5 | import Check from "@material-ui/icons/Check";
6 | import ChevronLeft from "@material-ui/icons/ChevronLeft";
7 | import ChevronRight from "@material-ui/icons/ChevronRight";
8 | import Clear from "@material-ui/icons/Clear";
9 | import DeleteOutline from "@material-ui/icons/DeleteOutline";
10 | import Edit from "@material-ui/icons/Edit";
11 | import FilterList from "@material-ui/icons/FilterList";
12 | import FirstPage from "@material-ui/icons/FirstPage";
13 | import LastPage from "@material-ui/icons/LastPage";
14 | import Remove from "@material-ui/icons/Remove";
15 | import SaveAlt from "@material-ui/icons/SaveAlt";
16 | import Search from "@material-ui/icons/Search";
17 | import ViewColumn from "@material-ui/icons/ViewColumn";
18 | import { Icons } from "@material-table/core";
19 |
20 | const icons: Icons = {
21 | Add: forwardRef((props, ref) => ),
22 | Check: forwardRef((props, ref) => ),
23 | Clear: forwardRef((props, ref) => ),
24 | Delete: forwardRef((props, ref) => ),
25 | DetailPanel: forwardRef((props, ref) => (
26 |
27 | )),
28 | Edit: forwardRef((props, ref) => ),
29 | Export: forwardRef((props, ref) => ),
30 | Filter: forwardRef((props, ref) => ),
31 | FirstPage: forwardRef((props, ref) => ),
32 | LastPage: forwardRef((props, ref) => ),
33 | NextPage: forwardRef((props, ref) => ),
34 | PreviousPage: forwardRef((props, ref) => (
35 |
36 | )),
37 | ResetSearch: forwardRef((props, ref) => ),
38 | Search: forwardRef((props, ref) => ),
39 | SortArrow: forwardRef((props, ref) => ),
40 | ThirdStateCheck: forwardRef((props, ref) => ),
41 | ViewColumn: forwardRef((props, ref) => ),
42 | };
43 |
44 | export default icons;
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ji-grade-analysis",
3 | "version": "0.2.4",
4 | "private": true,
5 | "description": "Analysis of anonymous grades retrieved by all JI students.",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "start": "webpack serve --mode development --hot --progress --color --port 3000",
10 | "build": "webpack --mode production --progress --color",
11 | "prettier": "prettier --write \"src/**/*.{ts,tsx,css}\""
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "@babel/core": "^7.2.2",
16 | "@types/classnames": "^2.2.7",
17 | "@types/node": "^16.3.3",
18 | "@types/react": "^17.0.14",
19 | "@types/react-dom": "^17.0.9",
20 | "@types/react-router": "^5.1.16",
21 | "@types/webpack": "^5.28.0",
22 | "babel-loader": "^8.0.5",
23 | "css-loader": "^6.2.0",
24 | "file-loader": "^6.2.0",
25 | "html-loader": "^2.1.2",
26 | "html-webpack-plugin": "^5.3.2",
27 | "loader-utils": "^2.0.4",
28 | "mini-css-extract-plugin": "^2.1.0",
29 | "mobx-react-devtools": "^6.0.3",
30 | "postcss": "^8.4.31",
31 | "postcss-browser-reporter": "^0.6.0",
32 | "postcss-import": "^14.0.2",
33 | "postcss-loader": "^6.1.1",
34 | "postcss-preset-env": "^6.5.0",
35 | "postcss-reporter": "^7.0.2",
36 | "postcss-url": "^10.1.3",
37 | "prettier": "^2.3.2",
38 | "react-hot-loader": "^4.6.3",
39 | "style-loader": "^3.2.1",
40 | "ts-loader": "^9.2.3",
41 | "typescript": "^4.3.5",
42 | "url-loader": "^4.1.1",
43 | "webpack": "^5.45.1",
44 | "webpack-cleanup-plugin": "^0.5.1",
45 | "webpack-cli": "^4.7.2",
46 | "webpack-dev-server": "^3.1.14",
47 | "webpack-hot-middleware": "^2.24.3"
48 | },
49 | "dependencies": {
50 | "@devexpress/dx-react-chart": "^2.3.2",
51 | "@devexpress/dx-react-chart-material-ui": "^2.3.2",
52 | "@devexpress/dx-react-core": "^2.3.2",
53 | "@material-table/core": "^4.1.0",
54 | "@material-ui/core": "^4.12.1",
55 | "@material-ui/icons": "^4.11.2",
56 | "@types/lodash": "^4.14.145",
57 | "busuanzi-statistics": "^0.0.2",
58 | "classnames": "^2.2.6",
59 | "d3-scale-chromatic": "^3.0.0",
60 | "ji-grade-analysis-data": "link:./data",
61 | "lodash": "^4.17.15",
62 | "mobx": "^5.8.0",
63 | "mobx-react": "^5.4.3",
64 | "mobx-react-router": "^4.0.5",
65 | "react": "^17.0.2",
66 | "react-dom": "^17.0.2",
67 | "react-github-button": "tc-imba/react-github-button#typescript",
68 | "react-intl": "^5.20.4",
69 | "react-router": "^5.2.0",
70 | "react-router-dom": "^5.2.0",
71 | "react-sticky": "^6.0.3",
72 | "typeface-roboto": "1.1.13"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/app/components/CourseList/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { inject } from "mobx-react";
3 | import {
4 | // Divider,
5 | // TableHead,
6 | Paper,
7 | } from "@material-ui/core";
8 | import MaterialTable, { Column } from "@material-table/core";
9 |
10 | import icons from "app/components/icons";
11 |
12 | import { STORE_COURSES, STORE_ROUTER, STORE_GLOBAL_STATE } from "app/constants";
13 | import { CoursesStore, RouterStore, GlobalStateStore } from "app/stores";
14 | import CourseModel from "app/models/CourseModel";
15 |
16 | export interface CourseListProps {
17 | /* empty */
18 | }
19 |
20 | export interface CourseListState {
21 | // columns: Array>;
22 | data: CourseModel[];
23 | }
24 |
25 | @inject(STORE_COURSES, STORE_ROUTER, STORE_GLOBAL_STATE)
26 | export class CourseList extends React.Component<
27 | CourseListProps,
28 | CourseListState
29 | > {
30 | columns: Array>;
31 | searchText: string;
32 |
33 | constructor(props: CourseListProps, context: any) {
34 | super(props, context);
35 | const coursesStore = this.props[STORE_COURSES] as CoursesStore;
36 | const globalStateStore = this.props[STORE_GLOBAL_STATE] as GlobalStateStore;
37 | this.searchText = globalStateStore.courseListSearchText || "";
38 | this.columns = [
39 | {
40 | title: "Code",
41 | field: "courseCode",
42 | width: "10%",
43 | },
44 | {
45 | title: "Name",
46 | field: "courseNameEn",
47 | width: "35%",
48 | },
49 | {
50 | title: "Name (Chinese)",
51 | field: "courseName",
52 | width: "35%",
53 | },
54 | { title: "Terms", field: "terms", sorting: false },
55 | ];
56 | this.state = {
57 | data: coursesStore.courses,
58 | };
59 | }
60 |
61 | render() {
62 | const router = this.props[STORE_ROUTER] as RouterStore;
63 | const globalStateStore = this.props[STORE_GLOBAL_STATE] as GlobalStateStore;
64 | return (
65 | //
66 |
,
80 | }}
81 | onRowClick={(event, rowData) => {
82 | console.log(rowData.courseCode);
83 | const currentHash = router.location.hash;
84 | console.log(currentHash);
85 | const nextHash = `/courses/${rowData.courseCode}`;
86 | if (currentHash !== nextHash) {
87 | router.push(nextHash);
88 | }
89 | }}
90 | onSearchChange={(searchText) => {
91 | this.searchText = searchText;
92 | globalStateStore.setCourseListSearchText(searchText);
93 | }}
94 | />
95 | //
96 | );
97 | }
98 | }
99 |
100 | export default CourseList;
101 |
--------------------------------------------------------------------------------
/src/app/components/CurvePieChart/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { inject } from "mobx-react";
3 | import {
4 | Chart,
5 | Legend,
6 | Tooltip,
7 | PieSeries,
8 | Title,
9 | } from "@devexpress/dx-react-chart-material-ui";
10 | import {
11 | Animation,
12 | EventTracker,
13 | HoverState,
14 | } from "@devexpress/dx-react-chart";
15 | import { withStyles } from "@material-ui/styles";
16 | import _ from "lodash";
17 |
18 | import { STORE_COURSES } from "app/constants";
19 | import { ScoreModel } from "app/models/CourseModel";
20 | import { CoursesStore } from "app/stores";
21 |
22 | export interface CurvePieChartProps {
23 | lessonClassCode: string;
24 | }
25 |
26 | export interface CurvePieChartState {
27 | hover: any;
28 | tooltipTarget: any;
29 | data: Array<{
30 | grade: string;
31 | count: number;
32 | }>;
33 | totalCount: number;
34 | }
35 |
36 | const chartRootStyles = {
37 | chart: {
38 | paddingRight: "20px",
39 | },
40 | };
41 | const legendStyles = {
42 | root: {
43 | display: "flex",
44 | margin: "auto",
45 | flexDirection: "row",
46 | },
47 | };
48 | const legendLabelStyles = (theme) => ({
49 | label: {
50 | paddingTop: theme.spacing(1),
51 | },
52 | });
53 | const legendItemStyles = {
54 | item: {
55 | flexDirection: "column",
56 | },
57 | };
58 |
59 | const ChartRootBase = ({ classes, ...restProps }) => (
60 | // @ts-ignore
61 |
62 | );
63 | const LegendRootBase = ({ classes, ...restProps }) => (
64 | // @ts-ignore
65 |
66 | );
67 | const LegendLabelBase = ({ classes, ...restProps }) => (
68 | // @ts-ignore
69 |
70 | );
71 | const LegendItemBase = ({ classes, ...restProps }) => (
72 | // @ts-ignore
73 |
74 | );
75 |
76 | // @ts-ignore
77 | const ChartRoot = withStyles(chartRootStyles, { name: "ChartRoot" })(
78 | ChartRootBase
79 | );
80 | // @ts-ignore
81 | const LegendRoot = withStyles(legendStyles, { name: "LegendRoot" })(
82 | LegendRootBase
83 | );
84 | // @ts-ignore
85 | const LegendLabel = withStyles(legendLabelStyles, { name: "LegendLabel" })(
86 | LegendLabelBase
87 | );
88 | // @ts-ignore
89 | const LegendItem = withStyles(legendItemStyles, { name: "LegendItem" })(
90 | LegendItemBase
91 | );
92 |
93 | const grades = ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"];
94 |
95 | @inject(STORE_COURSES)
96 | export class CurvePieChart extends React.Component<
97 | CurvePieChartProps,
98 | CurvePieChartState
99 | > {
100 | score: ScoreModel = null;
101 |
102 | constructor(props: CurvePieChartProps, context: any) {
103 | super(props, context);
104 | this.state = {
105 | hover: null,
106 | tooltipTarget: null,
107 | data: [],
108 | totalCount: 0,
109 | };
110 | }
111 |
112 | updateChartData() {
113 | const coursesStore = this.props[STORE_COURSES] as CoursesStore;
114 | console.log(this.props.lessonClassCode);
115 | if (coursesStore.scoresMap.hasOwnProperty(this.props.lessonClassCode)) {
116 | const scoreData = coursesStore.scoresMap[this.props.lessonClassCode];
117 | const scores = scoreData.scores;
118 | const data = _.zip(grades, scores);
119 | const totalCount = _.sum(scores);
120 | const chartData = _.map(data, (value) => {
121 | return { grade: value[0], count: value[1] };
122 | });
123 | console.log(chartData);
124 | this.setState({ data: chartData, totalCount: totalCount });
125 | } else {
126 | this.setState({ data: [], totalCount: 0 });
127 | }
128 | }
129 |
130 | componentDidMount() {
131 | this.updateChartData();
132 | }
133 |
134 | componentDidUpdate(
135 | prevProps: Readonly,
136 | prevState: Readonly,
137 | snapshot?: any
138 | ) {
139 | if (this.props.lessonClassCode !== prevProps.lessonClassCode) {
140 | this.updateChartData();
141 | }
142 | }
143 |
144 | onChangeHover(hover) {
145 | this.setState({ hover });
146 | }
147 |
148 | onChangeTooltip(targetItem) {
149 | this.setState({ tooltipTarget: targetItem });
150 | }
151 |
152 | render() {
153 | const TooltipContent = (props) => {
154 | // const { targetItem, text, ...restProps } = props;
155 | const { targetItem } = props;
156 | const pointData = this.state.data[targetItem.point];
157 | const percentage =
158 | Math.round((pointData.count / this.state.totalCount) * 10000) / 100;
159 | console.log(targetItem);
160 | return (
161 |
162 | {pointData.grade}: {percentage}% ({pointData.count}/
163 | {this.state.totalCount})
164 |
165 | );
166 | };
167 |
168 | return (
169 |
174 |
175 |
176 |
177 |
184 |
185 |
189 |
194 |
195 | );
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 |
4 | // variables
5 | var isProduction =
6 | process.argv.indexOf('production') >= 0 || process.env.NODE_ENV ===
7 | 'production';
8 | var sourcePath = path.join(__dirname, './src');
9 | var outPath = path.join(__dirname, './build');
10 |
11 | // plugins
12 | var HtmlWebpackPlugin = require('html-webpack-plugin');
13 | var MiniCssExtractPlugin = require('mini-css-extract-plugin');
14 | var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
15 |
16 | var cssModuleRegex = new RegExp(/\.module\.(less|css)$/);
17 | var loaderUtils = require('loader-utils');
18 |
19 | function getLocalIdent(loaderContext, localIdentName, localName, options) {
20 |
21 | // return local name if it's a global css file
22 | if (!cssModuleRegex.test(loaderContext.resourcePath)) {
23 | return localName;
24 | }
25 |
26 | if (loaderContext.resourcePath.includes('node_modules')) {
27 | return localName;
28 | }
29 |
30 | if (!options.context) {
31 | // eslint-disable-next-line no-param-reassign
32 | options.context = loaderContext.rootContext;
33 | }
34 |
35 | const request = path.relative(options.context, loaderContext.resourcePath).
36 | replace(/\\/g, '/');
37 |
38 | // eslint-disable-next-line no-param-reassign
39 | options.content = `${options.hashPrefix + request}+${localName}`;
40 |
41 | // eslint-disable-next-line no-param-reassign
42 | localIdentName = localIdentName.replace(/\[local\]/gi, localName);
43 |
44 | const hash = loaderUtils.interpolateName(
45 | loaderContext,
46 | localIdentName,
47 | options,
48 | );
49 |
50 | return hash.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-').
51 | replace(/^((-?[0-9])|--)/, '_$1');
52 | }
53 |
54 | module.exports = {
55 | context: sourcePath,
56 | entry: {
57 | app: './main.tsx',
58 | },
59 | output: {
60 | path: outPath,
61 | filename: isProduction ? '[contenthash].js' : '[chunkhash].js',
62 | chunkFilename: isProduction ?
63 | '[name].[contenthash].js' :
64 | '[name].[chunkhash].js',
65 | },
66 | target: 'web',
67 | resolve: {
68 | extensions: ['.js', '.ts', '.tsx'],
69 | // Fix webpack's default behavior to not load packages with jsnext:main module
70 | // (jsnext:main directs not usually distributable es6 format, but es6 sources)
71 | mainFields: ['module', 'browser', 'main'],
72 | alias: {
73 | app: path.resolve(__dirname, 'src/app/'),
74 | },
75 | },
76 | module: {
77 | rules: [
78 | // .ts, .tsx
79 | {
80 | test: /\.tsx?$/,
81 | use: [
82 | !isProduction && {
83 | loader: 'babel-loader',
84 | options: {plugins: ['react-hot-loader/babel']},
85 | },
86 | {
87 | loader: 'ts-loader',
88 | options: {
89 | 'allowTsInNodeModules': true,
90 | },
91 | },
92 | // 'ts-loader'
93 | ].filter(Boolean),
94 | },
95 | // css
96 | {
97 | test: /\.(css|less)$/,
98 | use: [
99 | isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
100 | {
101 | loader: 'css-loader',
102 | options: {
103 | modules: {
104 | localIdentName: isProduction
105 | ? '[chunkhash:base64:5]'
106 | : '[local]__[chunkhash:base64:5]',
107 | getLocalIdent,
108 | },
109 | sourceMap: !isProduction,
110 | importLoaders: 1,
111 | },
112 | },
113 | {
114 | loader: 'postcss-loader',
115 | options: {
116 | postcssOptions: {
117 | ident: 'postcss',
118 | plugins: [
119 | require('postcss-import')({addDependencyTo: webpack}),
120 | require('postcss-url')(),
121 | require('postcss-preset-env')({
122 | /* use stage 2 features (defaults) */
123 | stage: 2,
124 | }),
125 | require('postcss-reporter')(),
126 | require('postcss-browser-reporter')({
127 | disabled: isProduction,
128 | }),
129 | ],
130 | },
131 | },
132 | },
133 | ],
134 | },
135 | // static assets
136 | {test: /\.html$/, use: 'html-loader'},
137 | {test: /\.(a?png|svg)$/, use: 'url-loader?limit=10000'},
138 | {
139 | test: /\.(jpe?g|gif|bmp|mp3|mp4|ogg|wav|eot|ttf|woff|woff2)$/,
140 | use: 'file-loader',
141 | },
142 | ],
143 | },
144 | optimization: {
145 | splitChunks: {
146 | name: false,
147 | cacheGroups: {
148 | commons: {
149 | chunks: 'initial',
150 | minChunks: 2,
151 | },
152 | vendors: {
153 | test: /[\\/]node_modules[\\/]/,
154 | chunks: 'all',
155 | priority: -10,
156 | filename: isProduction ?
157 | 'vendor.[contenthash].js' :
158 | 'vendor.[chunkhash].js',
159 | },
160 | },
161 | },
162 | runtimeChunk: true,
163 | },
164 | plugins: [
165 | new webpack.EnvironmentPlugin({
166 | NODE_ENV: isProduction ? 'production' : 'development',
167 | // use 'development' unless process.env.NODE_ENV is defined
168 | DEBUG: false,
169 | }),
170 | // new WebpackCleanupPlugin(),
171 | new MiniCssExtractPlugin({
172 | filename: isProduction ? '[contenthash].css' : '[chunkhash].css',
173 | // disable: !isProduction
174 | }),
175 | new HtmlWebpackPlugin({
176 | template: 'assets/index.html',
177 | favicon: 'assets/favicon.ico',
178 | }),
179 | ],
180 | devServer: {
181 | contentBase: sourcePath,
182 | hot: true,
183 | inline: true,
184 | historyApiFallback: {
185 | disableDotRule: true,
186 | },
187 | stats: 'minimal',
188 | clientLogLevel: 'warning',
189 | },
190 | // https://webpack.js.org/configuration/devtool/
191 | devtool: isProduction ? 'hidden-source-map' : 'cheap-module-source-map',
192 | // node: {
193 | // workaround for webpack-dev-server issue
194 | // https://github.com/webpack/webpack-dev-server/issues/60#issuecomment-103411179
195 | // fs: 'empty',
196 | // net: 'empty'
197 | // }
198 | };
199 |
--------------------------------------------------------------------------------
/docs/088add6757803b6550ca.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///webpack/bootstrap"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","Object","prototype","hasOwnProperty","call","installedChunks","push","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","1","exports","module","l","m","c","d","name","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","p","jsonpArray","window","oldJsonpFunction","slice"],"mappings":"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","file":"088add6757803b6550ca.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t1: 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/src/app/components/CurveChart/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { inject } from "mobx-react";
3 | import {
4 | Chart,
5 | // Legend,
6 | Tooltip,
7 | BarSeries,
8 | PieSeries,
9 | Title,
10 | ArgumentAxis,
11 | ValueAxis,
12 | } from "@devexpress/dx-react-chart-material-ui";
13 | import {
14 | Animation,
15 | EventTracker,
16 | HoverState,
17 | // Palette,
18 | } from "@devexpress/dx-react-chart";
19 | // import {schemePastel2} from 'd3-scale-chromatic';
20 |
21 | import { withStyles } from "@material-ui/styles";
22 | import _ from "lodash";
23 |
24 | import { STORE_COURSES } from "app/constants";
25 | // import {ScoreModel} from 'app/models/CourseModel';
26 | // import {CoursesStore} from 'app/stores';
27 | import { ScoreData } from "app/components/Lesson";
28 |
29 | export interface CurveChartProps {
30 | lessonClassCode: string;
31 | data: ScoreData[];
32 | chartType: string;
33 | hideUnknown: boolean;
34 | hideZero: boolean;
35 | }
36 |
37 | export interface CurveChartState {
38 | hover: any;
39 | tooltipTarget: any;
40 | totalCount: number;
41 | chartData: ScoreData[];
42 | }
43 |
44 | const chartRootStyles = {
45 | chart: {
46 | paddingRight: "20px",
47 | },
48 | };
49 |
50 | /*const legendStyles = {
51 | root: {
52 | display: 'flex',
53 | margin: 'auto',
54 | flexDirection: 'row',
55 | },
56 | };
57 | const legendLabelStyles = theme => ({
58 | label: {
59 | paddingTop: theme.spacing(1),
60 | },
61 | });
62 | const legendItemStyles = {
63 | item: {
64 | flexDirection: 'column',
65 | },
66 | };*/
67 |
68 | const colors = ["#fdca00", "#19335d", "#ffffff"];
69 |
70 | const BarSeriesPointBase = ({ index, color, ...restProps }) => {
71 | color = colors[index % 2];
72 | // @ts-ignore
73 | return ;
74 | };
75 |
76 | const PieSeriesPointBase = ({ index, color, endAngle, ...restProps }) => {
77 | if (index >= 2 && index % 2 == 0 && Math.abs(endAngle - Math.PI * 2) < 1e-5) {
78 | color = colors[2];
79 | } else {
80 | color = colors[index % 2];
81 | }
82 | // @ts-ignore
83 | return (
84 | // @ts-ignore
85 |
91 | );
92 | };
93 |
94 | const ChartRootBase = ({ classes, ...restProps }) => (
95 | // @ts-ignore
96 |
97 | );
98 |
99 | /*const LegendRootBase = ({classes, ...restProps}) => (
100 | // @ts-ignore
101 |
102 | );
103 | const LegendLabelBase = ({classes, ...restProps}) => (
104 | // @ts-ignore
105 |
106 | );
107 | const LegendItemBase = ({classes, ...restProps}) => (
108 | // @ts-ignore
109 |
110 | );*/
111 |
112 | // @ts-ignore
113 | const ChartRoot = withStyles(chartRootStyles, { name: "ChartRoot" })(
114 | ChartRootBase
115 | );
116 |
117 | /*// @ts-ignore
118 | const LegendRoot = withStyles(legendStyles, {name: 'LegendRoot'})(
119 | LegendRootBase);
120 | // @ts-ignore
121 | const LegendLabel = withStyles(legendLabelStyles, {name: 'LegendLabel'})(
122 | LegendLabelBase);
123 | // @ts-ignore
124 | const LegendItem = withStyles(legendItemStyles, {name: 'LegendItem'})(
125 | LegendItemBase);*/
126 |
127 | @inject(STORE_COURSES)
128 | export class CurveChart extends React.Component<
129 | CurveChartProps,
130 | CurveChartState
131 | > {
132 | constructor(props: CurveChartProps, context: any) {
133 | super(props, context);
134 | const chartData = CurveChart.getChartData(
135 | props.data,
136 | props.chartType,
137 | props.hideUnknown,
138 | props.hideZero
139 | );
140 | this.state = {
141 | hover: null,
142 | tooltipTarget: null,
143 | totalCount: CurveChart.getTotalCount(chartData),
144 | chartData: chartData,
145 | };
146 | }
147 |
148 | static getTotalCount(data: ScoreData[]) {
149 | return _.sumBy(data, (scoreData: ScoreData) => scoreData.count);
150 | }
151 |
152 | static getChartData(
153 | data: ScoreData[],
154 | chartType: string,
155 | hideUnknown: boolean,
156 | hideZero: boolean
157 | ) {
158 | let newData = data;
159 | if (hideZero || chartType === "pie") {
160 | newData = _.filter(data, (scoreData) => scoreData.count != 0);
161 | }
162 | if (
163 | hideUnknown &&
164 | newData.length > 0 &&
165 | _.last(newData).grade === "Unknown"
166 | ) {
167 | newData = _.slice(newData, 0, newData.length - 1);
168 | }
169 | return newData;
170 | }
171 |
172 | /* updateChartData() {
173 | this.setState({totalCount: CurveChart.getTotalCount(this.props.data)});
174 | }*/
175 |
176 | /* componentDidMount() {
177 | this.updateChartData();
178 | }*/
179 |
180 | componentDidUpdate(
181 | prevProps: Readonly,
182 | prevState: Readonly,
183 | snapshot?: any
184 | ) {
185 | if (this.props.lessonClassCode === prevProps.lessonClassCode) {
186 | return;
187 | } else if (
188 | this.props.chartType === prevProps.chartType &&
189 | this.props.hideUnknown === prevProps.hideUnknown &&
190 | this.props.hideZero === prevProps.hideZero
191 | ) {
192 | return;
193 | }
194 | const chartData = CurveChart.getChartData(
195 | this.props.data,
196 | this.props.chartType,
197 | this.props.hideUnknown,
198 | this.props.hideZero
199 | );
200 | this.setState({
201 | totalCount: CurveChart.getTotalCount(chartData),
202 | chartData: chartData,
203 | });
204 | }
205 |
206 | onChangeHover(hover) {
207 | this.setState({ hover });
208 | }
209 |
210 | onChangeTooltip(targetItem) {
211 | this.setState({ tooltipTarget: targetItem });
212 | }
213 |
214 | render() {
215 | const TooltipContent = (props) => {
216 | // const { targetItem, text, ...restProps } = props;
217 | const { targetItem } = props;
218 | const pointData = this.state.chartData[targetItem.point];
219 | const percentage =
220 | Math.round((pointData.count / this.state.totalCount) * 10000) / 100;
221 | // console.log(targetItem);
222 | return (
223 |
224 | {pointData.grade}: {percentage}% ({pointData.count}/
225 | {this.state.totalCount})
226 |
227 | );
228 | };
229 |
230 | let series = [];
231 | const tickFormat = (scale) => (tick) => {
232 | return Number.isInteger(tick) ? tick : "";
233 | };
234 |
235 | if (this.props.chartType === "bar") {
236 | series = [
237 | ,
243 | // @ts-ignore
244 | ,
245 | // @ts-ignore
246 | ,
247 | ];
248 | } else if (this.props.chartType === "pie") {
249 | series = [
250 | ,
258 | // ,
259 | // ,
260 | ];
261 | }
262 |
263 | return (
264 |
269 | {series}
270 |
271 |
272 |
273 |
277 |
282 |
283 | );
284 | }
285 | }
286 |
287 | {
288 | /**/
307 | }
308 |
--------------------------------------------------------------------------------
/docs/2.37343606062d2e5d65aa.css:
--------------------------------------------------------------------------------
1 | .github-btn {
2 | font: bold 11px/14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | height: 20px;
4 | overflow: hidden;
5 | }
6 | .gh-btn,
7 | .gh-count,
8 | .gh-ico {
9 | float: left;
10 | }
11 | .gh-btn,
12 | .gh-count {
13 | padding: 2px 5px 2px 4px;
14 | color: #333;
15 | text-decoration: none;
16 | white-space: nowrap;
17 | cursor: pointer;
18 | border-radius: 3px;
19 | }
20 | .gh-btn {
21 | background-color: #eee;
22 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fcfcfc), to(#eee));
23 | background-image: linear-gradient(to bottom, #fcfcfc 0, #eee 100%);
24 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#eeeeee', GradientType=0);
25 | background-repeat: no-repeat;
26 | border: 1px solid #d5d5d5;
27 | }
28 | .gh-btn:hover,
29 | .gh-btn:focus {
30 | text-decoration: none;
31 | background-color: #ddd;
32 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eee), to(#ddd));
33 | background-image: linear-gradient(to bottom, #eee 0, #ddd 100%);
34 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);
35 | border-color: #ccc;
36 | }
37 | .gh-btn:active {
38 | background-image: none;
39 | background-color: #dcdcdc;
40 | border-color: #b5b5b5;
41 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
42 | }
43 | .gh-ico {
44 | width: 14px;
45 | height: 14px;
46 | margin-right: 4px;
47 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQwcHgiIGhlaWdodD0iNDBweCIgdmlld0JveD0iMTIgMTIgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMTIgMTIgNDAgNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiMzMzMzMzMiIGQ9Ik0zMiAxMy40Yy0xMC41IDAtMTkgOC41LTE5IDE5YzAgOC40IDUuNSAxNS41IDEzIDE4YzEgMC4yIDEuMy0wLjQgMS4zLTAuOWMwLTAuNSAwLTEuNyAwLTMuMiBjLTUuMyAxLjEtNi40LTIuNi02LjQtMi42QzIwIDQxLjYgMTguOCA0MSAxOC44IDQxYy0xLjctMS4yIDAuMS0xLjEgMC4xLTEuMWMxLjkgMC4xIDIuOSAyIDIuOSAyYzEuNyAyLjkgNC41IDIuMSA1LjUgMS42IGMwLjItMS4yIDAuNy0yLjEgMS4yLTIuNmMtNC4yLTAuNS04LjctMi4xLTguNy05LjRjMC0yLjEgMC43LTMuNyAyLTUuMWMtMC4yLTAuNS0wLjgtMi40IDAuMi01YzAgMCAxLjYtMC41IDUuMiAyIGMxLjUtMC40IDMuMS0wLjcgNC44LTAuN2MxLjYgMCAzLjMgMC4yIDQuNyAwLjdjMy42LTIuNCA1LjItMiA1LjItMmMxIDIuNiAwLjQgNC42IDAuMiA1YzEuMiAxLjMgMiAzIDIgNS4xYzAgNy4zLTQuNSA4LjktOC43IDkuNCBjMC43IDAuNiAxLjMgMS43IDEuMyAzLjVjMCAyLjYgMCA0LjYgMCA1LjJjMCAwLjUgMC40IDEuMSAxLjMgMC45YzcuNS0yLjYgMTMtOS43IDEzLTE4LjFDNTEgMjEuOSA0Mi41IDEzLjQgMzIgMTMuNHoiLz48L3N2Zz4=');
48 | background-size: 100% 100%;
49 | background-repeat: no-repeat;
50 | }
51 | .gh-count {
52 | position: relative;
53 | display: none; /* hidden to start */
54 | margin-left: 4px;
55 | background-color: #fafafa;
56 | border: 1px solid #d4d4d4;
57 | }
58 | .gh-count:hover,
59 | .gh-count:focus {
60 | color: #4183C4;
61 | }
62 | .gh-count:before,
63 | .gh-count:after {
64 | content: '';
65 | position: absolute;
66 | display: inline-block;
67 | width: 0;
68 | height: 0;
69 | border-color: transparent;
70 | border-style: solid;
71 | }
72 | .gh-count:before {
73 | top: 50%;
74 | left: -3px;
75 | margin-top: -4px;
76 | border-width: 4px 4px 4px 0;
77 | border-right-color: #fafafa;
78 | }
79 | .gh-count:after {
80 | top: 50%;
81 | left: -4px;
82 | z-index: -1;
83 | margin-top: -5px;
84 | border-width: 5px 5px 5px 0;
85 | border-right-color: #d4d4d4;
86 | }
87 | .github-btn-large {
88 | height: 30px;
89 | }
90 | .github-btn-large .gh-btn,
91 | .github-btn-large .gh-count {
92 | padding: 3px 10px 3px 8px;
93 | font-size: 16px;
94 | line-height: 22px;
95 | border-radius: 4px;
96 | }
97 | .github-btn-large .gh-ico {
98 | width: 20px;
99 | height: 20px;
100 | }
101 | .github-btn-large .gh-count {
102 | margin-left: 6px;
103 | }
104 | .github-btn-large .gh-count:before {
105 | left: -5px;
106 | margin-top: -6px;
107 | border-width: 6px 6px 6px 0;
108 | }
109 | .github-btn-large .gh-count:after {
110 | left: -6px;
111 | margin-top: -7px;
112 | border-width: 7px 7px 7px 0;
113 | }
114 | /* roboto-100normal - latin */
115 | @font-face {
116 | font-family: 'Roboto';
117 | font-style: normal;
118 | font-display: swap;
119 | font-weight: 100;
120 | src:
121 | local('Roboto Thin '),
122 | local('Roboto-Thin'),
123 | url(7370c3679472e9560965ff48a4399d0b.woff2) format('woff2'),
124 | url(5cb7edfceb233100075dc9a1e12e8da3.woff) format('woff'); /* Modern Browsers */
125 | }
126 | /* roboto-100italic - latin */
127 | @font-face {
128 | font-family: 'Roboto';
129 | font-style: italic;
130 | font-display: swap;
131 | font-weight: 100;
132 | src:
133 | local('Roboto Thin italic'),
134 | local('Roboto-Thinitalic'),
135 | url(f8b1df51ba843179fa1cc9b53d58127a.woff2) format('woff2'),
136 | url(f9e8e590b4e0f1ff83469bb2a55b8488.woff) format('woff'); /* Modern Browsers */
137 | }
138 | /* roboto-300normal - latin */
139 | @font-face {
140 | font-family: 'Roboto';
141 | font-style: normal;
142 | font-display: swap;
143 | font-weight: 300;
144 | src:
145 | local('Roboto Light '),
146 | local('Roboto-Light'),
147 | url(ef7c6637c68f269a882e73bcb57a7f6a.woff2) format('woff2'),
148 | url(b00849e00f4c2331cddd8ffb44a6720b.woff) format('woff'); /* Modern Browsers */
149 | }
150 | /* roboto-300italic - latin */
151 | @font-face {
152 | font-family: 'Roboto';
153 | font-style: italic;
154 | font-display: swap;
155 | font-weight: 300;
156 | src:
157 | local('Roboto Light italic'),
158 | local('Roboto-Lightitalic'),
159 | url(14286f3ba79c6627433572dfa925202e.woff2) format('woff2'),
160 | url(4df32891a5f2f98a363314f595482e08.woff) format('woff'); /* Modern Browsers */
161 | }
162 | /* roboto-400normal - latin */
163 | @font-face {
164 | font-family: 'Roboto';
165 | font-style: normal;
166 | font-display: swap;
167 | font-weight: 400;
168 | src:
169 | local('Roboto Regular '),
170 | local('Roboto-Regular'),
171 | url(479970ffb74f2117317f9d24d9e317fe.woff2) format('woff2'),
172 | url(60fa3c0614b8fb2f394fa29944c21540.woff) format('woff'); /* Modern Browsers */
173 | }
174 | /* roboto-400italic - latin */
175 | @font-face {
176 | font-family: 'Roboto';
177 | font-style: italic;
178 | font-display: swap;
179 | font-weight: 400;
180 | src:
181 | local('Roboto Regular italic'),
182 | local('Roboto-Regularitalic'),
183 | url(51521a2a8da71e50d871ac6fd2187e87.woff2) format('woff2'),
184 | url(fe65b8335ee19dd944289f9ed3178c78.woff) format('woff'); /* Modern Browsers */
185 | }
186 | /* roboto-500normal - latin */
187 | @font-face {
188 | font-family: 'Roboto';
189 | font-style: normal;
190 | font-display: swap;
191 | font-weight: 500;
192 | src:
193 | local('Roboto Medium '),
194 | local('Roboto-Medium'),
195 | url(020c97dc8e0463259c2f9df929bb0c69.woff2) format('woff2'),
196 | url(87284894879f5b1c229cb49c8ff6decc.woff) format('woff'); /* Modern Browsers */
197 | }
198 | /* roboto-500italic - latin */
199 | @font-face {
200 | font-family: 'Roboto';
201 | font-style: italic;
202 | font-display: swap;
203 | font-weight: 500;
204 | src:
205 | local('Roboto Medium italic'),
206 | local('Roboto-Mediumitalic'),
207 | url(db4a2a231f52e497c0191e8966b0ee58.woff2) format('woff2'),
208 | url(288ad9c6e8b43cf02443a1f499bdf67e.woff) format('woff'); /* Modern Browsers */
209 | }
210 | /* roboto-700normal - latin */
211 | @font-face {
212 | font-family: 'Roboto';
213 | font-style: normal;
214 | font-display: swap;
215 | font-weight: 700;
216 | src:
217 | local('Roboto Bold '),
218 | local('Roboto-Bold'),
219 | url(2735a3a69b509faf3577afd25bdf552e.woff2) format('woff2'),
220 | url(adcde98f1d584de52060ad7b16373da3.woff) format('woff'); /* Modern Browsers */
221 | }
222 | /* roboto-700italic - latin */
223 | @font-face {
224 | font-family: 'Roboto';
225 | font-style: italic;
226 | font-display: swap;
227 | font-weight: 700;
228 | src:
229 | local('Roboto Bold italic'),
230 | local('Roboto-Bolditalic'),
231 | url(da0e717829e033a69dec97f1e155ae42.woff2) format('woff2'),
232 | url(81f57861ed4ac74741f5671e1dff2fd9.woff) format('woff'); /* Modern Browsers */
233 | }
234 | /* roboto-900normal - latin */
235 | @font-face {
236 | font-family: 'Roboto';
237 | font-style: normal;
238 | font-display: swap;
239 | font-weight: 900;
240 | src:
241 | local('Roboto Black '),
242 | local('Roboto-Black'),
243 | url(9b3766ef4a402ad3fdeef7501a456512.woff2) format('woff2'),
244 | url(bb1e4dc6333675d11ada2e857e7f95d7.woff) format('woff'); /* Modern Browsers */
245 | }
246 | /* roboto-900italic - latin */
247 | @font-face {
248 | font-family: 'Roboto';
249 | font-style: italic;
250 | font-display: swap;
251 | font-weight: 900;
252 | src:
253 | local('Roboto Black italic'),
254 | local('Roboto-Blackitalic'),
255 | url(ebf6d1640ccddb99fb49f73c052c55a8.woff2) format('woff2'),
256 | url(28f9151055c950874d2c6803a39b425b.woff) format('woff'); /* Modern Browsers */
257 | }
258 |
259 |
260 |
--------------------------------------------------------------------------------
/docs/2.37343606062d2e5d65aa.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///../node_modules/react-github-button/assets/style.less","webpack:///../node_modules/typeface-roboto/index.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;AChHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D","file":"2.37343606062d2e5d65aa.css","sourcesContent":[".github-btn {\n font: bold 11px/14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n height: 20px;\n overflow: hidden;\n}\n.gh-btn,\n.gh-count,\n.gh-ico {\n float: left;\n}\n.gh-btn,\n.gh-count {\n padding: 2px 5px 2px 4px;\n color: #333;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n border-radius: 3px;\n}\n.gh-btn {\n background-color: #eee;\n background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fcfcfc), to(#eee));\n background-image: linear-gradient(to bottom, #fcfcfc 0, #eee 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#eeeeee', GradientType=0);\n background-repeat: no-repeat;\n border: 1px solid #d5d5d5;\n}\n.gh-btn:hover,\n.gh-btn:focus {\n text-decoration: none;\n background-color: #ddd;\n background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eee), to(#ddd));\n background-image: linear-gradient(to bottom, #eee 0, #ddd 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);\n border-color: #ccc;\n}\n.gh-btn:active {\n background-image: none;\n background-color: #dcdcdc;\n border-color: #b5b5b5;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);\n}\n.gh-ico {\n width: 14px;\n height: 14px;\n margin-right: 4px;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQwcHgiIGhlaWdodD0iNDBweCIgdmlld0JveD0iMTIgMTIgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMTIgMTIgNDAgNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiMzMzMzMzMiIGQ9Ik0zMiAxMy40Yy0xMC41IDAtMTkgOC41LTE5IDE5YzAgOC40IDUuNSAxNS41IDEzIDE4YzEgMC4yIDEuMy0wLjQgMS4zLTAuOWMwLTAuNSAwLTEuNyAwLTMuMiBjLTUuMyAxLjEtNi40LTIuNi02LjQtMi42QzIwIDQxLjYgMTguOCA0MSAxOC44IDQxYy0xLjctMS4yIDAuMS0xLjEgMC4xLTEuMWMxLjkgMC4xIDIuOSAyIDIuOSAyYzEuNyAyLjkgNC41IDIuMSA1LjUgMS42IGMwLjItMS4yIDAuNy0yLjEgMS4yLTIuNmMtNC4yLTAuNS04LjctMi4xLTguNy05LjRjMC0yLjEgMC43LTMuNyAyLTUuMWMtMC4yLTAuNS0wLjgtMi40IDAuMi01YzAgMCAxLjYtMC41IDUuMiAyIGMxLjUtMC40IDMuMS0wLjcgNC44LTAuN2MxLjYgMCAzLjMgMC4yIDQuNyAwLjdjMy42LTIuNCA1LjItMiA1LjItMmMxIDIuNiAwLjQgNC42IDAuMiA1YzEuMiAxLjMgMiAzIDIgNS4xYzAgNy4zLTQuNSA4LjktOC43IDkuNCBjMC43IDAuNiAxLjMgMS43IDEuMyAzLjVjMCAyLjYgMCA0LjYgMCA1LjJjMCAwLjUgMC40IDEuMSAxLjMgMC45YzcuNS0yLjYgMTMtOS43IDEzLTE4LjFDNTEgMjEuOSA0Mi41IDEzLjQgMzIgMTMuNHoiLz48L3N2Zz4=');\n background-size: 100% 100%;\n background-repeat: no-repeat;\n}\n.gh-count {\n position: relative;\n display: none; /* hidden to start */\n margin-left: 4px;\n background-color: #fafafa;\n border: 1px solid #d4d4d4;\n}\n.gh-count:hover,\n.gh-count:focus {\n color: #4183C4;\n}\n.gh-count:before,\n.gh-count:after {\n content: '';\n position: absolute;\n display: inline-block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.gh-count:before {\n top: 50%;\n left: -3px;\n margin-top: -4px;\n border-width: 4px 4px 4px 0;\n border-right-color: #fafafa;\n}\n.gh-count:after {\n top: 50%;\n left: -4px;\n z-index: -1;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #d4d4d4;\n}\n.github-btn-large {\n height: 30px;\n}\n.github-btn-large .gh-btn,\n.github-btn-large .gh-count {\n padding: 3px 10px 3px 8px;\n font-size: 16px;\n line-height: 22px;\n border-radius: 4px;\n}\n.github-btn-large .gh-ico {\n width: 20px;\n height: 20px;\n}\n.github-btn-large .gh-count {\n margin-left: 6px;\n}\n.github-btn-large .gh-count:before {\n left: -5px;\n margin-top: -6px;\n border-width: 6px 6px 6px 0;\n}\n.github-btn-large .gh-count:after {\n left: -6px;\n margin-top: -7px;\n border-width: 7px 7px 7px 0;\n}","/* roboto-100normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 100;\n src:\n local('Roboto Thin '),\n local('Roboto-Thin'),\n url(7370c3679472e9560965ff48a4399d0b.woff2) format('woff2'), \n url(5cb7edfceb233100075dc9a1e12e8da3.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-100italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 100;\n src:\n local('Roboto Thin italic'),\n local('Roboto-Thinitalic'),\n url(f8b1df51ba843179fa1cc9b53d58127a.woff2) format('woff2'), \n url(f9e8e590b4e0f1ff83469bb2a55b8488.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-300normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 300;\n src:\n local('Roboto Light '),\n local('Roboto-Light'),\n url(ef7c6637c68f269a882e73bcb57a7f6a.woff2) format('woff2'), \n url(b00849e00f4c2331cddd8ffb44a6720b.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-300italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 300;\n src:\n local('Roboto Light italic'),\n local('Roboto-Lightitalic'),\n url(14286f3ba79c6627433572dfa925202e.woff2) format('woff2'), \n url(4df32891a5f2f98a363314f595482e08.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-400normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 400;\n src:\n local('Roboto Regular '),\n local('Roboto-Regular'),\n url(479970ffb74f2117317f9d24d9e317fe.woff2) format('woff2'), \n url(60fa3c0614b8fb2f394fa29944c21540.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-400italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 400;\n src:\n local('Roboto Regular italic'),\n local('Roboto-Regularitalic'),\n url(51521a2a8da71e50d871ac6fd2187e87.woff2) format('woff2'), \n url(fe65b8335ee19dd944289f9ed3178c78.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-500normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 500;\n src:\n local('Roboto Medium '),\n local('Roboto-Medium'),\n url(020c97dc8e0463259c2f9df929bb0c69.woff2) format('woff2'), \n url(87284894879f5b1c229cb49c8ff6decc.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-500italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 500;\n src:\n local('Roboto Medium italic'),\n local('Roboto-Mediumitalic'),\n url(db4a2a231f52e497c0191e8966b0ee58.woff2) format('woff2'), \n url(288ad9c6e8b43cf02443a1f499bdf67e.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-700normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 700;\n src:\n local('Roboto Bold '),\n local('Roboto-Bold'),\n url(2735a3a69b509faf3577afd25bdf552e.woff2) format('woff2'), \n url(adcde98f1d584de52060ad7b16373da3.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-700italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 700;\n src:\n local('Roboto Bold italic'),\n local('Roboto-Bolditalic'),\n url(da0e717829e033a69dec97f1e155ae42.woff2) format('woff2'), \n url(81f57861ed4ac74741f5671e1dff2fd9.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-900normal - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-display: swap;\n font-weight: 900;\n src:\n local('Roboto Black '),\n local('Roboto-Black'),\n url(9b3766ef4a402ad3fdeef7501a456512.woff2) format('woff2'), \n url(bb1e4dc6333675d11ada2e857e7f95d7.woff) format('woff'); /* Modern Browsers */\n}\n/* roboto-900italic - latin */\n@font-face {\n font-family: 'Roboto';\n font-style: italic;\n font-display: swap;\n font-weight: 900;\n src:\n local('Roboto Black italic'),\n local('Roboto-Blackitalic'),\n url(ebf6d1640ccddb99fb49f73c052c55a8.woff2) format('woff2'), \n url(28f9151055c950874d2c6803a39b425b.woff) format('woff'); /* Modern Browsers */\n}\n\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/src/app/components/Course/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { inject } from "mobx-react";
3 | import {
4 | FormControlLabel,
5 | // Tab,
6 | // Tabs,
7 | // Typography,
8 | RadioGroup,
9 | Radio,
10 | Switch,
11 | Grid,
12 | Paper,
13 | } from "@material-ui/core";
14 | import MaterialTable, { Column } from "@material-table/core";
15 | import icons from "app/components/icons";
16 | import _, { Dictionary } from "lodash";
17 |
18 | import { STORE_COURSES, STORE_GLOBAL_STATE } from "app/constants";
19 | import CourseModel, { LessonModel } from "app/models/CourseModel";
20 | import { CoursesStore, GlobalStateStore } from "app/stores";
21 | import { Lesson, ScoreData } from "app/components/Lesson";
22 | import { StickyContainer, Sticky } from "react-sticky";
23 | import zIndex from "@material-ui/core/styles/zIndex";
24 |
25 | // import {CurvePieChart} from 'app/components/CurvePieChart';
26 |
27 | export interface CourseProps {
28 | /* empty */
29 | courseCode: string;
30 | }
31 |
32 | export interface CourseState {
33 | course: CourseModel;
34 | lessons: LessonModel[];
35 | chartType: string;
36 | hideUnknown: boolean;
37 | hideZero: boolean;
38 | // lessonClassCode: string | boolean;
39 | // lesson: LessonModel | null;
40 | }
41 |
42 | const grades = ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"];
43 |
44 | @inject(STORE_COURSES, STORE_GLOBAL_STATE)
45 | export class Course extends React.Component {
46 | columns: Array>;
47 | scoreDataMap: Dictionary = {};
48 | searchText: string;
49 |
50 | constructor(props: CourseProps, context: any) {
51 | super(props, context);
52 | const globalStateStore = this.props[STORE_GLOBAL_STATE] as GlobalStateStore;
53 | this.searchText = globalStateStore.courseSearchText[props.courseCode] || "";
54 | this.columns = [
55 | {
56 | title: "Code",
57 | field: "lessonClassCode",
58 | width: "20%",
59 | },
60 | {
61 | title: "Term",
62 | field: "termName",
63 | width: "10%",
64 | },
65 | {
66 | title: "Name",
67 | field: "lessonClassName",
68 | width: "25%",
69 | },
70 | {
71 | title: "Found",
72 | field: "scoreNum",
73 | width: "10%",
74 | },
75 | {
76 | title: "Elected",
77 | field: "studentNumStr",
78 | width: "10%",
79 | },
80 | {
81 | title: "Lecturers",
82 | field: "lecturersStr",
83 | sorting: false,
84 | },
85 | ];
86 | this.state = {
87 | course: null,
88 | lessons: [],
89 | chartType: "bar",
90 | hideUnknown: false,
91 | hideZero: false,
92 | };
93 | }
94 |
95 | updateCourse() {
96 | console.log(this.props.courseCode);
97 | const coursesStore = this.props[STORE_COURSES] as CoursesStore;
98 | const course = coursesStore.coursesMap[this.props.courseCode];
99 | let lessons: LessonModel[] = [];
100 | course.lessons.forEach((a) => {
101 | if (coursesStore.lessonsMap.hasOwnProperty(a[0])) {
102 | let lesson = coursesStore.lessonsMap[a[0]];
103 | lesson.lecturersStr = lesson.lecturers.join(", ");
104 | lesson.studentNumStr =
105 | lesson.studentNum >= 0 ? lesson.studentNum.toString() : "-";
106 | lessons.push(lesson);
107 | }
108 | });
109 | this.setState({
110 | course: course,
111 | lessons: lessons,
112 | });
113 | }
114 |
115 | ensureScoreDataMap(lessonClassCode: string) {
116 | if (this.scoreDataMap.hasOwnProperty(lessonClassCode)) {
117 | return this.scoreDataMap[lessonClassCode];
118 | }
119 | const coursesStore = this.props[STORE_COURSES] as CoursesStore;
120 | let studentNum = 0;
121 | let scoreNum = 0;
122 | let scoreData: ScoreData[];
123 | if (coursesStore.lessonsMap.hasOwnProperty(lessonClassCode)) {
124 | const lesson = coursesStore.lessonsMap[lessonClassCode];
125 | studentNum = lesson.studentNum;
126 | }
127 | if (coursesStore.scoresMap.hasOwnProperty(lessonClassCode)) {
128 | const scoreRawData = coursesStore.scoresMap[lessonClassCode];
129 | const scores = scoreRawData.scores;
130 | const temp = _.zip(grades, scores);
131 | scoreData = _.map(temp, (value) => {
132 | scoreNum += value[1];
133 | return { grade: value[0], count: value[1] };
134 | });
135 | } else {
136 | scoreData = _.map(grades, (value) => {
137 | return { grade: value, count: 0 };
138 | });
139 | }
140 | if (studentNum > scoreNum) {
141 | scoreData.push({ grade: "Unknown", count: studentNum - scoreNum });
142 | } else {
143 | scoreData.push({ grade: "Unknown", count: 0 });
144 | }
145 | this.scoreDataMap[lessonClassCode] = scoreData;
146 | return scoreData;
147 | }
148 |
149 | componentDidMount() {
150 | this.updateCourse();
151 | }
152 |
153 | componentDidUpdate(
154 | prevProps: Readonly,
155 | prevState: Readonly,
156 | snapshot?: any
157 | ) {
158 | if (this.props.courseCode !== prevProps.courseCode) {
159 | this.updateCourse();
160 | }
161 | }
162 |
163 | onChangeChartType(event: React.ChangeEvent) {
164 | this.setState({
165 | chartType: (event.target as HTMLInputElement).value,
166 | });
167 | }
168 |
169 | onChangeHideUnknown(event: React.ChangeEvent) {
170 | this.setState({
171 | hideUnknown: event.target.checked,
172 | });
173 | }
174 |
175 | onChangeHideZero(event: React.ChangeEvent) {
176 | this.setState({
177 | hideZero: event.target.checked,
178 | });
179 | }
180 |
181 | render() {
182 | let title = this.props.courseCode;
183 | if (this.state.course) {
184 | title +=
185 | " - " +
186 | this.state.course.courseNameEn +
187 | " - " +
188 | this.state.course.courseName;
189 | }
190 | const globalStateStore = this.props[STORE_GLOBAL_STATE] as GlobalStateStore;
191 | return (
192 |
193 |
194 | {({ style }) => (
195 | // TODO: better way to make it opaque
196 |
197 |
198 |
203 | }
206 | label="Bar"
207 | labelPlacement="bottom"
208 | />
209 | }
212 | label="Pie"
213 | labelPlacement="bottom"
214 | />
215 |
216 |
223 | }
224 | label="Hide Unknown"
225 | />
226 |
233 | }
234 | label="Hide Zero"
235 | />
236 |
237 |
238 | )}
239 |
240 | ,
254 | }}
255 | detailPanel={({ rowData }) => {
256 | const lessonClassCode = rowData.lessonClassCode;
257 | const scoreData = this.ensureScoreDataMap(lessonClassCode);
258 | // console.log(lessonClassCode);
259 | // console.log(scoreData);
260 | return (
261 |
268 | );
269 | }}
270 | onRowClick={(event, rowData, togglePanel) => togglePanel()}
271 | onSearchChange={(searchText) => {
272 | this.searchText = searchText;
273 | globalStateStore.setCourseSearchText(
274 | this.props.courseCode,
275 | searchText
276 | );
277 | }}
278 | />
279 |
280 | );
281 | }
282 | }
283 |
284 | // export default Course;
285 |
286 | {
287 | /*
293 | {
294 | this.state.course.lessons.map(lesson =>
295 | )
296 | }
297 |
298 |
302 | {this.state.lesson.lecturers}
303 | {typeof this.state.lessonClassCode === 'string' ?
304 | :
305 | null
306 | }
307 | */
308 | }
309 |
--------------------------------------------------------------------------------
/docs/app.7eb610b1c7157386632c.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///./app/constants/todos.ts","webpack:///./app/models/TodoModel.ts","webpack:///./app/models/CourseModel.ts","webpack:///./app/stores/TodoStore.ts","webpack:///./app/stores/RouterStore.ts","webpack:///./app/stores/CoursesStore.ts","webpack:///./app/stores/createStore.ts","webpack:///./app/components/Header/index.tsx","webpack:///./app/components/Footer/index.tsx","webpack:///./app/containers/Root/index.tsx","webpack:///./app/containers/Main/index.tsx","webpack:///./app/constants/stores.ts","webpack:///./app/components/icons.tsx","webpack:///./app/components/CourseList/index.tsx","webpack:///./app/containers/CourseListPage/index.tsx","webpack:///./app/components/CurveChart/index.tsx","webpack:///./app/components/Lesson/index.tsx","webpack:///./app/components/Course/index.tsx","webpack:///./app/containers/CoursePage/index.tsx","webpack:///./app/index.tsx","webpack:///./main.tsx"],"names":["TodoFilter","text","completed","this","id","TodoModel","generateId","nextId","fixtures","addTodo","item","todos","push","editTodo","data","map","todo","deleteTodo","filter","completeAll","clearCompleted","Array","history","courses","coursesMap","keyBy","lessonsMap","scoresMap","ALL","ACTIVE","COMPLETED","todoStore","routerStore","coursesStore","render","Grid","container","justify","Typography","component","variant","color","gutterBottom","align","namespace","props","repo","spacing","type","size","version","Link","href","target","renderDevTool","classes","className","Container","maxWidth","CssBaseline","Paper","mainFeaturedPostContent","children","RootWithStyle","withStyles","theme","createStyles","mainFeaturedPost","backgroundColor","palette","grey","common","white","marginBottom","padding","breakpoints","down","paddingRight","paddingLeft","context","state","md","paragraph","stats","scores","lessons","Button","to","Main","Add","ref","Check","Clear","Delete","DetailPanel","Edit","Export","Filter","FirstPage","LastPage","NextPage","PreviousPage","ResetSearch","Search","SortArrow","ThirdStateCheck","ViewColumn","columns","title","field","cellStyle","width","sorting","router","icons","options","pageSize","pageSizeOptions","style","components","elevation","onRowClick","event","rowData","console","log","courseCode","currentHash","location","hash","nextHash","CourseList","CourseListPage","colors","BarSeriesPointBase","index","Point","restProps","PieSeriesPointBase","endAngle","Math","abs","PI","ChartRoot","chart","name","Root","chartData","getChartData","chartType","hideUnknown","hideZero","hover","tooltipTarget","totalCount","getTotalCount","CurveChart","sumBy","scoreData","count","newData","length","last","grade","slice","componentDidUpdate","prevProps","prevState","snapshot","lessonClassCode","setState","onChangeHover","onChangeTooltip","targetItem","series","key","valueField","argumentField","pointComponent","innerRadius","outerRadius","rootComponent","onHoverChange","bind","onTargetItemChange","contentComponent","pointData","point","percentage","round","grades","scoreDataMap","course","updateCourse","forEach","a","hasOwnProperty","lesson","lecturersStr","lecturers","join","studentNumStr","studentNum","toString","ensureScoreDataMap","scoreNum","temp","zip","value","componentDidMount","onChangeChartType","onChangeHideUnknown","checked","onChangeHideZero","courseNameEn","courseName","RadioGroup","onChange","row","FormControlLabel","control","Radio","label","labelPlacement","detailPanel","togglePanel","Course","match","courseStore","params","toUpperCase","available","CoursePage","App","Router","Switch","Route","exact","path","rootStore","document","getElementById"],"mappings":"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","file":"app.7eb610b1c7157386632c.js","sourcesContent":["export enum TodoFilter {\n ALL = 0,\n ACTIVE,\n COMPLETED\n}\n\nexport const TODO_FILTER_TYPES = [\n TodoFilter.ALL,\n TodoFilter.ACTIVE,\n TodoFilter.COMPLETED\n];\n\nexport const TODO_FILTER_TITLES = {\n [TodoFilter.ALL]: 'All',\n [TodoFilter.ACTIVE]: 'Active',\n [TodoFilter.COMPLETED]: 'Completed'\n};\n\nexport const TODO_FILTER_LOCATION_HASH = {\n [TodoFilter.ALL]: '#',\n [TodoFilter.ACTIVE]: '#active',\n [TodoFilter.COMPLETED]: '#completed'\n};\n","import { observable } from 'mobx';\n\nexport class TodoModel {\n readonly id: number;\n @observable public text: string;\n @observable public completed: boolean;\n\n constructor(text: string, completed: boolean = false) {\n this.id = TodoModel.generateId();\n this.text = text;\n this.completed = completed;\n }\n\n static nextId = 1;\n static generateId() {\n return this.nextId++;\n }\n}\n\nexport default TodoModel;\n","import {observable} from 'mobx';\n\nexport class ScoreModel {\n readonly courseCode: string;\n readonly lessonClassCode: string;\n readonly scores: number[];\n}\n\nexport class LessonModel {\n readonly lessonClassCode: string;\n readonly courseCode: string;\n readonly lessonClassName: string;\n readonly termName: string;\n readonly studentNum: number;\n readonly scoreNum: number;\n readonly lecturers: string[];\n @observable public lecturersStr?: string;\n @observable public studentNumStr?: string;\n}\n\nexport class CourseModel {\n readonly courseCode: string;\n readonly courseName: string;\n readonly courseNameEn: string;\n readonly lessons: Array;\n readonly terms: string;\n}\n\nexport default CourseModel;\n\n","import { observable, computed, action } from 'mobx';\nimport { TodoModel } from 'app/models';\n\nexport class TodoStore {\n constructor(fixtures: TodoModel[]) {\n this.todos = fixtures;\n }\n\n @observable public todos: Array;\n\n @computed\n get activeTodos() {\n return this.todos.filter((todo) => !todo.completed);\n }\n\n @computed\n get completedTodos() {\n return this.todos.filter((todo) => todo.completed);\n }\n\n @action\n addTodo = (item: Partial): void => {\n this.todos.push(new TodoModel(item.text, item.completed));\n };\n\n @action\n editTodo = (id: number, data: Partial): void => {\n this.todos = this.todos.map((todo) => {\n if (todo.id === id) {\n if (typeof data.completed == 'boolean') {\n todo.completed = data.completed;\n }\n if (typeof data.text == 'string') {\n todo.text = data.text;\n }\n }\n return todo;\n });\n };\n\n @action\n deleteTodo = (id: number): void => {\n this.todos = this.todos.filter((todo) => todo.id !== id);\n };\n\n @action\n completeAll = (): void => {\n this.todos = this.todos.map((todo) => ({ ...todo, completed: true }));\n };\n\n @action\n clearCompleted = (): void => {\n this.todos = this.todos.filter((todo) => !todo.completed);\n };\n}\n\nexport default TodoStore;\n","import { History } from 'history';\nimport {\n RouterStore as BaseRouterStore,\n syncHistoryWithStore\n} from 'mobx-react-router';\n\nexport class RouterStore extends BaseRouterStore {\n constructor(history?: History) {\n super();\n if (history) {\n this.history = syncHistoryWithStore(history, this);\n }\n }\n}\n\nexport default RouterStore;\n","import {observable} from 'mobx';\nimport _ from 'lodash';\n\nimport {CourseModel, LessonModel, ScoreModel} from 'app/models';\n\nimport courses from 'ji-grade-analysis-data/courses.json';\nimport lessons from 'ji-grade-analysis-data/lessons.json';\nimport scores from 'ji-grade-analysis-data/scores.json';\nimport {Dictionary} from 'lodash';\n\nexport class CoursesStore {\n\n @observable public courses: Array;\n @observable public coursesMap: Dictionary;\n @observable public lessonsMap: Dictionary;\n @observable public scoresMap: Dictionary;\n\n constructor() {\n this.courses = courses;\n this.coursesMap = _.keyBy(courses, 'courseCode');\n this.lessonsMap = _.keyBy(lessons, 'lessonClassCode');\n this.scoresMap = _.keyBy(scores, 'lessonClassCode');\n /*scores.forEach(score => {\n const courseCode = score.courseCode;\n if (this.coursesMap.hasOwnProperty(courseCode)) {\n if (!this.coursesMap[courseCode].hasOwnProperty('sections')) {\n this.coursesMap[courseCode].sections = [];\n }\n this.coursesMap[courseCode].sections.push(score);\n console.log(score, this.coursesMap[courseCode].sections)\n }\n });*/\n }\n\n}\n","import {History} from 'history';\nimport {TodoModel} from 'app/models';\nimport {TodoStore} from './TodoStore';\nimport {RouterStore} from './RouterStore';\nimport {CoursesStore} from 'app/stores/CoursesStore';\nimport {STORE_TODO, STORE_ROUTER, STORE_COURSES} from 'app/constants';\n\nexport function createStores(history: History, defaultTodos?: TodoModel[]) {\n const todoStore = new TodoStore(defaultTodos);\n const routerStore = new RouterStore(history);\n const coursesStore = new CoursesStore();\n return {\n [STORE_TODO]: todoStore,\n [STORE_ROUTER]: routerStore,\n [STORE_COURSES]: coursesStore,\n };\n}\n","import * as React from 'react';\nimport {\n Grid,\n Typography,\n} from '@material-ui/core';\nimport {GitHubButton, GitHubButtonProvider} from 'react-github-button';\nimport 'react-github-button/assets/style.less';\n\nexport interface HeaderProps {\n namespace: string;\n repo: string;\n /* empty */\n}\n\nexport interface HeaderState {\n /* empty */\n}\n\nexport class Header extends React.Component {\n render() {\n return (\n \n \n \n UM-SJTU JI Grade Analysis\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n );\n }\n}\n\nexport default Header;\n","import * as React from 'react';\nimport {Grid, Link} from '@material-ui/core';\nimport packageData from 'app/../../package.json';\n\nexport interface FooterProps {\n}\n\nexport interface FooterState {\n}\n\nexport class Footer extends React.Component {\n render() {\n return (\n \n );\n }\n}\n\nexport default Footer;\n","import * as React from 'react';\nimport {\n Container,\n CssBaseline,\n Grid,\n Paper,\n} from '@material-ui/core';\nimport Header from 'app/components/Header';\nimport Footer from 'app/components/Footer';\n\nimport {\n withStyles,\n createStyles,\n WithStyles,\n Theme,\n} from '@material-ui/core/styles';\nimport {RouteComponentProps} from 'react-router';\n\nconst styles = (theme: Theme) => createStyles({\n mainFeaturedPost: {\n backgroundColor: theme.palette.grey[800],\n color: theme.palette.common.white,\n marginBottom: theme.spacing(4),\n },\n mainFeaturedPostContent: {\n padding: `${theme.spacing(6)}px`,\n [theme.breakpoints.down('sm')]: {\n paddingRight: 0,\n paddingLeft: 0,\n },\n },\n});\n\nexport interface RootProps extends RouteComponentProps,\n WithStyles {\n}\n\nexport interface RootState {\n}\n\nclass Root extends React.Component {\n renderDevTool() {\n if (process.env.NODE_ENV !== 'production') {\n const DevTools = require('mobx-react-devtools').default;\n return ;\n }\n }\n\n render() {\n const {classes} = this.props;\n return (\n \n
\n \n
\n \n \n \n
\n \n {this.props.children}\n \n \n
\n \n \n {this.renderDevTool()}\n
\n );\n }\n}\n\nconst RootWithStyle = withStyles(styles)(Root);\nexport default props => ;\n","import * as React from 'react';\n\nimport {inject, observer} from 'mobx-react';\nimport {RouteComponentProps} from 'react-router';\nimport {Link as RouterLink} from 'react-router-dom';\n\nimport {STORE_ROUTER} from 'app/constants';\nimport {\n Button, Grid,\n Typography, Link,\n} from '@material-ui/core';\nimport statsData from 'ji-grade-analysis-data/stats.json';\n\nexport interface MainProps extends RouteComponentProps {\n}\n\nexport interface MainState {\n}\n\n@inject(STORE_ROUTER)\n@observer\nexport class Main extends React.Component {\n constructor(props: MainProps, context: any) {\n super(props, context);\n this.state = {};\n }\n\n render() {\n return (\n \n \n Analysis anonymous grades retrieved by all JI students,\n among the courses with the same course code in different\n sections and terms, aiming at forming a better\n and fairer GPA environment in JI.\n \n \n {statsData.scores} score data\n from {statsData.lessons} classes\n of {statsData.courses} courses\n have been analyzed.\n \n \n \n \n \n );\n }\n}\n","export const STORE_TODO = 'todo';\nexport const STORE_ROUTER = 'router';\nexport const STORE_COURSES = 'courses';\n","import * as React from 'react';\nimport {forwardRef} from 'react';\nimport AddBox from '@material-ui/icons/AddBox';\nimport ArrowDownward from '@material-ui/icons/ArrowDownward';\nimport Check from '@material-ui/icons/Check';\nimport ChevronLeft from '@material-ui/icons/ChevronLeft';\nimport ChevronRight from '@material-ui/icons/ChevronRight';\nimport Clear from '@material-ui/icons/Clear';\nimport DeleteOutline from '@material-ui/icons/DeleteOutline';\nimport Edit from '@material-ui/icons/Edit';\nimport FilterList from '@material-ui/icons/FilterList';\nimport FirstPage from '@material-ui/icons/FirstPage';\nimport LastPage from '@material-ui/icons/LastPage';\nimport Remove from '@material-ui/icons/Remove';\nimport SaveAlt from '@material-ui/icons/SaveAlt';\nimport Search from '@material-ui/icons/Search';\nimport ViewColumn from '@material-ui/icons/ViewColumn';\nimport {Icons} from 'material-table';\n\nconst icons: Icons = {\n Add: forwardRef((props, ref) => ),\n Check: forwardRef((props, ref) => ),\n Clear: forwardRef((props, ref) => ),\n Delete: forwardRef((props, ref) => ),\n DetailPanel: forwardRef((props, ref) => ),\n Edit: forwardRef((props, ref) => ),\n Export: forwardRef((props, ref) => ),\n Filter: forwardRef((props, ref) => ),\n FirstPage: forwardRef((props, ref) => ),\n LastPage: forwardRef((props, ref) => ),\n NextPage: forwardRef((props, ref) => ),\n PreviousPage: forwardRef((props, ref) => ),\n ResetSearch: forwardRef((props, ref) => ),\n Search: forwardRef((props, ref) => ),\n SortArrow: forwardRef((props, ref) => ),\n ThirdStateCheck: forwardRef((props, ref) => ),\n ViewColumn: forwardRef((props, ref) => ),\n};\n\nexport default icons;\n","import * as React from 'react';\nimport {inject} from 'mobx-react';\nimport {\n // Divider,\n // TableHead,\n Paper,\n} from '@material-ui/core';\nimport MaterialTable, {Column} from 'material-table';\n\nimport icons from 'app/components/icons';\nimport {STORE_COURSES, STORE_ROUTER} from 'app/constants';\nimport {CoursesStore, RouterStore} from 'app/stores';\nimport CourseModel from 'app/models/CourseModel';\n\nexport interface CourseListProps {\n /* empty */\n}\n\nexport interface CourseListState {\n // columns: Array>;\n data: CourseModel[];\n}\n\n@inject(STORE_COURSES, STORE_ROUTER)\nexport class CourseList extends React.Component {\n columns: Array>;\n\n constructor(props: CourseListProps, context: any) {\n super(props, context);\n const coursesStore = this.props[STORE_COURSES] as CoursesStore;\n this.columns = [\n {\n title: 'Code', field: 'courseCode',\n cellStyle: {width: '10%', maxWidth: '10%'},\n },\n {\n title: 'Name', field: 'courseNameEn',\n cellStyle: {width: '35%', maxWidth: '35%'},\n },\n {\n title: 'Name (Chinese)', field: 'courseName',\n cellStyle: {width: '35%', maxWidth: '35%'},\n },\n {title: 'Terms', field: 'terms', sorting: false},\n ];\n this.state = {\n data: coursesStore.courses,\n };\n }\n\n render() {\n const router = this.props[STORE_ROUTER] as RouterStore;\n return (\n // \n
(),\n }}\n onRowClick={(event, rowData) => {\n console.log(rowData.courseCode);\n const currentHash = router.location.hash;\n console.log(currentHash);\n const nextHash = `/courses/${rowData.courseCode}`;\n if (currentHash !== nextHash) {\n router.push(nextHash);\n }\n }}\n />\n // \n );\n }\n}\n\nexport default CourseList;\n","import * as React from 'react';\n\nimport { inject, observer } from 'mobx-react';\nimport { RouteComponentProps } from 'react-router';\nimport { CourseList } from 'app/components/CourseList';\n\nimport { STORE_ROUTER } from 'app/constants';\n// import {\n// Container,\n// CssBaseline, Paper\n// } from '@material-ui/core';\n\nexport interface CourseListPageProps extends RouteComponentProps {\n}\n\nexport interface CourseListPageState {\n}\n\n@inject(STORE_ROUTER)\n@observer\nexport class CourseListPage extends React.Component {\n constructor(props: CourseListPageProps, context: any) {\n super(props, context);\n this.state = {};\n }\n\n render() {\n return (\n \n );\n }\n}\n","import * as React from 'react';\nimport {inject} from 'mobx-react';\nimport {\n Chart,\n // Legend,\n Tooltip,\n BarSeries,\n LineSeries,\n PieSeries,\n Title,\n} from '@devexpress/dx-react-chart-material-ui';\nimport {\n Animation,\n EventTracker,\n HoverState,\n ArgumentAxis,\n ValueAxis,\n // Palette,\n} from '@devexpress/dx-react-chart';\n// import {schemePastel2} from 'd3-scale-chromatic';\n\nimport {withStyles} from '@material-ui/styles';\nimport _ from 'lodash';\n\nimport {STORE_COURSES} from 'app/constants';\n// import {ScoreModel} from 'app/models/CourseModel';\n// import {CoursesStore} from 'app/stores';\nimport {ScoreData} from 'app/components/Lesson';\n\nexport interface CurveChartProps {\n lessonClassCode: string\n data: ScoreData[];\n chartType: string;\n hideUnknown: boolean;\n hideZero: boolean;\n}\n\nexport interface CurveChartState {\n hover: any;\n tooltipTarget: any;\n totalCount: number;\n chartData: ScoreData[];\n}\n\nconst chartRootStyles = {\n chart: {\n paddingRight: '20px',\n },\n};\n\n/*const legendStyles = {\n root: {\n display: 'flex',\n margin: 'auto',\n flexDirection: 'row',\n },\n};\nconst legendLabelStyles = theme => ({\n label: {\n paddingTop: theme.spacing(1),\n },\n});\nconst legendItemStyles = {\n item: {\n flexDirection: 'column',\n },\n};*/\n\nconst colors = ['#fdca00', '#19335d', '#ffffff'];\n\nconst BarSeriesPointBase = ({index, color, ...restProps}) => {\n color = colors[index % 2];\n // @ts-ignore\n return ;\n};\n\n/*const LineSeriesPathBase = ({coordinates, color, ...restProps}) => {\n console.log(coordinates);\n // color = colors[coordinates.arg % 2];\n // @ts-ignore\n return ;\n};*/\n\nconst PieSeriesPointBase = ({index, color, endAngle, ...restProps}) => {\n if (index >= 2 && index % 2 == 0 && Math.abs(endAngle - Math.PI * 2) < 1e-5) {\n color = colors[2];\n } else {\n color = colors[index % 2];\n }\n // @ts-ignore\n return ;\n};\n\nconst ChartRootBase = ({classes, ...restProps}) => (\n // @ts-ignore\n \n);\n\n/*const LegendRootBase = ({classes, ...restProps}) => (\n // @ts-ignore\n \n);\nconst LegendLabelBase = ({classes, ...restProps}) => (\n // @ts-ignore\n \n);\nconst LegendItemBase = ({classes, ...restProps}) => (\n // @ts-ignore\n \n);*/\n\n// @ts-ignore\nconst ChartRoot = withStyles(chartRootStyles, {name: 'ChartRoot'})(\n ChartRootBase);\n\n/*// @ts-ignore\nconst LegendRoot = withStyles(legendStyles, {name: 'LegendRoot'})(\n LegendRootBase);\n// @ts-ignore\nconst LegendLabel = withStyles(legendLabelStyles, {name: 'LegendLabel'})(\n LegendLabelBase);\n// @ts-ignore\nconst LegendItem = withStyles(legendItemStyles, {name: 'LegendItem'})(\n LegendItemBase);*/\n\n@inject(STORE_COURSES)\nexport class CurveChart extends React.Component {\n constructor(props: CurveChartProps, context: any) {\n super(props, context);\n const chartData = CurveChart.getChartData(props.data, props.chartType,\n props.hideUnknown, props.hideZero);\n this.state = {\n hover: null,\n tooltipTarget: null,\n totalCount: CurveChart.getTotalCount(chartData),\n chartData: chartData,\n };\n }\n\n static getTotalCount(data: ScoreData[]) {\n return _.sumBy(data, (scoreData: ScoreData) => scoreData.count);\n }\n\n static getChartData(\n data: ScoreData[], chartType: string, hideUnknown: boolean,\n hideZero: boolean) {\n let newData = data;\n if (hideZero || chartType === 'pie') {\n newData = _.filter(data, scoreData => scoreData.count != 0);\n }\n if (hideUnknown && newData.length > 0 && _.last(newData).grade ===\n 'Unknown') {\n newData = _.slice(newData, 0, newData.length - 1);\n }\n return newData;\n }\n\n /* updateChartData() {\n this.setState({totalCount: CurveChart.getTotalCount(this.props.data)});\n }*/\n\n /* componentDidMount() {\n this.updateChartData();\n }*/\n\n componentDidUpdate(\n prevProps: Readonly,\n prevState: Readonly, snapshot?: any) {\n if (this.props.lessonClassCode === prevProps.lessonClassCode) {\n return;\n } else if (this.props.chartType === prevProps.chartType &&\n this.props.hideUnknown === prevProps.hideUnknown &&\n this.props.hideZero === prevProps.hideZero\n ) {\n return;\n }\n const chartData = CurveChart.getChartData(this.props.data,\n this.props.chartType, this.props.hideUnknown, this.props.hideZero);\n this.setState({\n totalCount: CurveChart.getTotalCount(chartData),\n chartData: chartData,\n });\n }\n\n onChangeHover(hover) {\n this.setState({hover});\n }\n\n onChangeTooltip(targetItem) {\n this.setState({tooltipTarget: targetItem});\n }\n\n render() {\n const TooltipContent = (props) => {\n // const { targetItem, text, ...restProps } = props;\n const {targetItem} = props;\n const pointData = this.state.chartData[targetItem.point];\n const percentage = Math.round(\n pointData.count / this.state.totalCount * 10000) / 100;\n // console.log(targetItem);\n return (\n \n {pointData.grade}: {percentage}%\n ({pointData.count}/{this.state.totalCount})\n
\n );\n };\n\n let series = [];\n if (this.props.chartType === 'bar') {\n series = [\n ,\n // @ts-ignore\n ,\n // @ts-ignore\n ,\n ];\n } else if (this.props.chartType === 'line') {\n series = [\n ,\n // @ts-ignore\n ,\n // @ts-ignore\n ,\n ];\n } else if (this.props.chartType === 'pie') {\n series = [\n ,\n //