Compare commits

...

106 Commits

Author SHA1 Message Date
Hykilpikonna b6b3f921a2 [R] Recreate license 2019-09-24 07:37:09 -04:00
Hykilpikonna d3234be6db [-] Remove license 2019-09-24 07:35:37 -04:00
Hykilpikonna 50a5f9dcc5 [O] Remove todo that is already done 2019-09-23 21:11:27 -04:00
Hykilpikonna 62d40dd0a7 [+] Add cname 2019-09-23 20:14:49 -04:00
Hykilpikonna 9e8e44b91f [-] Remove public path 2019-09-23 20:14:41 -04:00
Hykilpikonna df85e1084f [+] Create deploy script
https://cli.vuejs.org/guide/deployment.html#github-pages
2019-09-23 19:58:02 -04:00
Hykilpikonna 0b41f8ac5d [+] Add deploy path 2019-09-23 19:56:27 -04:00
Hykilpikonna 7b11537e0f [R] Make title shorter 2019-09-23 19:52:00 -04:00
Hykilpikonna 830f55441c [R] Create License
[R] Create License
2019-09-23 19:50:57 -04:00
Hykilpikonna 0a49d791ae [R] Create License 2019-09-23 19:50:35 -04:00
Hykilpikonna c675fc5650 [M] Merge Feature > Master: First release
[M] Merge Feature > Master: First release
2019-09-23 19:49:33 -04:00
Hykilpikonna ad8496e661 [R] Add license 2019-09-23 19:47:16 -04:00
Hykilpikonna 72b9e4d214 [R] Write introduction 2019-09-23 19:46:31 -04:00
Hykilpikonna fa3fc44526 [U] Update version number 2019-09-23 19:41:49 -04:00
Hykilpikonna 419cced592 [R] Add a title 2019-09-23 19:37:58 -04:00
Hykilpikonna 352fd58fb5 [O] Show the graph with minimum y axis at 70 2019-09-23 19:33:27 -04:00
Hykilpikonna f043d77e5e [M] Move graph title to the graph component 2019-09-23 19:32:37 -04:00
Hykilpikonna 9e88f3efe1 [U] Ignore 0 grades in calculations 2019-09-23 19:32:21 -04:00
Hykilpikonna cadd328958 [O] Separate settings 2019-09-20 19:27:10 -04:00
Hykilpikonna 8c4759d497 [-] Remove unnecessary sample chart data 2019-09-20 19:26:23 -04:00
Hykilpikonna ac8f488ab4 [O] Refresh page after signing out 2019-09-20 19:21:07 -04:00
Hykilpikonna df639a360c [+] Implement actual sign out function 2019-09-20 19:10:37 -04:00
Hykilpikonna 24328508c0 [+] Bind sign out event 2019-09-20 19:10:23 -04:00
Hykilpikonna ebd4849703 [O] Optimize sign out button 2019-09-20 19:10:12 -04:00
Hykilpikonna 80d72bb5c0 Merge branch 'styling' into feature 2019-09-20 18:52:56 -04:00
Hykilpikonna 204e7dc04a [F] Move signOut() to the right place 2019-09-20 18:50:13 -04:00
Hykilpikonna cef0b31dba [O] Only display score when it is complete 2019-09-19 19:11:54 -04:00
Hykilpikonna 22ba3acab0 [F] Fix key word typo 2019-09-19 19:11:00 -04:00
Hykilpikonna 88bdcd8cf8 [O] Optimize code length 2019-09-19 18:58:59 -04:00
Hykilpikonna 8559a93b9c [+] Filter courses to a variable 2019-09-19 18:58:00 -04:00
Hykilpikonna f8db85dca3 [F] Fix getter signature problem 2019-09-19 18:57:47 -04:00
Hykilpikonna b4bd24d884 [+] Filter courses without assignments 2019-09-19 18:55:55 -04:00
Hykilpikonna f6d30a750a [+] Filter courses without levels 2019-09-19 18:55:44 -04:00
Hykilpikonna 988d6f5463 [+] Create method to filter out courses 2019-09-19 18:55:34 -04:00
Hykilpikonna 608132ad6c [O] Reduce code length 2019-09-19 18:49:00 -04:00
Hykilpikonna 6cffb745a7 [O] Specify type with ts-ignore 2019-09-19 18:48:17 -04:00
Hykilpikonna 82212cf10c [O] Fix " warning 2019-09-19 18:46:40 -04:00
Hykilpikonna 9656b3184b [O] Optimize nav bar shadow 2019-09-18 23:25:50 -04:00
Hykilpikonna 08b665a1fa [+] Call custom event to sign out 2019-09-18 23:25:25 -04:00
Hykilpikonna 500e13ef9d [+] Add onclick to sign-out 2019-09-18 23:25:12 -04:00
Hykilpikonna b95220ba1a [+] Add sign out button 2019-09-18 23:23:24 -04:00
Hykilpikonna b8dfb8f732 [F] Fix negative index problem 2019-09-18 23:07:43 -04:00
Hykilpikonna 002aa84444 [O] Ignore non-active scores 2019-09-18 21:52:10 -04:00
Hykilpikonna e7c513695d [O] Disable host check 2019-09-18 21:51:27 -04:00
Hykilpikonna e51dbd2c5b [O] Make GPA time font size smaller 2019-09-18 21:50:44 -04:00
Hykilpikonna 9157232d45 [O] Make GPA font size larger 2019-09-18 21:50:32 -04:00
Hykilpikonna 17ef8f4380 [O] Vertically center 2019-09-18 21:50:19 -04:00
Hykilpikonna c31dbf0e50 [O] Make GPA header font smaller 2019-09-18 21:50:05 -04:00
Hykilpikonna 9c2c1c3195 [O] Optimize card margins 2019-09-18 08:27:59 -04:00
Hykilpikonna 820c3c1148 [O] Optimize layout 2019-09-18 08:27:27 -04:00
Hykilpikonna 6efb832212 Merge branch 'feature' into styling 2019-09-18 08:26:07 -04:00
Hykilpikonna c885137ed7 [O] Fix floating point precision errors 2019-09-16 22:47:13 -04:00
Hykilpikonna bb4b34722f [+] Display GPA string 2019-09-16 22:24:56 -04:00
Hykilpikonna 2beca45e38 [+] Encapsulate method to get GPA string 2019-09-16 22:24:47 -04:00
Hykilpikonna cbec0add3b Revert "[T] Test GPA calculations"
This reverts commit 0ebce968b9.
2019-09-16 22:24:10 -04:00
Hykilpikonna 0ebce968b9 [T] Test GPA calculations 2019-09-16 22:24:06 -04:00
Hykilpikonna 4095041925 [+] Add status field in course model 2019-09-16 22:23:52 -04:00
Hykilpikonna 0e618ceb13 [+] Create cards in overall 2019-09-07 17:04:04 -04:00
Hykilpikonna 042e72abb6 [O] Reduce redundant code 2019-09-07 16:48:57 -04:00
Hykilpikonna 517235982b [F] Fix return type void 2019-09-07 16:48:46 -04:00
Hykilpikonna 6e7041edcd [+] Create method to calculate GP for a course 2019-09-07 16:46:46 -04:00
Hykilpikonna 67cf33b48c [F] Remove scaleUp if score is 0 2019-09-07 16:44:34 -04:00
Hykilpikonna 4c822fd207 [+] Add borders to navigation 2019-09-07 16:32:05 -04:00
Hykilpikonna 68afbb8c76 [O] Optimize accuracy detection 2019-09-07 16:14:42 -04:00
Hykilpikonna 5da0e89e08 [+] Return boolean indicating accurate or not when calculating gpa 2019-09-07 16:12:32 -04:00
Hykilpikonna b2db05d5e2 [O] Remove course that does not have level 2019-09-07 16:04:50 -04:00
Hykilpikonna 3cb74083a7 [+] Create method to calculate GPA 2019-09-07 16:03:57 -04:00
Hykilpikonna d3072ccaf6 [+] Create keyword constants in GPAUtils 2019-09-07 16:03:34 -04:00
Hykilpikonna d566b53c22 [+] Create letterGrade and numericGrade fields 2019-09-07 15:49:11 -04:00
Hykilpikonna 7bad961f70 [+] Add field level and scaleUp 2019-09-07 14:57:37 -04:00
Hykilpikonna 13e307f8d2 [+] Create GPA scale 2019-09-07 14:52:54 -04:00
Hykilpikonna 448e699cd3 [O] Optimize login http fetch 2019-09-07 14:42:22 -04:00
Hykilpikonna 1ca32b5ebd [O] Optimize loadAssignments() 2019-09-07 14:07:49 -04:00
Hykilpikonna 5ac3183ec1 [F] Fix body isn't in json format problem 2019-09-07 14:04:59 -04:00
Hykilpikonna a7384753c8 [O] Optimize loadCourses request 2019-09-07 14:04:38 -04:00
Hykilpikonna 393fc1cc71 [+] Create http field in App.ts 2019-09-07 14:04:23 -04:00
Hykilpikonna 06c265159b [F] Fix constructor 2019-09-07 13:59:05 -04:00
Hykilpikonna 53a0884d0b [+] Create http-utils class 2019-09-07 13:56:43 -04:00
Hykilpikonna 7b0f11a1f4 [F] Fix assignments null 2019-09-07 13:01:05 -04:00
Hykilpikonna 4dc5966e51 [+] Load assignments 2019-09-07 12:30:27 -04:00
Hykilpikonna be0a657ba3 [+] Load courses 2019-09-07 12:30:19 -04:00
Hykilpikonna 3287c14fa3 [+] Store token in a field 2019-09-07 12:15:13 -04:00
Hykilpikonna 7af20f806b [O] Move cookies detection to login class 2019-09-07 12:14:57 -04:00
Hykilpikonna bd8d7fd113 [U] Update keywords (from "token" to "data") 2019-09-07 11:48:28 -04:00
Hykilpikonna 76cf8c4c53 [+] Check cookies on initt 2019-09-07 10:11:31 -04:00
Hykilpikonna eaa1609b77 [+] Load data after login 2019-09-07 10:11:18 -04:00
Hykilpikonna 1324afe978 [+] Create method to load data after login 2019-09-07 10:11:08 -04:00
Hykilpikonna e86d2fd4f5 [O] Create a better ascii art 2019-09-07 10:08:49 -04:00
Hykilpikonna cf34db2c61 [+] Show splash on create 2019-09-07 10:07:04 -04:00
Hykilpikonna 9038a73678 [+] Save cookies on login 2019-09-07 10:06:19 -04:00
Hykilpikonna 446ed686bd [+] Create splash ascii art 2019-09-07 10:05:53 -04:00
Hykilpikonna 4782870d94 [+] Add vue-cookies to Vue.use() 2019-09-07 09:21:57 -04:00
Hykilpikonna 3ce21623d8 [+] Import vue-cookies 2019-09-07 09:21:29 -04:00
Hykilpikonna 2338e4f6af [+] Show error message on error 2019-09-07 09:14:13 -04:00
Hykilpikonna 38089c74b5 [F] Fix duplicate "/api/" in path 2019-09-07 09:13:11 -04:00
Hykilpikonna 8860c88b1a [F] Fix HTTP GET can't have body 2019-09-07 09:09:50 -04:00
Hykilpikonna 75f9dc9849 [U] Remove testing error text 2019-09-07 08:31:32 -04:00
Hykilpikonna 805ffaa50e [+] Check the success flag in response 2019-09-07 08:28:54 -04:00
Hykilpikonna c7d16a00e6 [+] Send username and password to api in body 2019-09-07 08:28:24 -04:00
Hykilpikonna 67ec2b85b2 [+] Create error message div 2019-09-07 08:24:16 -04:00
Hykilpikonna 9a752305e3 [O] Fix error message styling 2019-09-07 08:23:34 -04:00
Hykilpikonna c95a5b343e Merge branch 'feature' into styling 2019-09-07 08:10:10 -04:00
Hykilpikonna 5029555c21 [U] Update api url 2019-09-07 08:02:50 -04:00
Hykilpikonna 8eb2080f14 [+] Add error class on error 2019-09-07 08:02:22 -04:00
Hykilpikonna 0296b2151a [+] Create error message field 2019-09-07 08:01:49 -04:00
Hykilpikonna 3575db8182 [+] Create css class for input error 2019-09-07 08:01:36 -04:00
24 changed files with 561 additions and 63 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 HyDEV
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+39 -12
View File
@@ -1,6 +1,38 @@
# veracross-analyzer
<h1 align="center"><br><br>
VeracrossAnalyzer UI
</h1>
## Project setup
<h4 align="center">
A Website, A Visual Representation of Students' Grade Data on Veracross
</h4>
<h5 align="center">
<a href="#intro">Introduction</a>&nbsp;&nbsp;
<a href="#setup">Project Setup</a>&nbsp;&nbsp;
<a href="#license">License</a>
</h5><br><br><br>
<a name="intro"></a>
Introduction:
--------
This is a website that generates visual representation of students' grade data on Veracross. Currently there is only one graph and one numerical data representing the GPA. But also it just released yesterday! (Yay!) What do you expect this soon lol?
**Here's how it looks like right now:** *(Now all of you know my grades ;-;)*
![](https://i.imgur.com/xl3Q4Nt.jpg)
<br>
<a name="setup"></a>
Project Setup:
--------
TODO: Actually write a project setup tutorial that's not generated by Vue on initialization ;-;.
### Install
```
npm install
```
@@ -15,15 +47,10 @@ npm run serve
npm run build
```
### Run your tests
```
npm run test
```
<br>
### Lints and fixes files
```
npm run lint
```
<a name="license"></a>
License: [MIT](https://choosealicense.com/licenses/mit/)
--------
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
The MIT license basically means that this project is open-soucred and you can do whatever you want with it, as long as you include a copy of this license in your distribution. You don't have to ask for permissions to use or anything. However, if you do bad things with it, I'm not responsible.
+5
View File
@@ -10848,6 +10848,11 @@
"resolved": "https://registry.npm.taobao.org/vue-class-component/download/vue-class-component-7.1.0.tgz",
"integrity": "sha1-sz78sQ4XI21oT3Cx6W8ZRux5Poc="
},
"vue-cookies": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.5.13.tgz",
"integrity": "sha512-8pjpXnvbNWx1Lft0t3MJnW+ylv0Wa2Tb6Ch617u/pQah3+SfXUZZdkh3EL3bSpe/Sw2Wdw3+DhycgQsKANSxXg=="
},
"vue-hot-reload-api": {
"version": "2.3.3",
"resolved": "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.3.tgz",
+1
View File
@@ -15,6 +15,7 @@
"v-charts": "^1.19.0",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-cookies": "^1.5.13",
"vue-property-decorator": "^8.1.0"
},
"devDependencies": {
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# abort on errors
set -e
# build
npm run build
# navigate into the build output directory
cd dist
# if you are deploying to a custom domain
echo 'vera.hydev.org' > CNAME
git init
git add -A
git commit -m 'deploy'
# if you are deploying to https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:HyDevelop/VeracrossAnalyzer.Client.git master:gh-pages
cd -
+90 -16
View File
@@ -5,6 +5,7 @@ import Overall from '@/pages/overall/overall';
import Constants from '@/constants';
import JsonUtils from '@/utils/json-utils';
import pWaitFor from 'p-wait-for';
import {HttpUtils} from '@/utils/http-utils';
/**
* Objects of this interface represent assignment grades.
@@ -31,6 +32,13 @@ export interface Course
id: number,
name: string,
teacherName: string,
status: string,
letterGrade?: string,
numericGrade?: number,
level: string,
scaleUp: number,
assignments: Grade[]
}
@@ -47,44 +55,98 @@ export default class App extends Vue
public courses: Course[] = [];
// Currently selected tab
public selectedTab: string = "overall";
public selectedTab: string = 'overall';
// Are the course assignments loaded from the server.
public assignmentsReady: boolean = false;
// Token
public token: string = '';
// Http Client
public http: HttpUtils = new HttpUtils('');
/**
* This is called when the instance is created.
*/
public created()
{
// Show splash
console.log(Constants.SPLASH);
}
/**
* This is called when the user logs in.
*
* @param courses Courses Json
* @param token Authorization token
*/
public onLogin(courses: Course[])
public onLogin(token: string)
{
// Hide login bar
this.showLogin = false;
// Assign courses
this.courses = courses;
// Store token
this.token = token;
// Debug output TODO: Remove this
console.log(courses);
// Assign token to http client
this.http.token = token;
// Load data
this.loadCoursesAfterLogin();
}
/**
* Load courses data after login.
*/
public loadCoursesAfterLogin()
{
this.http.post('/courses', {}).then(response =>
{
// Check success
if (response.success)
{
// Save courses
this.courses = response.data;
// Load assignments
this.loadAssignments();
}
else
{
// Show error message TODO: Show it properly
alert(response.data);
}
})
.catch(alert);
}
/**
* Load the assignments of the courses
*
* @param courses Courses Json
*/
public loadAssignments()
{
// Get assignments for all the courses
this.courses.forEach(course =>
{
// Send request to get assignments
fetch(`${Constants.API_URL}/veracross/assignments?id=${course.assignmentsId}`).then(res =>
this.http.post('/assignments', {id: course.assignmentsId}).then(response =>
{
// Get response body text
res.text().then(text =>
// Check success
if (response.success)
{
// Load assignments
// Parse json and filter it
course.assignments = JsonUtils.filterAssignments(JSON.parse(text));
})
course.assignments = JsonUtils.filterAssignments(response.data);
}
else
{
// Show error message TODO: Show it properly
alert(response.data);
}
})
.catch(err =>
{
alert(err);
});
.catch(alert);
});
// Wait for assignments to be ready.
@@ -123,4 +185,16 @@ export default class App extends Vue
// Update selected tab
this.selectedTab = tab;
}
/**
* Sign out
*/
public signOut()
{
// Clear all cookies
this.$cookies.keys().forEach(key => this.$cookies.remove(key));
// Refresh
window.location.reload();
}
}
+5 -2
View File
@@ -1,7 +1,10 @@
<template>
<div id="app">
<login v-if="showLogin" v-on:login:courses="onLogin"></login>
<navigation :courses="courses" v-on:navigation:select="onNavigate"></navigation>
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
<navigation :courses="courses"
v-on:sign-out="signOut()"
v-on:navigation:select="onNavigate">
</navigation>
<div id="app-content">
<overall :courses="courses" v-if="selectedTab === 'overall' && assignmentsReady"></overall>
+25
View File
@@ -72,3 +72,28 @@
width: 100%;
}
}
// Error
.input-error
{
.el-input__inner
{
color: #ff3a3a6b !important;
border-color: #ff3a3a6b !important;
background-color: #ffdddd3b !important;
}
.el-input__inner:focus
{
background-color: white !important;
}
}
// Fix error message
.el-form-item__error.custom
{
padding-top: 0;
position: relative;
top: auto;
float: left;
}
+38 -8
View File
@@ -1,5 +1,6 @@
import {Component, Vue} from 'vue-property-decorator';
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {HttpUtils} from '@/utils/http-utils';
/**
* This component handles user login, and obtains data from the server.
@@ -13,6 +14,23 @@ export default class Login extends Vue
public password: any = '';
public loading: boolean = false;
public error: String = '';
@Prop()
public http?: HttpUtils;
/**
* This is called when the instance is created.
*/
public created()
{
// Check login cookies
if (this.$cookies.isKey('va.token'))
{
// Already contains valid token / TODO: Validate
this.$emit('login:token', this.$cookies.get('va.token'));
}
}
/**
* On click, sends username and password to the server.
@@ -22,15 +40,27 @@ export default class Login extends Vue
// Make login button loading
this.loading = true;
// Fetch request TODO: Add username and password when the https server is ready.
fetch(`${Constants.API_URL}/veracross/courses`).then(res =>
// Fetch request
(<HttpUtils> this.http).post('/login', {username: this.username, password: this.password})
.then(response =>
{
// Get response body text
res.text().then(text =>
// Check success
if (response.success)
{
// Call custom event with courses info
this.$emit('login:courses', JSON.parse(text));
})
// Save token to cookies
this.$cookies.set('va.token', response.data, '7d');
// Call custom event with token
this.$emit('login:token', response.data);
}
else
{
// Show error message
this.error = response.data;
// Allow the user to retry
this.loading = false;
}
})
.catch(err =>
{
+15 -2
View File
@@ -3,9 +3,22 @@
<div class="login-vertical-center">
<div class="login-panel">
<img alt="Vue logo" src="../../assets/logo.png">
<h1>Veracross Analyzer</h1>
<el-input v-model="username" placeholder="School Username"></el-input>
<el-input v-model="password" placeholder="Veracross Password" show-password=""></el-input>
<el-input v-model="username"
placeholder="School Username"
:class="{'input-error': error !== ''}">
</el-input>
<el-input v-model="password"
placeholder="Veracross Password"
show-password=""
:class="{'input-error': error !== ''}">
</el-input>
<div class="el-form-item__error custom">{{error}}</div>
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
</div>
</div>
+12
View File
@@ -1,5 +1,17 @@
.el-menu.centered li
{
display: inline-block !important;
float: none !important;
}
// Borders
#navigation
{
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
ul
{
border-bottom-width: 0;
}
}
+9
View File
@@ -27,4 +27,13 @@ export default class Navigation extends Vue
// Call custom event
this.$emit('navigation:select', this.activeIndex);
}
/**
* This function is called when the sign out button is clicked.
*/
public signOut()
{
// Call custom event
this.$emit('sign-out');
}
}
+1
View File
@@ -11,6 +11,7 @@
:key="course.name">{{course.name}}</el-menu-item>
</el-submenu>
<el-button @click="signOut" id="sign-out-button" type="text">Sign Out</el-button>
</el-menu>
<div class="line"></div>
</div>
+8 -2
View File
@@ -5,7 +5,13 @@ export default class Constants
{
/**
* Base url for api access
* TODO: Use https for actual usage
*/
public static API_URL: string = 'http://cn2.hydev.org:24021/api';
public static API_URL: string = 'https://va.hydev.org/api';
public static SPLASH: string =
'. , ,---. | \n' +
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
' v0.1.1.0 `---\' '
}
+4
View File
@@ -3,6 +3,7 @@ import ElementUI from 'element-ui';
const VCharts = require('v-charts');
import App from './components/app/app.vue';
import VueCookies from 'vue-cookies';
Vue.config.productionTip = false;
@@ -12,6 +13,9 @@ Vue.use(ElementUI, {locale: 'en-us'});
// Use VCharts
Vue.use(VCharts);
// Use Cookies
Vue.use(VueCookies);
// Init app
new Vue({
render: (h) => h(App),
@@ -7,16 +7,15 @@ export default class GraphOverall extends Vue
{
@Prop({required: true}) chart: any;
public chartData =
private settings =
{
columns: ['日期', '访问用户', '下单用户', '下单率'],
rows: [
{ '日期': '1/1', '访问用户': 1393, '下单用户': 1093, '下单率': 0.32 },
{ '日期': '1/2', '访问用户': 3530, '下单用户': 3230, '下单率': 0.26 },
{ '日期': '1/3', '访问用户': 2923, '下单用户': 2623, '下单率': 0.76 },
{ '日期': '1/4', '访问用户': 1723, '下单用户': 1423, '下单率': 0.49 },
{ '日期': '1/5', '访问用户': 3792, '下单用户': 3492, '下单率': 0.323 },
{ '日期': '1/6', '访问用户': 4593, '下单用户': 4293, '下单率': 0.78 }
]
};
series:
{
smooth: false
},
yAxis:
{
min: 70
}
}
}
@@ -1,6 +1,7 @@
<template>
<div id="graph-overall">
<ve-line :data="chart" :extend="{series: {smooth: false}}"></ve-line>
<p>Your average score graph all time:</p>
<ve-line :data="chart" :extend="settings"></ve-line>
</div>
</template>
+29
View File
@@ -0,0 +1,29 @@
// Add some margins
.el-card
{
margin: 10px;
height: 494px;
padding: 0;
// Vertical center
display: flex;
justify-content: center;
flex-direction: column;
}
.span-gpa-header
{
font-size: 14px;
}
.span-gpa
{
font-size: 35px;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
.gpa-time
{
font-size: 14px;
}
+59 -6
View File
@@ -1,30 +1,36 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
import {Course} from '@/components/app/app';
import {GPAUtils} from '@/utils/gpa-utils';
@Component({
components: {GraphOverall}
})
export default class Overall extends Vue
{
@Prop({required: true}) courses: any;
// @ts-ignore
@Prop({required: true}) courses: Course[];
get convertCharts()
{
// Null case
if (this.courses == null) return [];
// Filter it
let courses: Course[] = this.filterCourses();
// Compute the column names
let columns = ['date'];
this.courses.forEach((course: Course) =>
courses.forEach(course =>
{
columns.push(course.name);
});
// Find the min date
let minDate: Date = new Date();
this.courses.forEach((course: Course) =>
courses.forEach(course =>
{
if (course.assignments.length == 0) return;
let date = new Date(course.assignments[course.assignments.length - 1].date);
if (date < minDate) minDate = date;
});
@@ -41,7 +47,7 @@ export default class Overall extends Vue
let courseScores: {[index: string]: any} = {};
let courseMaxScores: {[index: string]: any} = {};
let courseIndexes: {[index: string]: any} = {};
this.courses.forEach((course: Course) =>
courses.forEach(course =>
{
courseScores[course.name] = 0;
courseMaxScores[course.name] = 0;
@@ -56,15 +62,18 @@ export default class Overall extends Vue
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
// Loop through courses
this.courses.forEach((course: Course) =>
courses.forEach(course =>
{
// Reversed loop through the assignments
for (let r = courseIndexes[course.name]; r >= 0; r--)
{
let assignment = course.assignments[r];
let assignmentDate = new Date(assignment.date);
// If assignment should be displayed
if (assignment.complete != 'Complete') continue;
// Date is being looked at
let assignmentDate = new Date(assignment.date);
if (assignmentDate.getTime() == date.getTime())
{
// Record scores
@@ -95,4 +104,48 @@ export default class Overall extends Vue
rows: rows
}
}
/**
* Return a list of courses that are graphed
*/
private filterCourses(): Course[]
{
// Define result
let result: Course[] = [];
// Filter through courses
this.courses.forEach(course =>
{
// Skip future or past courses
if (course.status != 'active') return;
// Skip courses without levels
if (course.level == 'None') return;
// Skip courses without assignments
if (course.assignments.length == 0) return;
// Add it to the list
result.push(course);
});
return result;
}
/**
* This function is called to get gpa as a string.
*/
public getGPA()
{
let gpa = GPAUtils.getGPA(this.courses);
let result = '' + gpa.gpa;
/* Not accurate
if (!gpa.accurate)
{
result = `(${result})`;
}*/
return result;
}
}
+22 -2
View File
@@ -1,7 +1,27 @@
<template>
<div id="overall">
<p>这是 Overall</p>
<graph-overall :chart="convertCharts"></graph-overall>
<el-row>
<el-col :span="4">
<el-card style="margin-left: 20px">
<div style="padding: 14px;">
<span class="span-gpa-header">GPA:</span>
<br>
<span class="span-gpa">{{getGPA()}}</span>
<div class="bottom clearfix gpa-time">
<time class="time">{{ new Date().toDateString() }}</time>
<br>
<el-button type="text" class="button">Button</el-button>
</div>
</div>
</el-card>
</el-col>
<el-col :span="20">
<el-card style="margin-right: 20px">
<graph-overall :chart="convertCharts"></graph-overall>
</el-card>
</el-col>
</el-row>
<div class=""></div>
</div>
</template>
+101
View File
@@ -0,0 +1,101 @@
/**
* This is an utility class to calculate GPA.
*/
import {Course} from '@/components/app/app';
export class GPAUtils
{
// [[Min score, Letter grade, Base GPA], ...]
public static SCALE =
[
[96.5, 'A+', 4.00],
[92.5, 'A' , 3.75],
[89.5, 'A-', 3.50],
[86.5, 'B+', 3.25],
[82.5, 'B' , 3.00],
[79.5, 'B-', 2.75],
[76.5, 'C+', 2.50],
[72.5, 'C' , 2.25],
[70.5, 'C-', 2.00],
[69.5, 'D' , 1.00],
[0 , 'F' , 0.00]
];
// Keywords
public static MIN = 0;
public static LETTER = 1;
public static GPA = 2;
/**
* Calculate GPA for a list of couses
*
* @param coursesOriginal List of courses
*/
public static getGPA(coursesOriginal: Course[]): {gpa: number, accurate: boolean}
{
// Clone array
let courses: Course[] = [];
// Accurate or not
let accurate: boolean = true;
// Remove all courses that does not have a grade
coursesOriginal.forEach(course =>
{
if (course.numericGrade == null || course.numericGrade == 0)
{
accurate = false;
}
else if (course.level != 'none')
{
courses.push(course);
}
});
// If no course have grade, return -1
if (courses.length == 0)
{
return {gpa: -1, accurate: false};
}
// Count total GPA
let totalGPA = 0;
courses.forEach(course =>
{
totalGPA += this.getGP(course);
});
// Get average GPA, round to two decimal places
let gpa = Math.round(totalGPA / courses.length * 100) / 100;
// Return results
return {gpa: gpa, accurate: accurate};
}
/**
* Calculate GPA for a course
*
* @param course Course
*/
public static getGP(course: Course): number
{
// Find the GPA for this course.
for (let scale of this.SCALE)
{
// Letter grades are the same
if (scale[this.LETTER] == course.letterGrade)
{
// Get grade and add it
let grade = <number> scale[this.GPA];
// Add scaleUp if not failed.
if (grade != 0) grade += course.scaleUp;
// That's it
return grade;
}
}
return -1;
}
}
+35
View File
@@ -0,0 +1,35 @@
import Constants from '@/constants';
export class HttpUtils
{
public token: string = '';
constructor (token: string)
{
this.token = token;
}
public post(node: string, body: any): Promise<any>
{
// Add token
if (this.token != '') body['token'] = this.token;
// Create promise
return new Promise<any>((resolve, reject) =>
{
// Fetch request
fetch(`${Constants.API_URL}${node}`, {method: 'POST', body: JSON.stringify(body)}).then(res =>
{
// Get response body text
res.text().then(text =>
{
// Parse response
let response = JSON.parse(text);
resolve(response);
})
.catch(reject)
})
.catch(reject)
});
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ export default class JsonUtils
type: assignment.assignment_type,
description: assignment.assignment_description,
date: assignment._date,
complete: assignment.complete_status,
complete: assignment.completion_status,
include: assignment.include_in_calculated_grade == 1,
display: assignment.display_grade == 1,
+7
View File
@@ -0,0 +1,7 @@
module.exports =
{
devServer:
{
disableHostCheck: true,
}
};