├── 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 |
13 |
14 | 15 |
16 | 17 | page view:  18 | 19 | 20 |
21 | 22 | Version {packageData.version}, Powered by  23 | 24 | tc-imba 25 | 26 | , Copyright 2019-2021 27 | 28 |
29 |
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 | <Animation /> 177 | <Legend 178 | // position="right" 179 | // rootComponent={LegendRoot} 180 | // itemComponent={LegendItem} 181 | // // @ts-ignore 182 | // labelComponent={LegendLabel} 183 | /> 184 | <EventTracker /> 185 | <HoverState 186 | hover={this.state.hover} 187 | onHoverChange={this.onChangeHover.bind(this)} 188 | /> 189 | <Tooltip 190 | targetItem={this.state.tooltipTarget} 191 | onTargetItemChange={this.onChangeTooltip.bind(this)} 192 | contentComponent={TooltipContent} 193 | /> 194 | </Chart> 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 <BarSeries.Point index={index} color={color} {...restProps} />; 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 | <PieSeries.Point 86 | index={index} 87 | endAngle={endAngle} 88 | color={color} 89 | {...restProps} 90 | /> 91 | ); 92 | }; 93 | 94 | const ChartRootBase = ({ classes, ...restProps }) => ( 95 | // @ts-ignore 96 | <Chart.Root {...restProps} className={classes.chart} /> 97 | ); 98 | 99 | /*const LegendRootBase = ({classes, ...restProps}) => ( 100 | // @ts-ignore 101 | <Legend.Root {...restProps} className={classes.root}/> 102 | ); 103 | const LegendLabelBase = ({classes, ...restProps}) => ( 104 | // @ts-ignore 105 | <Legend.Label {...restProps} className={classes.label}/> 106 | ); 107 | const LegendItemBase = ({classes, ...restProps}) => ( 108 | // @ts-ignore 109 | <Legend.Item {...restProps} className={classes.item}/> 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<CurveChartProps>, 182 | prevState: Readonly<CurveChartState>, 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 | <h3> 224 | {pointData.grade}: {percentage}% ({pointData.count}/ 225 | {this.state.totalCount}) 226 | </h3> 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 | <BarSeries 238 | key="series" 239 | valueField="count" 240 | argumentField="grade" 241 | pointComponent={BarSeriesPointBase} 242 | />, 243 | // @ts-ignore 244 | <ArgumentAxis key="argument" />, 245 | // @ts-ignore 246 | <ValueAxis key="value" showGrid={false} tickFormat={tickFormat} />, 247 | ]; 248 | } else if (this.props.chartType === "pie") { 249 | series = [ 250 | <PieSeries 251 | key="series" 252 | valueField="count" 253 | argumentField="grade" 254 | innerRadius={0.4} 255 | outerRadius={0.8} 256 | pointComponent={PieSeriesPointBase} 257 | />, 258 | // <Legend key="legend"/>, 259 | // <Palette scheme={schemePastel2}/>, 260 | ]; 261 | } 262 | 263 | return ( 264 | <Chart 265 | data={this.state.chartData} 266 | // @ts-ignore 267 | rootComponent={ChartRoot} 268 | > 269 | {series} 270 | <Title text="" /> 271 | <Animation /> 272 | <EventTracker /> 273 | <HoverState 274 | hover={this.state.hover} 275 | onHoverChange={this.onChangeHover.bind(this)} 276 | /> 277 | <Tooltip 278 | targetItem={this.state.tooltipTarget} 279 | onTargetItemChange={this.onChangeTooltip.bind(this)} 280 | contentComponent={TooltipContent} 281 | /> 282 | </Chart> 283 | ); 284 | } 285 | } 286 | 287 | { 288 | /*<Legend*/ 289 | } 290 | { 291 | /* // position="right"*/ 292 | } 293 | { 294 | /* // rootComponent={LegendRoot}*/ 295 | } 296 | { 297 | /* // itemComponent={LegendItem}*/ 298 | } 299 | { 300 | /* // // @ts-ignore*/ 301 | } 302 | { 303 | /* // labelComponent={LegendLabel}*/ 304 | } 305 | { 306 | /*/>*/ 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<CourseProps, CourseState> { 46 | columns: Array<Column<LessonModel>>; 47 | scoreDataMap: Dictionary<ScoreData[]> = {}; 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<CourseProps>, 155 | prevState: Readonly<CourseState>, 156 | snapshot?: any 157 | ) { 158 | if (this.props.courseCode !== prevProps.courseCode) { 159 | this.updateCourse(); 160 | } 161 | } 162 | 163 | onChangeChartType(event: React.ChangeEvent<HTMLInputElement>) { 164 | this.setState({ 165 | chartType: (event.target as HTMLInputElement).value, 166 | }); 167 | } 168 | 169 | onChangeHideUnknown(event: React.ChangeEvent<HTMLInputElement>) { 170 | this.setState({ 171 | hideUnknown: event.target.checked, 172 | }); 173 | } 174 | 175 | onChangeHideZero(event: React.ChangeEvent<HTMLInputElement>) { 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 | <StickyContainer style={{ width: "100%" }}> 193 | <Sticky> 194 | {({ style }) => ( 195 | // TODO: better way to make it opaque 196 | <div className="MuiPaper-root" style={{ zIndex: 64, ...style }}> 197 | <Grid container> 198 | <RadioGroup 199 | value={this.state.chartType} 200 | onChange={this.onChangeChartType.bind(this)} 201 | row 202 | > 203 | <FormControlLabel 204 | value="bar" 205 | control={<Radio color="secondary" />} 206 | label="Bar" 207 | labelPlacement="bottom" 208 | /> 209 | <FormControlLabel 210 | value="pie" 211 | control={<Radio color="secondary" />} 212 | label="Pie" 213 | labelPlacement="bottom" 214 | /> 215 | </RadioGroup> 216 | <FormControlLabel 217 | control={ 218 | <Switch 219 | checked={this.state.hideUnknown} 220 | onChange={this.onChangeHideUnknown.bind(this)} 221 | color="secondary" 222 | /> 223 | } 224 | label="Hide Unknown" 225 | /> 226 | <FormControlLabel 227 | control={ 228 | <Switch 229 | checked={this.state.hideZero} 230 | onChange={this.onChangeHideZero.bind(this)} 231 | color="secondary" 232 | /> 233 | } 234 | label="Hide Zero" 235 | /> 236 | </Grid> 237 | </div> 238 | )} 239 | </Sticky> 240 | <MaterialTable 241 | title={title} 242 | columns={this.columns} 243 | data={this.state.lessons} 244 | icons={icons} 245 | options={{ 246 | pageSize: 10, 247 | pageSizeOptions: [10, 25, 50, 100], 248 | searchText: this.searchText, 249 | columnsButton: true, 250 | }} 251 | style={{ width: "100%" }} 252 | components={{ 253 | Container: (props) => <Paper elevation={0} {...props}></Paper>, 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 | <Lesson 262 | scores={scoreData} 263 | lessonClassCode={lessonClassCode} 264 | chartType={this.state.chartType} 265 | hideUnknown={this.state.hideUnknown} 266 | hideZero={this.state.hideZero} 267 | /> 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 | </StickyContainer> 280 | ); 281 | } 282 | } 283 | 284 | // export default Course; 285 | 286 | { 287 | /*<Tabs 288 | value={this.state.lessonClassCode} 289 | onChange={this.handleChange.bind(this)} 290 | variant="scrollable" 291 | scrollButtons="auto" 292 | > 293 | { 294 | this.state.course.lessons.map(lesson => 295 | <Tab key={lesson[0]} label={lesson[1]} value={lesson[0]}/>) 296 | } 297 | </Tabs> 298 | <Typography 299 | component="div" 300 | role="tabpanel" 301 | > 302 | {this.state.lesson.lecturers} 303 | {typeof this.state.lessonClassCode === 'string' ? 304 | <CurvePieChart lessonClassCode={this.state.lessonClassCode}/> : 305 | null 306 | } 307 | </Typography>*/ 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<string[]>;\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<TodoModel>;\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<TodoModel>): void => {\n this.todos.push(new TodoModel(item.text, item.completed));\n };\n\n @action\n editTodo = (id: number, data: Partial<TodoModel>): 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<CourseModel>;\n @observable public coursesMap: Dictionary<CourseModel>;\n @observable public lessonsMap: Dictionary<LessonModel>;\n @observable public scoresMap: Dictionary<ScoreModel>;\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<HeaderProps, HeaderState> {\n render() {\n return (\n <div>\n <Grid container justify=\"center\">\n <Typography component=\"h1\" variant=\"h3\" color=\"inherit\"\n gutterBottom align={'center'}>\n UM-SJTU JI Grade Analysis\n </Typography>\n </Grid>\n <GitHubButtonProvider namespace={this.props.namespace}\n repo={this.props.repo}>\n <Grid container justify=\"flex-end\" spacing={1}>\n <Grid item>\n <GitHubButton type=\"stargazers\" size=\"large\"/>\n </Grid>\n <Grid item>\n <GitHubButton type=\"watchers\" size=\"large\"/>\n </Grid>\n <Grid item>\n <GitHubButton type=\"forks\" size=\"large\"/>\n </Grid>\n </Grid>\n </GitHubButtonProvider>\n </div>\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<FooterProps, FooterState> {\n render() {\n return (\n <footer>\n <br/>\n <Grid container justify=\"flex-end\">\n Version {packageData.version}, Powered by \n <Link href=\"https://github.com/tc-imba\" target=\"_blank\">tc-imba</Link>,\n Copyright 2019-2020\n </Grid>\n </footer>\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<any>,\n WithStyles<typeof styles> {\n}\n\nexport interface RootState {\n}\n\nclass Root extends React.Component<RootProps, RootState> {\n renderDevTool() {\n if (process.env.NODE_ENV !== 'production') {\n const DevTools = require('mobx-react-devtools').default;\n return <DevTools/>;\n }\n }\n\n render() {\n const {classes} = this.props;\n return (\n <div className=\"container\">\n <Container maxWidth={'lg'}>\n <CssBaseline/>\n <br/>\n <Paper>\n <div className={classes.mainFeaturedPostContent}>\n <Header namespace=\"tc-imba\" repo=\"ji-grade-analysis\"/>\n <br/>\n <Grid container justify=\"center\">\n {this.props.children}\n </Grid>\n <Footer/>\n </div>\n </Paper>\n </Container>\n {this.renderDevTool()}\n </div>\n );\n }\n}\n\nconst RootWithStyle = withStyles(styles)(Root);\nexport default props => <RootWithStyle {...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<any> {\n}\n\nexport interface MainState {\n}\n\n@inject(STORE_ROUTER)\n@observer\nexport class Main extends React.Component<MainProps, MainState> {\n constructor(props: MainProps, context: any) {\n super(props, context);\n this.state = {};\n }\n\n render() {\n return (\n <Grid item md={10}>\n <Typography variant=\"h5\" color=\"inherit\" paragraph>\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 </Typography>\n <Typography variant=\"h5\" color=\"inherit\" paragraph>\n <Link color=\"secondary\">{statsData.scores}</Link> score data\n from <Link color=\"secondary\">{statsData.lessons}</Link> classes\n of <Link color=\"secondary\">{statsData.courses}</Link> courses\n have been analyzed.\n </Typography>\n <Grid container justify=\"center\">\n <Button variant=\"outlined\" color=\"secondary\" size=\"large\"\n component={RouterLink} to=\"/courses\">\n Start Explore\n </Button>\n </Grid>\n </Grid>\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) => <AddBox {...props} ref={ref}/>),\n Check: forwardRef((props, ref) => <Check {...props} ref={ref}/>),\n Clear: forwardRef((props, ref) => <Clear {...props} ref={ref}/>),\n Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref}/>),\n DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref}/>),\n Edit: forwardRef((props, ref) => <Edit {...props} ref={ref}/>),\n Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref}/>),\n Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref}/>),\n FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref}/>),\n LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref}/>),\n NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref}/>),\n PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref}/>),\n ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref}/>),\n Search: forwardRef((props, ref) => <Search {...props} ref={ref}/>),\n SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref}/>),\n ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref}/>),\n ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={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<Column<CourseModel>>;\n data: CourseModel[];\n}\n\n@inject(STORE_COURSES, STORE_ROUTER)\nexport class CourseList extends React.Component<CourseListProps, CourseListState> {\n columns: Array<Column<CourseModel>>;\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 // <div>\n <MaterialTable\n title=\"Courses\"\n columns={this.columns}\n data={this.state.data}\n icons={icons}\n options={{\n pageSize: 10,\n pageSizeOptions: [10, 25, 50, 100],\n }}\n style={{width: '100%'}}\n components={{\n Container: props => (<Paper elevation={0} {...props}></Paper>),\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 // </div>\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<any> {\n}\n\nexport interface CourseListPageState {\n}\n\n@inject(STORE_ROUTER)\n@observer\nexport class CourseListPage extends React.Component<CourseListPageProps, CourseListPageState> {\n constructor(props: CourseListPageProps, context: any) {\n super(props, context);\n this.state = {};\n }\n\n render() {\n return (\n <CourseList/>\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 <BarSeries.Point index={index}\n color={color} {...restProps}/>;\n};\n\n/*const LineSeriesPathBase = ({coordinates, color, ...restProps}) => {\n console.log(coordinates);\n // color = colors[coordinates.arg % 2];\n // @ts-ignore\n return <LineSeries.Path coordinates={coordinates}\n color={color} {...restProps}/>;\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 <PieSeries.Point index={index} endAngle={endAngle}\n color={color} {...restProps}/>;\n};\n\nconst ChartRootBase = ({classes, ...restProps}) => (\n // @ts-ignore\n <Chart.Root {...restProps} className={classes.chart}/>\n);\n\n/*const LegendRootBase = ({classes, ...restProps}) => (\n // @ts-ignore\n <Legend.Root {...restProps} className={classes.root}/>\n);\nconst LegendLabelBase = ({classes, ...restProps}) => (\n // @ts-ignore\n <Legend.Label {...restProps} className={classes.label}/>\n);\nconst LegendItemBase = ({classes, ...restProps}) => (\n // @ts-ignore\n <Legend.Item {...restProps} className={classes.item}/>\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<CurveChartProps, CurveChartState> {\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<CurveChartProps>,\n prevState: Readonly<CurveChartState>, 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 <h3>\n {pointData.grade}: {percentage}%\n ({pointData.count}/{this.state.totalCount})\n </h3>\n );\n };\n\n let series = [];\n if (this.props.chartType === 'bar') {\n series = [\n <BarSeries\n key=\"series\"\n valueField=\"count\"\n argumentField=\"grade\"\n pointComponent={BarSeriesPointBase}\n />,\n // @ts-ignore\n <ArgumentAxis key=\"argument\"/>,\n // @ts-ignore\n <ValueAxis key=\"value\"/>,\n ];\n } else if (this.props.chartType === 'line') {\n series = [\n <LineSeries\n key=\"series\"\n valueField=\"count\"\n argumentField=\"grade\"\n color={colors[1]}\n />,\n // @ts-ignore\n <ArgumentAxis key=\"argument\"/>,\n // @ts-ignore\n <ValueAxis key=\"value\"/>,\n ];\n } else if (this.props.chartType === 'pie') {\n series = [\n <PieSeries\n key=\"series\"\n valueField=\"count\"\n argumentField=\"grade\"\n innerRadius={0.4}\n outerRadius={0.8}\n pointComponent={PieSeriesPointBase}\n />,\n // <Legend key=\"legend\"/>,\n // <Palette scheme={schemePastel2}/>,\n ];\n }\n\n return (\n <Chart\n data={this.state.chartData}\n // @ts-ignore\n rootComponent={ChartRoot}\n >\n {series}\n <Title text=\"\"/>\n <Animation/>\n <EventTracker/>\n <HoverState hover={this.state.hover}\n onHoverChange={this.onChangeHover.bind(this)}/>\n <Tooltip\n targetItem={this.state.tooltipTarget}\n onTargetItemChange={this.onChangeTooltip.bind(this)}\n contentComponent={TooltipContent}\n />\n </Chart>\n );\n }\n}\n\n{/*<Legend*/\n}\n{/* // position=\"right\"*/\n}\n{/* // rootComponent={LegendRoot}*/\n}\n{/* // itemComponent={LegendItem}*/\n}\n{/* // // @ts-ignore*/\n}\n{/* // labelComponent={LegendLabel}*/\n}\n{/*/>*/\n}\n","import * as React from 'react';\nimport {Grid} from '@material-ui/core';\nimport {CurveChart} from 'app/components/CurveChart';\n\nexport interface ScoreData {\n grade: string;\n count: number;\n}\n\nexport interface LessonProps {\n /* empty */\n scores: ScoreData[];\n lessonClassCode: string;\n chartType: string;\n hideUnknown: boolean;\n hideZero: boolean;\n}\n\nexport interface LessonState {\n\n}\n\nexport class Lesson extends React.Component<LessonProps, LessonState> {\n\n constructor(props: LessonProps, context: any) {\n super(props, context);\n // console.log(this.props.lessonClassCode)\n }\n\n /* getColor() {\n const colors = ['#fdca00', '#19335d'];\n // console.log(this.props.lessonClassCode);\n const digit = this.props.lessonClassCode.charCodeAt(\n this.props.lessonClassCode.length - 1);\n return colors[digit % 2];\n }*/\n\n render() {\n return (\n <Grid>\n <CurveChart data={this.props.scores} chartType={this.props.chartType}\n lessonClassCode={this.props.lessonClassCode}\n hideUnknown={this.props.hideUnknown}\n hideZero={this.props.hideZero}/>\n </Grid>\n );\n }\n\n}\n\n","import * as React from 'react';\nimport {inject} from 'mobx-react';\nimport {\n FormControlLabel,\n // Tab,\n // Tabs,\n // Typography,\n RadioGroup,\n Radio,\n Switch,\n Grid,\n Paper,\n} from '@material-ui/core';\nimport MaterialTable, {Column} from 'material-table';\nimport icons from 'app/components/icons';\nimport _, {Dictionary} from 'lodash';\n\nimport {STORE_COURSES} from 'app/constants';\nimport CourseModel, {LessonModel} from 'app/models/CourseModel';\nimport {CoursesStore} from 'app/stores';\nimport {Lesson, ScoreData} from 'app/components/Lesson';\n\n// import {CurvePieChart} from 'app/components/CurvePieChart';\n\nexport interface CourseProps {\n /* empty */\n courseCode: string\n}\n\nexport interface CourseState {\n course: CourseModel;\n lessons: LessonModel[];\n chartType: string;\n hideUnknown: boolean;\n hideZero: boolean;\n // lessonClassCode: string | boolean;\n // lesson: LessonModel | null;\n}\n\nconst grades = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F'];\n\n@inject(STORE_COURSES)\nexport class Course extends React.Component<CourseProps, CourseState> {\n columns: Array<Column<LessonModel>>;\n scoreDataMap: Dictionary<ScoreData[]> = {};\n\n constructor(props: CourseProps, context: any) {\n super(props, context);\n this.columns = [\n {\n title: 'Code', field: 'lessonClassCode',\n cellStyle: {width: '20%', maxWidth: '20%'},\n },\n {\n title: 'Term', field: 'termName',\n cellStyle: {width: '10%', maxWidth: '10%'},\n },\n {\n title: 'Name', field: 'lessonClassName',\n cellStyle: {width: '25%', maxWidth: '25%'},\n },\n {\n title: 'Found', field: 'scoreNum',\n cellStyle: {width: '10%', maxWidth: '10%'},\n },\n {\n title: 'Elected', field: 'studentNumStr',\n cellStyle: {width: '10%', maxWidth: '10%'},\n },\n {\n title: 'Lecturers', field: 'lecturersStr', sorting: false,\n cellStyle: {width: '25%', maxWidth: '25%'},\n },\n ];\n this.state = {\n course: null,\n lessons: [],\n chartType: 'bar',\n hideUnknown: false,\n hideZero: false,\n };\n }\n\n updateCourse() {\n console.log(this.props.courseCode);\n const coursesStore = this.props[STORE_COURSES] as CoursesStore;\n const course = coursesStore.coursesMap[this.props.courseCode];\n let lessons: LessonModel[] = [];\n course.lessons.forEach(a => {\n if (coursesStore.lessonsMap.hasOwnProperty(a[0])) {\n let lesson = coursesStore.lessonsMap[a[0]];\n lesson.lecturersStr = lesson.lecturers.join(', ');\n lesson.studentNumStr = lesson.studentNum >= 0 ?\n lesson.studentNum.toString() : '-';\n lessons.push(lesson);\n }\n });\n this.setState({\n course: course,\n lessons: lessons,\n });\n }\n\n ensureScoreDataMap(lessonClassCode: string) {\n if (this.scoreDataMap.hasOwnProperty(lessonClassCode)) {\n return this.scoreDataMap[lessonClassCode];\n }\n const coursesStore = this.props[STORE_COURSES] as CoursesStore;\n let studentNum = 0;\n let scoreNum = 0;\n let scoreData: ScoreData[];\n if (coursesStore.lessonsMap.hasOwnProperty(lessonClassCode)) {\n const lesson = coursesStore.lessonsMap[lessonClassCode];\n studentNum = lesson.studentNum;\n }\n if (coursesStore.scoresMap.hasOwnProperty(lessonClassCode)) {\n const scoreRawData = coursesStore.scoresMap[lessonClassCode];\n const scores = scoreRawData.scores;\n const temp = _.zip(grades, scores);\n scoreData = _.map(temp, value => {\n scoreNum += value[1];\n return {grade: value[0], count: value[1]};\n });\n } else {\n scoreData = _.map(grades, value => {\n return {grade: value, count: 0};\n });\n }\n if (studentNum > scoreNum) {\n scoreData.push({grade: 'Unknown', count: studentNum - scoreNum});\n } else {\n scoreData.push({grade: 'Unknown', count: 0});\n }\n this.scoreDataMap[lessonClassCode] = scoreData;\n return scoreData;\n }\n\n componentDidMount() {\n this.updateCourse();\n }\n\n componentDidUpdate(\n prevProps: Readonly<CourseProps>, prevState: Readonly<CourseState>,\n snapshot?: any) {\n if (this.props.courseCode !== prevProps.courseCode) {\n this.updateCourse();\n }\n }\n\n onChangeChartType(event: React.ChangeEvent<HTMLInputElement>) {\n this.setState({\n chartType: (event.target as HTMLInputElement).value,\n });\n }\n\n onChangeHideUnknown(event: React.ChangeEvent<HTMLInputElement>) {\n this.setState({\n hideUnknown: event.target.checked,\n });\n }\n\n onChangeHideZero(event: React.ChangeEvent<HTMLInputElement>) {\n this.setState({\n hideZero: event.target.checked,\n });\n }\n\n render() {\n let title = this.props.courseCode;\n if (this.state.course) {\n title += ' - ' + this.state.course.courseNameEn + ' - ' +\n this.state.course.courseName;\n }\n return (\n <React.Fragment>\n <Grid container>\n <RadioGroup value={this.state.chartType}\n onChange={this.onChangeChartType.bind(this)} row>\n <FormControlLabel\n value=\"bar\"\n control={<Radio color=\"secondary\"/>}\n label=\"Bar\"\n labelPlacement=\"bottom\"\n />\n <FormControlLabel\n value=\"line\"\n control={<Radio color=\"secondary\"/>}\n label=\"Line\"\n labelPlacement=\"bottom\"\n />\n <FormControlLabel\n value=\"pie\"\n control={<Radio color=\"secondary\"/>}\n label=\"Pie\"\n labelPlacement=\"bottom\"\n />\n </RadioGroup>\n <FormControlLabel\n control={\n <Switch\n checked={this.state.hideUnknown}\n onChange={this.onChangeHideUnknown.bind(this)}\n color=\"secondary\"\n />\n }\n label=\"Hide Unknown\"\n />\n <FormControlLabel\n control={\n <Switch\n checked={this.state.hideZero}\n onChange={this.onChangeHideZero.bind(this)}\n color=\"secondary\"\n />\n }\n label=\"Hide Zero\"\n />\n </Grid>\n <MaterialTable\n title={title}\n columns={this.columns}\n data={this.state.lessons}\n icons={icons}\n options={{\n pageSize: 10,\n pageSizeOptions: [10, 25, 50, 100],\n }}\n style={{width: '100%'}}\n components={{\n Container: props => (<Paper elevation={0} {...props}></Paper>),\n }}\n detailPanel={rowData => {\n const lessonClassCode = rowData.lessonClassCode;\n const scoreData = this.ensureScoreDataMap(lessonClassCode);\n // console.log(lessonClassCode);\n // console.log(scoreData);\n return (\n <Lesson scores={scoreData} lessonClassCode={lessonClassCode}\n chartType={this.state.chartType}\n hideUnknown={this.state.hideUnknown}\n hideZero={this.state.hideZero}/>\n );\n }}\n onRowClick={(event, rowData, togglePanel) => togglePanel()}\n />\n </React.Fragment>\n );\n }\n}\n\n// export default Course;\n\n{/*<Tabs\n value={this.state.lessonClassCode}\n onChange={this.handleChange.bind(this)}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n >\n {\n this.state.course.lessons.map(lesson =>\n <Tab key={lesson[0]} label={lesson[1]} value={lesson[0]}/>)\n }\n </Tabs>\n <Typography\n component=\"div\"\n role=\"tabpanel\"\n >\n {this.state.lesson.lecturers}\n {typeof this.state.lessonClassCode === 'string' ?\n <CurvePieChart lessonClassCode={this.state.lessonClassCode}/> :\n null\n }\n </Typography>*/\n}\n","import * as React from 'react';\n\nimport {inject, observer} from 'mobx-react';\nimport {RouteComponentProps} from 'react-router';\nimport {Course} from 'app/components/Course';\n\nimport {STORE_COURSES} from 'app/constants';\nimport {CoursesStore} from 'app/stores/CoursesStore';\n\n// import {RouterStore} from 'app/stores';\n\nexport interface CoursePageProps extends RouteComponentProps<any> {\n}\n\nexport interface CoursePageState {\n courseCode: string;\n available: boolean;\n}\n\n@inject(STORE_COURSES)\n@observer\nexport class CoursePage extends React.Component<CoursePageProps, CoursePageState> {\n constructor(props: CoursePageProps, context: any) {\n super(props, context);\n const {match} = this.props;\n const courseStore = this.props[STORE_COURSES] as CoursesStore;\n const courseCode = match.params.courseCode.toUpperCase();\n this.state = {\n courseCode: courseCode,\n available: courseStore.coursesMap.hasOwnProperty(courseCode),\n };\n }\n\n render() {\n return (\n this.state.available ?\n <Course courseCode={this.state.courseCode}/> :\n <div>Sorry, course {this.state.courseCode} is not available.</div>\n );\n }\n}\n","import * as React from 'react';\nimport {hot} from 'react-hot-loader/root';\nimport {Router, Route, Switch} from 'react-router';\nimport Root from 'app/containers/Root';\nimport {Main} from 'app/containers/Main';\nimport {CourseListPage} from 'app/containers/CourseListPage';\nimport {CoursePage} from 'app/containers/CoursePage';\n\n// render react DOM\nexport const App = hot(({history}) => (\n <Root>\n <Router history={history}>\n <Switch>\n <Route exact path='/' component={Main}/>\n <Route exact path='/courses' component={CourseListPage}/>\n <Route path=\"/courses/:courseCode\" component={CoursePage}/>\n </Switch>\n </Router>\n </Root>\n));\n","import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport {Provider} from 'mobx-react';\n\nimport {createHashHistory} from 'history';\nimport {TodoModel} from 'app/models';\nimport {createStores} from 'app/stores';\nimport {App} from 'app';\n\n\nimport 'typeface-roboto';\n\n// default fixtures for TodoStore\nconst defaultTodos = [\n new TodoModel('Use Mobx'),\n new TodoModel('Use React', true),\n];\n\n// prepare MobX stores\nconst history = createHashHistory();\nconst rootStore = createStores(history, defaultTodos);\n\n// render react DOM\nReactDOM.render(\n <Provider {...rootStore}>\n <App history={history}/>\n </Provider>,\n document.getElementById('root'),\n);\n\n/*\n<Container>\n <Paper className={classes.mainFeaturedPost}>\n <Grid container>\n <Grid item md={6}>\n <div className={classes.mainFeaturedPostContent}>\n <Typography component=\"h1\" variant=\"h3\" color=\"inherit\"\n gutterBottom>\n JI Grade Analysis\n </Typography>\n <Typography variant=\"h5\" color=\"inherit\" paragraph>\n Analysis anonymous grades retrieved by all JI students,\n among the courses with the same course id in different\n sections and academic years, aiming at forming a better\n and fairer GPA environment in JI.\n </Typography>\n </div>\n </Grid>\n </Grid>\n </Paper>\n </Container>\n */\n"],"sourceRoot":""} --------------------------------------------------------------------------------