Compare commits

..

92 Commits

Author SHA1 Message Date
Hykilpikonna fa3fc44526 [U] Update version number 2019-09-23 19:41:49 -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
21 changed files with 477 additions and 50 deletions
+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": {
+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 -1
View File
@@ -7,5 +7,12 @@ 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,
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
devServer: {
disableHostCheck: true,
}
}