Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86ed26afeb | |||
| 35c4147cc1 | |||
| 711e69ddfb | |||
| 90d9cac19a | |||
| 61593d7d23 | |||
| 7c41e7adcc | |||
| 0215be8681 | |||
| 1139384ab3 | |||
| 45174422e9 | |||
| 2077d784fd | |||
| fa341101a8 | |||
| 1959ac589d | |||
| 5744493ac3 | |||
| 64eb4530af | |||
| d38fdfaced | |||
| f76484f824 | |||
| ef6adb0a27 | |||
| ca80df9541 | |||
| 828907120b | |||
| 460074b18a | |||
| 104bd4498e | |||
| 97082b1e55 | |||
| c9561fba71 | |||
| 8aa913fe09 | |||
| a598521491 | |||
| 6461456c16 | |||
| b4f697bdea | |||
| 721295b9d1 | |||
| 70c6e74623 | |||
| 1348ca27ab | |||
| 116592e436 | |||
| 19284a4ddf | |||
| 35e603b850 | |||
| 882c7bb35e | |||
| 9c8ce3a7f2 | |||
| 8a6af65786 | |||
| 4633bd902c | |||
| 10fcb8a2f6 | |||
| 3ad47eddfd | |||
| 636f1474bd | |||
| ebc9fac4dc | |||
| a0e5db6ed9 | |||
| f8acd8e222 | |||
| 7b21f82024 | |||
| 240cd7ce69 | |||
| 8a4f9c6f79 | |||
| 16f5865c4c | |||
| 25c3d8e6c1 | |||
| e1584c80a5 | |||
| 89dc493edf | |||
| aa59075939 | |||
| c55beaf30d | |||
| 67f1343744 | |||
| 6b0e0fc8e8 | |||
| af41ad5f53 | |||
| 622cd524e5 | |||
| 7266cf0d80 | |||
| 47fc11ed96 | |||
| fd927991c7 | |||
| 4b5e6d9856 | |||
| 3aa5ef9c95 | |||
| ded76b774c | |||
| 1bcf2f7410 | |||
| d8cce9ae11 | |||
| b4f29559cf | |||
| a7ea833860 | |||
| 9c54033343 | |||
| 7b9dab9d78 | |||
| 7f094cbafb | |||
| c4b8542b5b | |||
| 0345d37b2c | |||
| 97818c45f5 | |||
| 1a09c7311e | |||
| b658f6f707 | |||
| 1d63e4865b | |||
| 78fbd342d9 | |||
| 141c192017 | |||
| 87e44b83f7 | |||
| 1064831379 | |||
| 1e42ef7873 | |||
| c8327f3774 | |||
| 69cd4ff050 | |||
| 027f8bfd34 | |||
| 70ede22c18 | |||
| fd74298b0b | |||
| d47bae00ff | |||
| 0c91b1b598 | |||
| 6ba789c7cf | |||
| 54b70f6a1c | |||
| ac32a02ff4 | |||
| 993578277e | |||
| 48fb8c7d4c | |||
| 0f6f27aa15 | |||
| b2947e9ef6 | |||
| c5ef8a297b | |||
| 02ca6b8d47 | |||
| 98b92b4dfb | |||
| 1c2afdfa52 | |||
| 55b4bd9173 | |||
| e422c03ca5 | |||
| 831ef16a2f | |||
| 0deb0ed719 | |||
| f3d44ceda2 | |||
| cb11db6201 | |||
| b52f3d2617 | |||
| 39cd5bf1a2 | |||
| 19f03717bc | |||
| 6d728c25ae | |||
| c8daa2a06e | |||
| 4d41e679dd | |||
| acfebf9ff3 | |||
| 5af1b1e5a1 | |||
| bc9a4f00c1 | |||
| f13f3a155c | |||
| a19edb8aa7 | |||
| 6a0500acb2 | |||
| 96af69508f | |||
| 3fbc9764e9 | |||
| a79dcb76af | |||
| 86c2fd5df7 | |||
| 07eaeef133 | |||
| d1bd0a5667 | |||
| 49dad14c84 | |||
| 50e3c345d5 | |||
| a444dbb32a | |||
| 03e212bd37 | |||
| 0325eb13d5 | |||
| bfcb80aa64 | |||
| 91a6c0c6e4 | |||
| 845cc94513 | |||
| 21ccaceb53 | |||
| b9f63171f2 | |||
| 636d92d1a0 | |||
| 199ab9e00c | |||
| 77541764d1 | |||
| 53ae74f9c4 | |||
| 5bc1d6ba48 | |||
| 831ddcf84e | |||
| 35a85d8e83 | |||
| 71b0a6e4dd | |||
| d4fbd04466 | |||
| 31b3814b1e | |||
| 1aff2b0a68 | |||
| 2567fcadbd | |||
| d9e0e9f84e | |||
| 59b31ea43f | |||
| 2c8b3e0f84 | |||
| 6910a7b5ea | |||
| cbdcfc4ca1 | |||
| feabc336c1 | |||
| b5b9f14a49 | |||
| fc93cd1248 | |||
| 6e58c634a1 | |||
| 10cca344c7 | |||
| cdf58ea3a0 | |||
| d97c80afbb | |||
| 8c7d028f5b | |||
| 19ba8ec7e6 | |||
| 6937952f3a | |||
| 485717d3ef | |||
| 3f3e46bec5 | |||
| 1494315e0c | |||
| 76eb8168ce | |||
| e4a1c4973d | |||
| b22fc41582 | |||
| 0e1264d938 | |||
| a94a6db4b7 | |||
| 18b5b7de77 | |||
| 28d4e5d871 | |||
| cc5b7b2793 | |||
| 7b4ced4e94 | |||
| 70e6debe82 | |||
| e0c4426ebb | |||
| 495e776971 | |||
| fc625c8edb | |||
| 6c1623cec5 | |||
| a9a9cf5f11 | |||
| d8f276005d | |||
| 5c9e67478f | |||
| 5e0394f11c | |||
| 774e436c9c | |||
| 8b653e3317 | |||
| 9731d4e380 | |||
| e3ca21ec29 | |||
| a89c29dc53 | |||
| 87a59416ec | |||
| 8243312179 | |||
| 3dcc1b8319 | |||
| 899ff22677 | |||
| 788f51399a | |||
| 575dbdf765 | |||
| 1659964011 | |||
| 02533f6b07 | |||
| 68a91e5eb8 | |||
| 1eb6128aac | |||
| fbd59f9ec3 | |||
| 7eb7838f66 | |||
| 6459e1c09b | |||
| 4461ef835f | |||
| 6ee207d754 | |||
| 3c0c0d5f1b | |||
| bc8fcc021d | |||
| e9a6cfe5c5 | |||
| 0e92cedf54 | |||
| a73d6b0399 | |||
| 8042765ff5 | |||
| 74a033fb5d | |||
| cc4378d905 | |||
| 0fd3696342 | |||
| 3de4b1f6e6 | |||
| 610d1d190d | |||
| c262584e2f | |||
| a526946b83 | |||
| b78c5b4f5b | |||
| 8978b7ca7b | |||
| d254706a21 |
@@ -22,7 +22,7 @@ This is a website that generates visual representation of students' grade data o
|
|||||||
|
|
||||||
**Here's how it looks like right now:** *(Now all of you know my grades ;-;)*
|
**Here's how it looks like right now:** *(Now all of you know my grades ;-;)*
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|||||||
Generated
+5
@@ -6935,6 +6935,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.24.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||||
|
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||||
|
},
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"core-js": "^2.6.5",
|
"core-js": "^2.6.5",
|
||||||
"echarts": "^4.2.1",
|
"echarts": "^4.2.1",
|
||||||
"element-ui": "^2.11.1",
|
"element-ui": "^2.11.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
"p-wait-for": "^3.1.0",
|
"p-wait-for": "^3.1.0",
|
||||||
"v-charts": "^1.19.0",
|
"v-charts": "^1.19.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
#app
|
#app
|
||||||
{
|
{
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-default
|
||||||
|
{
|
||||||
|
--unread: #ff6c00;
|
||||||
|
--main: #0c6dad;
|
||||||
|
|
||||||
|
--assignment-type-2: #3f991e;
|
||||||
|
--assignment-type-3: #ff9900;
|
||||||
|
--assignment-type-4: #b02b02;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,25 @@ import Constants from '@/constants';
|
|||||||
import JsonUtils from '@/utils/json-utils';
|
import JsonUtils from '@/utils/json-utils';
|
||||||
import pWaitFor from 'p-wait-for';
|
import pWaitFor from 'p-wait-for';
|
||||||
import {HttpUtils} from '@/utils/http-utils';
|
import {HttpUtils} from '@/utils/http-utils';
|
||||||
|
import {CourseUtils} from '@/utils/course-utils';
|
||||||
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Objects of this interface represent assignment grades.
|
* Objects of this interface represent assignment grades.
|
||||||
*/
|
*/
|
||||||
export interface Grade
|
export interface Assignment
|
||||||
{
|
{
|
||||||
|
id: number,
|
||||||
type: string,
|
type: string,
|
||||||
|
typeId: number,
|
||||||
description: string,
|
description: string,
|
||||||
date: string,
|
date: string,
|
||||||
complete: string,
|
complete: string,
|
||||||
include: boolean,
|
include: boolean,
|
||||||
display: boolean,
|
display: boolean,
|
||||||
|
|
||||||
|
unread: boolean,
|
||||||
|
|
||||||
scoreMax: number,
|
scoreMax: number,
|
||||||
score: number
|
score: number
|
||||||
}
|
}
|
||||||
@@ -40,7 +46,13 @@ export interface Course
|
|||||||
level: string,
|
level: string,
|
||||||
scaleUp: number,
|
scaleUp: number,
|
||||||
|
|
||||||
assignments: Grade[]
|
grading:
|
||||||
|
{
|
||||||
|
method: string,
|
||||||
|
weightingMap: {[index: string]: number}
|
||||||
|
}
|
||||||
|
|
||||||
|
assignments: Assignment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -54,6 +66,9 @@ export default class App extends Vue
|
|||||||
// List of course that the student takes
|
// List of course that the student takes
|
||||||
public courses: Course[] = [];
|
public courses: Course[] = [];
|
||||||
|
|
||||||
|
// List of course that should be displayed
|
||||||
|
public filteredCourses: Course[] = [];
|
||||||
|
|
||||||
// Currently selected tab
|
// Currently selected tab
|
||||||
public selectedTab: string = 'overall';
|
public selectedTab: string = 'overall';
|
||||||
|
|
||||||
@@ -122,8 +137,6 @@ export default class App extends Vue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the assignments of the courses
|
* Load the assignments of the courses
|
||||||
*
|
|
||||||
* @param courses Courses Json
|
|
||||||
*/
|
*/
|
||||||
public loadAssignments()
|
public loadAssignments()
|
||||||
{
|
{
|
||||||
@@ -152,8 +165,11 @@ export default class App extends Vue
|
|||||||
// Wait for assignments to be ready.
|
// Wait for assignments to be ready.
|
||||||
pWaitFor(() => this.isAssignmentsReady()).then(() =>
|
pWaitFor(() => this.isAssignmentsReady()).then(() =>
|
||||||
{
|
{
|
||||||
// When the assignments are ready
|
// Filter courses
|
||||||
this.assignmentsReady = true;
|
this.filteredCourses = CourseUtils.getGradedCourses(this.courses);
|
||||||
|
|
||||||
|
// Check grading algorithms
|
||||||
|
this.checkGradingAlgorithms();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +188,67 @@ export default class App extends Vue
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the courses' grading algorithms. (Total-mean or percent-type)
|
||||||
|
*/
|
||||||
|
private checkGradingAlgorithms()
|
||||||
|
{
|
||||||
|
// Loop through all the courses
|
||||||
|
for (const course of this.filteredCourses)
|
||||||
|
{
|
||||||
|
// Check if total-average grade is the same with percent-type grade
|
||||||
|
if (course.numericGrade == +GPAUtils.getTotalMeanAverage(course).toFixed(2))
|
||||||
|
{
|
||||||
|
course.grading = {method: 'TOTAL_MEAN', weightingMap: {}};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Request grading scheme for this course
|
||||||
|
this.http.post('/grading', {'assignment_id': course.assignmentsId}).then(response =>
|
||||||
|
{
|
||||||
|
// Check success
|
||||||
|
if (response.success)
|
||||||
|
{
|
||||||
|
// Add it to course
|
||||||
|
course.grading = response.data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Show error message TODO: Show it properly
|
||||||
|
alert(response.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(alert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for done
|
||||||
|
pWaitFor(() => this.isGradingReady()).then(() =>
|
||||||
|
{
|
||||||
|
// When the assignments are ready
|
||||||
|
// TODO: Display loading
|
||||||
|
this.assignmentsReady = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are grading algorithms ready or not.
|
||||||
|
*
|
||||||
|
* @returns boolean Ready or not
|
||||||
|
*/
|
||||||
|
private isGradingReady(): boolean
|
||||||
|
{
|
||||||
|
for (const course of this.filteredCourses)
|
||||||
|
{
|
||||||
|
if (course.grading == undefined)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when a navigation tab is clicked
|
* This is called when a navigation tab is clicked
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app" class="theme-default">
|
||||||
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
|
<login v-if="showLogin" v-on:login:token="onLogin" :http="http"></login>
|
||||||
<navigation :courses="courses"
|
<navigation :courses="filteredCourses"
|
||||||
v-on:sign-out="signOut()"
|
v-on:sign-out="signOut()"
|
||||||
v-on:navigation:select="onNavigate">
|
v-on:navigation:select="onNavigate">
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
||||||
<div id="app-content">
|
<div id="app-content">
|
||||||
<overall :courses="courses" v-if="selectedTab === 'overall' && assignmentsReady"></overall>
|
<overall :courses="filteredCourses"
|
||||||
|
v-if="selectedTab === 'overall' && assignmentsReady">
|
||||||
|
</overall>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -15,3 +15,36 @@
|
|||||||
border-bottom-width: 0;
|
border-bottom-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sign-out-button
|
||||||
|
{
|
||||||
|
// Float right
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
// Set width and height
|
||||||
|
height: 60px;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-title
|
||||||
|
{
|
||||||
|
// Float left
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
// Set height
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
// Center text
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// Margins
|
||||||
|
margin-left: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
// Font
|
||||||
|
font-size: 1.25rem;
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="navigation">
|
<div id="navigation">
|
||||||
<el-menu class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
|
<el-menu style="margin-bottom: 10px;"
|
||||||
|
class="centered" :default-active="activeIndex" mode="horizontal" @select="onSelect">
|
||||||
|
|
||||||
|
<!--div id="nav-title">
|
||||||
|
Veracross Analyzer
|
||||||
|
</div-->
|
||||||
|
|
||||||
<el-menu-item index="overall">Overall</el-menu-item>
|
<el-menu-item index="overall">Overall</el-menu-item>
|
||||||
|
|
||||||
|
|||||||
+33
-1
@@ -8,10 +8,42 @@ export default class Constants
|
|||||||
*/
|
*/
|
||||||
public static API_URL: string = 'https://va.hydev.org/api';
|
public static API_URL: string = 'https://va.hydev.org/api';
|
||||||
|
|
||||||
|
public static VERSION: string = '0.3.2.452';
|
||||||
|
|
||||||
|
public static GITHUB: string = 'https://github.com/HyDevelop/VeracrossAnalyzer.Client';
|
||||||
|
|
||||||
public static SPLASH: string =
|
public static SPLASH: string =
|
||||||
'. , ,---. | \n' +
|
'. , ,---. | \n' +
|
||||||
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
|
'| |. , |---|,---.,---.| , .,---,,---.,---.\n' +
|
||||||
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
|
' \\ / >< | || |,---|| | | .-\' |---\'| \n' +
|
||||||
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
|
' `\' \' ` ` \'` \'`---^`---\'`---|\'---\'`---\'` \n' +
|
||||||
' v0.1.2.236 `---\' '
|
' `---\' \n' +
|
||||||
|
` Version v${Constants.VERSION} by Hykilpikonna (YGui21)\n` +
|
||||||
|
` Github: ${Constants.GITHUB}`;
|
||||||
|
|
||||||
|
// Graph Theme
|
||||||
|
public static THEME =
|
||||||
|
{
|
||||||
|
// Colors
|
||||||
|
colors:
|
||||||
|
[
|
||||||
|
'#18cea5',
|
||||||
|
'#4fa8ed',
|
||||||
|
'#f9627b',
|
||||||
|
'#ffb075',
|
||||||
|
'#005c9c',
|
||||||
|
'#bcabe0',
|
||||||
|
'#d36e75',
|
||||||
|
'#fc97af',
|
||||||
|
'#919e8b',
|
||||||
|
'#d7ab82',
|
||||||
|
'#6e7074',
|
||||||
|
'#61a0a8',
|
||||||
|
'#efa18d',
|
||||||
|
'#787464',
|
||||||
|
'#cc7e63',
|
||||||
|
'#724e58',
|
||||||
|
'#4b565b'
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
|
||||||
import {Course} from '@/components/app/app';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
})
|
|
||||||
export default class GraphOverall extends Vue
|
|
||||||
{
|
|
||||||
@Prop({required: true}) chart: any;
|
|
||||||
|
|
||||||
private settings =
|
|
||||||
{
|
|
||||||
series:
|
|
||||||
{
|
|
||||||
smooth: false
|
|
||||||
},
|
|
||||||
yAxis:
|
|
||||||
{
|
|
||||||
min: 70
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="graph-overall">
|
|
||||||
<p>Your average score graph all time:</p>
|
|
||||||
<ve-line :data="chart" :extend="settings"></ve-line>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./graph-overall.ts" lang="ts"></script>
|
|
||||||
<style src="./graph-overall.scss" lang="scss"></style>
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#overall-bar
|
||||||
|
{
|
||||||
|
.graph
|
||||||
|
{
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Course} from '@/components/app/app';
|
||||||
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
import Constants from '@/constants';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
})
|
||||||
|
export default class OverallBar extends Vue
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate settings
|
||||||
|
*/
|
||||||
|
get chartSettings()
|
||||||
|
{
|
||||||
|
let settings =
|
||||||
|
{
|
||||||
|
// Title
|
||||||
|
title:
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
textStyle:
|
||||||
|
{
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
text: 'Course GPA',
|
||||||
|
subtext: 'Current GPA for every course',
|
||||||
|
x: 'center'
|
||||||
|
},
|
||||||
|
|
||||||
|
// X axis represents course names
|
||||||
|
xAxis:
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
inside: false,
|
||||||
|
rotate: 90,
|
||||||
|
|
||||||
|
// Truncate text length
|
||||||
|
formatter: (value: string) => value.length <= 16 ? value : value.substr(0, 14) + '...'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Y axis represents GPAs and MaxGPAs
|
||||||
|
yAxis:
|
||||||
|
{
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Data
|
||||||
|
series:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barGap: '-100%',
|
||||||
|
data: this.courses.map(course =>
|
||||||
|
{
|
||||||
|
return {value: [course.name, GPAUtils.getGP(course, 'A+')],
|
||||||
|
itemStyle: {color: '#d8d8d8'}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barGap: '-100%',
|
||||||
|
data: this.generateGPData(),
|
||||||
|
|
||||||
|
label:
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
rotate: 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Disable tooltip
|
||||||
|
tooltip:
|
||||||
|
{
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate GP data for each course
|
||||||
|
*/
|
||||||
|
private generateGPData()
|
||||||
|
{
|
||||||
|
let data: any = [];
|
||||||
|
|
||||||
|
this.courses.forEach(course =>
|
||||||
|
{
|
||||||
|
data.push(
|
||||||
|
{
|
||||||
|
value: [course.name, GPAUtils.getGP(course, course.letterGrade)],
|
||||||
|
itemStyle:
|
||||||
|
{
|
||||||
|
color: Constants.THEME.colors[data.length]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div id="overall-bar">
|
||||||
|
<ve-bar height="450px" class="graph"
|
||||||
|
:extend="chartSettings"></ve-bar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./overall-bar.ts" lang="ts"></script>
|
||||||
|
<style src="./overall-bar.scss" lang="scss"></style>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Card
|
||||||
|
.el-card.course-card
|
||||||
|
{
|
||||||
|
// Margins
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
// Height limit
|
||||||
|
max-height: 250px;
|
||||||
|
|
||||||
|
// Limit name length
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
// Expansion color
|
||||||
|
background: #f9f9f9;
|
||||||
|
|
||||||
|
// Main card content
|
||||||
|
.course-card-content.main
|
||||||
|
{
|
||||||
|
padding: 0 20px 0 20px;
|
||||||
|
height: 90px;
|
||||||
|
|
||||||
|
// Main color
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion content
|
||||||
|
.course-card-content.expand.notification
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove card padding for styling issues
|
||||||
|
div.el-card.course-card > div.el-card__body
|
||||||
|
{
|
||||||
|
padding-right: 0 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-col-name
|
||||||
|
{
|
||||||
|
// Align left
|
||||||
|
text-align: left;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
.course-name
|
||||||
|
{
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-name:hover
|
||||||
|
{
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-teacher
|
||||||
|
{
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-col-grade
|
||||||
|
{
|
||||||
|
// Align right
|
||||||
|
text-align: right;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
// Adjust position
|
||||||
|
margin-top: -2px;
|
||||||
|
|
||||||
|
.course-grade
|
||||||
|
{
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-updates
|
||||||
|
{
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.unread-number
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-text
|
||||||
|
{
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-updates.unread
|
||||||
|
{
|
||||||
|
.unread-number
|
||||||
|
{
|
||||||
|
background: var(--unread);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-text
|
||||||
|
{
|
||||||
|
color: var(--unread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-updates.none
|
||||||
|
{
|
||||||
|
color: #999999;
|
||||||
|
|
||||||
|
.unread-number
|
||||||
|
{
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card__body
|
||||||
|
{
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Assignment, Course} from '@/components/app/app';
|
||||||
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
import Constants from '@/constants';
|
||||||
|
import UnreadEntry from '@/pages/overall/overall-course/unread-entry/unread-entry';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {UnreadEntry}
|
||||||
|
})
|
||||||
|
export default class OverallCourse extends Vue
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop({required: true}) course: Course;
|
||||||
|
|
||||||
|
private unread: number = -1;
|
||||||
|
private unreadAssignments: Assignment[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of unread assignments with cache
|
||||||
|
*/
|
||||||
|
countUnread(): number
|
||||||
|
{
|
||||||
|
if (this.unread == -1)
|
||||||
|
{
|
||||||
|
this.unreadAssignments = this.course.assignments.filter(a => a.unread);
|
||||||
|
return this.unread = this.unreadAssignments.length;
|
||||||
|
}
|
||||||
|
else return this.unread;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreadTable()
|
||||||
|
{
|
||||||
|
return [{date: '2019-10-04', name: 'Letter from a Penn Farmer Discussion Forum', grade: '14|14'}];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<div id="overall-course">
|
||||||
|
<el-card class="course-card">
|
||||||
|
<div class="course-card-content main vertical-center">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="6" class="course-col-name">
|
||||||
|
<div class="course-name">
|
||||||
|
{{course.name}}
|
||||||
|
</div>
|
||||||
|
<div class="course-teacher">
|
||||||
|
{{course.teacherName}}
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" class="course-col-grade">
|
||||||
|
<div class="course-grade">
|
||||||
|
<span class="letter">{{course.letterGrade}} </span>
|
||||||
|
<span class="numeric">{{course.numericGrade.toFixed(2)}}</span>
|
||||||
|
<span class="percent">%</span>
|
||||||
|
</div>
|
||||||
|
<div class="course-updates" :class="countUnread() === 0 ? 'none' : 'unread'">
|
||||||
|
<span class="unread-number">
|
||||||
|
{{countUnread()}}
|
||||||
|
</span>
|
||||||
|
<span class="unread-text">
|
||||||
|
new update{{countUnread() >= 2 ? 's' : ''}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<div class="course-card-content expand notification"
|
||||||
|
v-if="countUnread() !== 0">
|
||||||
|
<unread-entry v-for="assignment in unreadAssignments"
|
||||||
|
:assignment="assignment"
|
||||||
|
:key="assignment.id">
|
||||||
|
</unread-entry>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./overall-course.ts" lang="ts"></script>
|
||||||
|
<style src="./overall-course.scss" lang="scss"></style>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
// Row
|
||||||
|
.unread-entry
|
||||||
|
{
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 20px 0 20px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
// Date
|
||||||
|
.el-col.date
|
||||||
|
{
|
||||||
|
span.month
|
||||||
|
{
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.now
|
||||||
|
{
|
||||||
|
font-size: 11px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-col.description
|
||||||
|
{
|
||||||
|
span.type
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
background: #eee;
|
||||||
|
border-left: 2px solid #000;
|
||||||
|
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grade
|
||||||
|
.el-col.grade
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
span.percent
|
||||||
|
{
|
||||||
|
font-style: italic;
|
||||||
|
background: #ffc;
|
||||||
|
color: #555;
|
||||||
|
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
.symbol
|
||||||
|
{
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score you got
|
||||||
|
span.score
|
||||||
|
{
|
||||||
|
background: #f2f2f2;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max score
|
||||||
|
span.max
|
||||||
|
{
|
||||||
|
background: #ddd;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-box
|
||||||
|
{
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-entry:first-child
|
||||||
|
{
|
||||||
|
padding-top: 3px;
|
||||||
|
|
||||||
|
// Top shadow
|
||||||
|
// https://stackoverflow.com/questions/17572619/inset-box-shadow-only-on-one-side
|
||||||
|
box-shadow: inset 0 7px 9px -7px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Assignment, Course} from '@/components/app/app';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
})
|
||||||
|
export default class UnreadEntry extends Vue
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop({required: true}) assignment: Assignment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date to the displayed format
|
||||||
|
*
|
||||||
|
* @param date Date
|
||||||
|
*/
|
||||||
|
private getMoment(date: string)
|
||||||
|
{
|
||||||
|
return moment(new Date(date));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="unread-entry vertical-center">
|
||||||
|
<el-row class="unread-row">
|
||||||
|
<el-col :span="3" class="date">
|
||||||
|
<span class="month">{{getMoment(assignment.date).format("MMM Do")}}</span>
|
||||||
|
<span class="now">({{getMoment(assignment.date).fromNow()}})</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="15" class="description">
|
||||||
|
<span class="type entry-box"
|
||||||
|
:style="`border-color: var(--assignment-type-${assignment.typeId})`">
|
||||||
|
{{assignment.type}}
|
||||||
|
</span>
|
||||||
|
<span class="text">{{assignment.description}}</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" class="grade">
|
||||||
|
<span class="percent entry-box">
|
||||||
|
{{(assignment.score / assignment.scoreMax * 100).toFixed(1)}}
|
||||||
|
<span class="symbol">%</span>
|
||||||
|
</span>
|
||||||
|
<span class="score entry-box">{{assignment.score}}</span>
|
||||||
|
<span class="max entry-box">{{assignment.scoreMax}}</span>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./unread-entry.ts" lang="ts"></script>
|
||||||
|
<style src="./unread-entry.scss" lang="scss"></style>
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import {Course} from '@/components/app/app';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
})
|
||||||
|
export default class OverallLine extends Vue
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
|
private settings =
|
||||||
|
{
|
||||||
|
// Title
|
||||||
|
title:
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
textStyle:
|
||||||
|
{
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
text: 'Average Grade',
|
||||||
|
subtext: 'Average score trend for every course',
|
||||||
|
x: 'center'
|
||||||
|
},
|
||||||
|
// Legend
|
||||||
|
legend:
|
||||||
|
{
|
||||||
|
show: false,
|
||||||
|
//left: 'auto',
|
||||||
|
//align: 'left',
|
||||||
|
//orient: 'vertical'
|
||||||
|
textStyle:
|
||||||
|
{
|
||||||
|
fontSize: 11
|
||||||
|
},
|
||||||
|
icon: 'circle'
|
||||||
|
},
|
||||||
|
// Zoom bar
|
||||||
|
dataZoom:
|
||||||
|
[
|
||||||
|
// TODO: Calculate real value for startValue
|
||||||
|
{
|
||||||
|
startValue: '9/13/2019'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inside'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series:
|
||||||
|
{
|
||||||
|
smooth: true
|
||||||
|
},
|
||||||
|
xAxis:
|
||||||
|
{
|
||||||
|
//type: 'time'
|
||||||
|
},
|
||||||
|
yAxis:
|
||||||
|
{
|
||||||
|
min: (value: any) => value.min - 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert assignments list to a graph dataset.
|
||||||
|
*/
|
||||||
|
get convertChart()
|
||||||
|
{
|
||||||
|
let courses = this.courses;
|
||||||
|
|
||||||
|
// Compute the column names
|
||||||
|
let columns = courses.map(course => course.name);
|
||||||
|
columns.unshift('date');
|
||||||
|
|
||||||
|
// Find the min date
|
||||||
|
let minDates = courses.map(course => new Date(course.assignments[course.assignments.length - 1].date).getTime());
|
||||||
|
let minDate: Date = new Date(Math.min.apply(null, minDates));
|
||||||
|
|
||||||
|
// Find the dates in between
|
||||||
|
let now = new Date();
|
||||||
|
let dates = [];
|
||||||
|
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
|
||||||
|
{
|
||||||
|
dates.push(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the rows data
|
||||||
|
let rows: {[index: string]: any}[] = [];
|
||||||
|
dates.forEach(date =>
|
||||||
|
{
|
||||||
|
// Define row object
|
||||||
|
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
|
||||||
|
|
||||||
|
// Loop through courses
|
||||||
|
courses.forEach(course =>
|
||||||
|
{
|
||||||
|
// Total Mean
|
||||||
|
if (course.grading.method == 'TOTAL_MEAN')
|
||||||
|
{
|
||||||
|
let score = 0;
|
||||||
|
let max = 0;
|
||||||
|
|
||||||
|
// Loop through assignments
|
||||||
|
course.assignments.forEach(assignment =>
|
||||||
|
{
|
||||||
|
// If assignment should be displayed
|
||||||
|
if (assignment.complete != 'Complete') return;
|
||||||
|
|
||||||
|
// Date is being looked at
|
||||||
|
let assignmentDate = new Date(assignment.date);
|
||||||
|
if (assignmentDate.getTime() < date.getTime())
|
||||||
|
{
|
||||||
|
// Record scores
|
||||||
|
score += assignment.score;
|
||||||
|
max += assignment.scoreMax;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add average to the row
|
||||||
|
row[course.name] = score / max * 100;
|
||||||
|
}
|
||||||
|
else if (course.grading.method == 'PERCENT_TYPE')
|
||||||
|
{
|
||||||
|
let typeScores: {[index: string]: any} = {};
|
||||||
|
let typeCounts: {[index: string]: any} = {};
|
||||||
|
|
||||||
|
// Loop through assignments
|
||||||
|
course.assignments.forEach(assignment =>
|
||||||
|
{
|
||||||
|
// If assignment should be displayed
|
||||||
|
if (assignment.complete != 'Complete') return;
|
||||||
|
|
||||||
|
// Date is being looked at
|
||||||
|
let assignmentDate = new Date(assignment.date);
|
||||||
|
if (assignmentDate.getTime() < date.getTime())
|
||||||
|
{
|
||||||
|
// Record scores
|
||||||
|
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
|
||||||
|
typeScores[assignment.type] += assignment.score / assignment.scoreMax;
|
||||||
|
|
||||||
|
if (typeCounts[assignment.type] == undefined) typeCounts[assignment.type] = 0;
|
||||||
|
typeCounts[assignment.type] ++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Count total percentage (This is to avoid less than expected cases)
|
||||||
|
// Eg. If HW = 25% and Quiz = 75%, I have 1 hw and 0 quiz
|
||||||
|
// Without total percentage, the avg grade I get is 25%.
|
||||||
|
let totalPercentage = 0;
|
||||||
|
for (let type in course.grading.weightingMap)
|
||||||
|
{
|
||||||
|
if (typeScores[type] != undefined)
|
||||||
|
{
|
||||||
|
totalPercentage += course.grading.weightingMap[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count
|
||||||
|
let score = 0;
|
||||||
|
for (let type in typeScores)
|
||||||
|
{
|
||||||
|
let typeFactor = course.grading.weightingMap[type] / totalPercentage;
|
||||||
|
score += typeScores[type] * typeFactor / typeCounts[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add average to the row
|
||||||
|
if (score != 0) row[course.name] = score * 100;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add it to the array
|
||||||
|
rows.push(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns: columns,
|
||||||
|
rows: rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div id="overall-line">
|
||||||
|
<ve-line :data="convertChart" :extend="settings"></ve-line>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./overall-line.ts" lang="ts"></script>
|
||||||
|
<style src="./overall-line.scss" lang="scss"></style>
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
.el-card
|
.el-card
|
||||||
{
|
{
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
height: 494px;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Vertical center
|
.el-card.large
|
||||||
display: flex;
|
{
|
||||||
justify-content: center;
|
height: 494px;
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gpa-card
|
.gpa-card
|
||||||
@@ -46,3 +45,26 @@
|
|||||||
{
|
{
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
.el-card.overall-bar-card
|
||||||
|
{
|
||||||
|
margin-right: 20px;
|
||||||
|
min-width: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix padding
|
||||||
|
.el-card__body
|
||||||
|
{
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical centering
|
||||||
|
.vertical-center
|
||||||
|
{
|
||||||
|
// Vertical center
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,155 +1,24 @@
|
|||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
import GraphOverall from '@/pages/overall/graph-overall/graph-overall';
|
import OverallLine from '@/pages/overall/overall-line/overall-line';
|
||||||
|
import OverallBar from '@/pages/overall/overall-bar/overall-bar';
|
||||||
|
import OverallCourse from '@/pages/overall/overall-course/overall-course';
|
||||||
import {Course} from '@/components/app/app';
|
import {Course} from '@/components/app/app';
|
||||||
import {GPAUtils} from '@/utils/gpa-utils';
|
import {GPAUtils} from '@/utils/gpa-utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {GraphOverall}
|
components: {OverallLine, OverallBar, OverallCourse}
|
||||||
})
|
})
|
||||||
export default class Overall extends Vue
|
export default class Overall extends Vue
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@Prop({required: true}) courses: Course[];
|
@Prop({required: true}) courses: Course[];
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert assignments list to a graph dataset.
|
|
||||||
*/
|
|
||||||
get convertCharts()
|
|
||||||
{
|
|
||||||
// Null case
|
|
||||||
if (this.courses == null) return [];
|
|
||||||
|
|
||||||
// Filter it
|
|
||||||
let courses: Course[] = this.filterCourses();
|
|
||||||
|
|
||||||
// Compute the column names
|
|
||||||
let columns = ['date'];
|
|
||||||
courses.forEach(course =>
|
|
||||||
{
|
|
||||||
columns.push(course.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find the min date
|
|
||||||
let minDate: Date = new Date();
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find the dates in between
|
|
||||||
let now = new Date();
|
|
||||||
let dates = [];
|
|
||||||
for (let date = minDate; date <= now; date.setDate(date.getDate() + 1))
|
|
||||||
{
|
|
||||||
dates.push(new Date(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize course specific variables
|
|
||||||
let courseScores: {[index: string]: any} = {};
|
|
||||||
let courseMaxScores: {[index: string]: any} = {};
|
|
||||||
let courseIndexes: {[index: string]: any} = {};
|
|
||||||
courses.forEach(course =>
|
|
||||||
{
|
|
||||||
courseScores[course.name] = 0;
|
|
||||||
courseMaxScores[course.name] = 0;
|
|
||||||
courseIndexes[course.name] = course.assignments.length - 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compute the rows data
|
|
||||||
let rows: {[index: string]: any}[] = [];
|
|
||||||
dates.forEach(date =>
|
|
||||||
{
|
|
||||||
// Define row object
|
|
||||||
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
|
|
||||||
|
|
||||||
// Loop through courses
|
|
||||||
courses.forEach(course =>
|
|
||||||
{
|
|
||||||
// Reversed loop through the assignments
|
|
||||||
for (let r = courseIndexes[course.name]; r >= 0; r--)
|
|
||||||
{
|
|
||||||
let assignment = course.assignments[r];
|
|
||||||
|
|
||||||
// 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
|
|
||||||
courseScores[course.name] += assignment.score;
|
|
||||||
courseMaxScores[course.name] += assignment.scoreMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not now
|
|
||||||
else if (assignmentDate > date)
|
|
||||||
{
|
|
||||||
courseIndexes[course.name] = r;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add average to the row
|
|
||||||
row[course.name] = courseScores[course.name] / courseMaxScores[course.name] * 100;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add it to the array
|
|
||||||
rows.push(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(rows);
|
|
||||||
|
|
||||||
return {
|
|
||||||
columns: columns,
|
|
||||||
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 since I can't import another
|
* This function is called to get gpa since I can't import another
|
||||||
* class in the Vue file.
|
* class in the Vue file.
|
||||||
*/
|
*/
|
||||||
public getGPA()
|
public getGPA()
|
||||||
{
|
{
|
||||||
let gpa = GPAUtils.getGPA(this.courses);
|
return GPAUtils.getGPA(this.courses);
|
||||||
// let result = '' + gpa.gpa;
|
|
||||||
|
|
||||||
/* Not accurate
|
|
||||||
if (!gpa.accurate)
|
|
||||||
{
|
|
||||||
result = `(${result})`;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return gpa;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div id="overall">
|
<div id="overall">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-card class="gpa-card">
|
<el-card class="large gpa-card vertical-center">
|
||||||
<div style="padding: 14px;">
|
<div style="padding: 14px;">
|
||||||
<span class="gpa header">GPA:</span>
|
<span class="gpa header">GPA:</span>
|
||||||
<span class="gpa text">{{getGPA().gpa}}</span>
|
<span class="gpa text">{{getGPA().gpa}}</span>
|
||||||
@@ -13,13 +13,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="20">
|
<el-col :span="14">
|
||||||
<el-card style="margin-right: 20px">
|
<el-card class="large overall-line-card vertical-center">
|
||||||
<graph-overall :chart="convertCharts"></graph-overall>
|
<overall-line :courses="courses"></overall-line>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card class="large overall-bar-card vertical-center">
|
||||||
|
<overall-bar :courses="courses"></overall-bar>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<div class=""></div>
|
|
||||||
|
<overall-course v-for="course in courses"
|
||||||
|
:course="course"
|
||||||
|
:key="course.id">
|
||||||
|
</overall-course>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import {Course} from '@/components/app/app';
|
||||||
|
|
||||||
|
export class CourseUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return a list of courses that are graphed
|
||||||
|
*
|
||||||
|
* @param original Original course list
|
||||||
|
* @return Course[] Filtered course list
|
||||||
|
*/
|
||||||
|
public static getGradedCourses(original: Course[]): Course[]
|
||||||
|
{
|
||||||
|
// Define result
|
||||||
|
let result: Course[] = [];
|
||||||
|
|
||||||
|
// Filter through courses
|
||||||
|
original.forEach(course =>
|
||||||
|
{
|
||||||
|
// Skip future or past courses
|
||||||
|
if (course.status != 'active') return;
|
||||||
|
|
||||||
|
// Skip courses without levels
|
||||||
|
if (course.level == 'None') return;
|
||||||
|
|
||||||
|
// Skip courses without graded assignments
|
||||||
|
if (course.assignments.filter(a => a.complete == 'Complete').length == 0) return;
|
||||||
|
|
||||||
|
// Skip if there are no grading scale
|
||||||
|
// if (course.grading.method == 'NOT_GRADED') return;
|
||||||
|
|
||||||
|
// Add it to the list
|
||||||
|
result.push(course);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Initialize course specific variables
|
||||||
|
let courseScores: {[index: string]: any} = {};
|
||||||
|
let courseMaxScores: {[index: string]: any} = {};
|
||||||
|
let courseIndexes: {[index: string]: any} = {};
|
||||||
|
courses.forEach(course =>
|
||||||
|
{
|
||||||
|
courseScores[course.name] = 0;
|
||||||
|
courseMaxScores[course.name] = 0;
|
||||||
|
courseIndexes[course.name] = course.assignments.length - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compute the rows data
|
||||||
|
let rows: {[index: string]: any}[] = [];
|
||||||
|
dates.forEach(date =>
|
||||||
|
{
|
||||||
|
// Define row object
|
||||||
|
let row: {[index: string]:any} = {'date': date.toLocaleDateString('en-US')};
|
||||||
|
|
||||||
|
// Loop through courses
|
||||||
|
courses.forEach(course =>
|
||||||
|
{
|
||||||
|
// Reversed loop through the assignments
|
||||||
|
for (let r = courseIndexes[course.name]; r >= 0; r--)
|
||||||
|
{
|
||||||
|
let assignment = course.assignments[r];
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
{
|
||||||
|
// Detect grading method and record scores
|
||||||
|
if (course.grading.method == 'TOTAL_MEAN')
|
||||||
|
{
|
||||||
|
courseScores[course.name] += assignment.score;
|
||||||
|
courseMaxScores[course.name] += assignment.scoreMax;
|
||||||
|
}
|
||||||
|
else if (course.grading.method == 'PERCENT_TYPE')
|
||||||
|
{
|
||||||
|
let scale = course.grading.weightingMap[assignment.type];
|
||||||
|
courseScores[course.name] += assignment.score * scale;
|
||||||
|
courseMaxScores[course.name] += assignment.scoreMax * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not now
|
||||||
|
else if (assignmentDate > date)
|
||||||
|
{
|
||||||
|
courseIndexes[course.name] = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add average to the row
|
||||||
|
row[course.name] = courseScores[course.name] / courseMaxScores[course.name] * 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add it to the array
|
||||||
|
rows.push(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
else if (course.grading.method == 'PERCENT_TYPE')
|
||||||
|
{
|
||||||
|
let typeScores: {[index: string]: any} = {};
|
||||||
|
let typeCounts: {[index: string]: any} = {};
|
||||||
|
|
||||||
|
// Loop through assignments
|
||||||
|
course.assignments.forEach(assignment =>
|
||||||
|
{
|
||||||
|
// If assignment should be displayed
|
||||||
|
if (assignment.complete != 'Complete') return;
|
||||||
|
|
||||||
|
// Date is being looked at
|
||||||
|
let assignmentDate = new Date(assignment.date);
|
||||||
|
if (assignmentDate.getTime() < date.getTime())
|
||||||
|
{
|
||||||
|
// Record scores
|
||||||
|
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
|
||||||
|
typeScores[assignment.type] += assignment.score / assignment.scoreMax;
|
||||||
|
|
||||||
|
if (typeCounts[assignment.type] == undefined) typeCounts[assignment.type] = 0;
|
||||||
|
typeCounts[assignment.type] ++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
// Count
|
||||||
|
for (let type in typeScores)
|
||||||
|
{
|
||||||
|
score += typeScores[type] * course.grading.weightingMap[type] / typeCounts[type];
|
||||||
|
console.log(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add average to the row
|
||||||
|
row[course.name] = score * 100;
|
||||||
|
}
|
||||||
@@ -102,4 +102,29 @@ export class GPAUtils
|
|||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the total-mean (total/max) average
|
||||||
|
*
|
||||||
|
* @param course Course
|
||||||
|
*/
|
||||||
|
public static getTotalMeanAverage(course: Course)
|
||||||
|
{
|
||||||
|
let score = 0;
|
||||||
|
let max = 0;
|
||||||
|
|
||||||
|
// Loop through assignments
|
||||||
|
course.assignments.forEach(assignment =>
|
||||||
|
{
|
||||||
|
// If assignment should be displayed
|
||||||
|
if (assignment.complete != 'Complete') return;
|
||||||
|
|
||||||
|
// Record scores
|
||||||
|
score += assignment.score;
|
||||||
|
max += assignment.scoreMax;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return score / max * 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Grade} from '@/components/app/app';
|
import {Assignment} from '@/components/app/app';
|
||||||
|
|
||||||
export default class JsonUtils
|
export default class JsonUtils
|
||||||
{
|
{
|
||||||
@@ -6,23 +6,27 @@ export default class JsonUtils
|
|||||||
* This method filters the information provided in an assignments json.
|
* This method filters the information provided in an assignments json.
|
||||||
*
|
*
|
||||||
* @param assignments Assignments object
|
* @param assignments Assignments object
|
||||||
* @returns Grade[] Filtered assignment grade object list
|
* @returns Assignment[] Filtered assignment grade object list
|
||||||
*/
|
*/
|
||||||
public static filterAssignments(assignments: any): Grade[]
|
public static filterAssignments(assignments: any): Assignment[]
|
||||||
{
|
{
|
||||||
let result: Grade[] = [];
|
let result: Assignment[] = [];
|
||||||
|
|
||||||
assignments.assignments.forEach((assignment: any) =>
|
assignments.assignments.forEach((assignment: any) =>
|
||||||
{
|
{
|
||||||
result.push(
|
result.push(
|
||||||
{
|
{
|
||||||
|
id: assignment.assignment_id,
|
||||||
type: assignment.assignment_type,
|
type: assignment.assignment_type,
|
||||||
|
typeId: assignment.assignment_type_id,
|
||||||
description: assignment.assignment_description,
|
description: assignment.assignment_description,
|
||||||
date: assignment._date,
|
date: assignment._date,
|
||||||
complete: assignment.completion_status,
|
complete: assignment.completion_status,
|
||||||
include: assignment.include_in_calculated_grade == 1,
|
include: assignment.include_in_calculated_grade == 1,
|
||||||
display: assignment.display_grade == 1,
|
display: assignment.display_grade == 1,
|
||||||
|
|
||||||
|
unread: assignment.is_unread == 1,
|
||||||
|
|
||||||
scoreMax: assignment.maximum_score,
|
scoreMax: assignment.maximum_score,
|
||||||
score: +assignment.raw_score
|
score: +assignment.raw_score
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user