Compare commits
36 Commits
master
...
frontend-r
Author | SHA1 | Date | |
---|---|---|---|
3450ce6f87 | |||
9fd0055f36 | |||
bc88c632be | |||
4131213723 | |||
33506a4d9e | |||
933e0e7c0c | |||
b298ac5815 | |||
49260a8829 | |||
af7edf2b45 | |||
5613b2a61e | |||
651698d833 | |||
420ab2d278 | |||
5b3ae837dc | |||
80d16b15d8 | |||
0cb3cbe2d3 | |||
ee1f4c5222 | |||
5748cc8410 | |||
24240164ad | |||
bd9eae2dff | |||
8c62028cc4 | |||
a69489b53f | |||
6a1f3b84cd | |||
1f8e8069cb | |||
417ab3da50 | |||
a73740f400 | |||
695df651e1 | |||
cfad59d139 | |||
6f9e9141b7 | |||
3321f4a58a | |||
ebd5493e46 | |||
286d1c7fda | |||
e13d157dd6 | |||
33a0b0840b | |||
a0a4e1ca97 | |||
b6645e148e | |||
24a7907d26 |
6
android/.idea/compiler.xml
generated
Normal file
6
android/.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="1.8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
2
android/.idea/misc.xml
generated
2
android/.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
10
frontend/.editorconfig
Normal file
10
frontend/.editorconfig
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 120
|
||||||
|
tab_width = 4
|
4124
frontend/package-lock.json
generated
4124
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "Geo_Timetracking",
|
"name": "geo-timetracking",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -8,27 +8,28 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apexcharts": "^3.19.0",
|
"apexcharts": "^3.20.2",
|
||||||
"core-js": "^3.6.4",
|
"axios": "^0.19.2",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"sass-loader": "^8.0.2",
|
"vue": "^2.6.12",
|
||||||
"vue": "^2.6.11",
|
"vue-apexcharts": "^1.6.0",
|
||||||
"vue-apexcharts": "^1.5.3",
|
"vue-router": "^3.4.3",
|
||||||
"vue-router": "^3.1.6",
|
"vuetify": "^2.3.10"
|
||||||
"vuetify": "^2.2.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.3.0",
|
"@vue/cli-plugin-babel": "^4.5.6",
|
||||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
"@vue/cli-plugin-eslint": "^4.5.6",
|
||||||
"@vue/cli-service": "~4.3.0",
|
"@vue/cli-plugin-router": "^4.5.6",
|
||||||
|
"@vue/cli-service": "^4.5.6",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"sass": "^1.19.0",
|
"sass": "^1.26.11",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^8.0.0",
|
||||||
"vue-cli-plugin-vuetify": "~2.0.5",
|
"vue-cli-plugin-vuetify": "^2.0.7",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.12",
|
||||||
"vuetify-loader": "^1.3.0"
|
"vuetify-loader": "^1.6.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<v-app id="geotimetracking">
|
<v-app id="geotimetracking">
|
||||||
<!-- Side navigation menu -->
|
<!-- Side navigation menu -->
|
||||||
<v-navigation-drawer v-model="drawer" app clipped class="main_accent">
|
<v-navigation-drawer v-model="sideNavDrawer" app clipped class="main_accent">
|
||||||
<v-list dense>
|
<v-list dense>
|
||||||
<v-list-item link to="/">
|
<v-list-item link to="/">
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<v-list-item-title>Home</v-list-item-title>
|
<v-list-item-title>Home</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item link to="/timerecords" v-bind:class="{'d-none':loggedIn=='false'}">
|
<v-list-item link to="/timerecords" v-if="loggedIn">
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-icon>mdi-clock</v-icon>
|
<v-icon>mdi-clock</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<v-list-item-title>Time Records</v-list-item-title>
|
<v-list-item-title>Time Records</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item link to="/statistics" v-bind:class="{'d-none':loggedIn=='false'}">
|
<v-list-item link to="/statistics" v-if="loggedIn">
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-icon>mdi-chart-bar</v-icon>
|
<v-icon>mdi-chart-bar</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<v-list-item-title>Statistics</v-list-item-title>
|
<v-list-item-title>Statistics</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item @click="toAccounts" v-bind:class="{'d-none':loggedIn=='false'}">
|
<v-list-item @click="toAccounts" v-if="loggedIn">
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-icon>mdi-account-details</v-icon>
|
<v-icon>mdi-account-details</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<v-list-item-title>About</v-list-item-title>
|
<v-list-item-title>About</v-list-item-title>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item link to="/users" v-bind:class="{'d-none':loggedIn=='false'}">
|
<v-list-item link to="/users" v-if="loggedIn">
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-icon>mdi-account-group</v-icon>
|
<v-icon>mdi-account-group</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
@ -57,18 +57,19 @@
|
|||||||
|
|
||||||
<!-- Top menu bar -->
|
<!-- Top menu bar -->
|
||||||
<v-app-bar app clipped-left class="main" elevation="10">
|
<v-app-bar app clipped-left class="main" elevation="10">
|
||||||
<v-app-bar-nav-icon @click.stop="drawer = !drawer" />
|
<v-app-bar-nav-icon @click.stop="sideNavDrawer = !sideNavDrawer"/>
|
||||||
<v-img @click="forward" src="./assets/logo.svg" max-height="100%" max-width="100" contain></v-img>
|
<v-img style="cursor: pointer" @click="handleLogoClick" src="./assets/logo.svg" max-height="100%"
|
||||||
|
max-width="100" contain></v-img>
|
||||||
<v-toolbar-title>Geo Timetracking</v-toolbar-title>
|
<v-toolbar-title>Geo Timetracking</v-toolbar-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<!-- Menu with account icon -->
|
<!-- Menu with account icon -->
|
||||||
<v-menu
|
<v-menu
|
||||||
v-model="menu"
|
v-model="userInfoDialog"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-width="200"
|
:nudge-width="200"
|
||||||
offset-y
|
offset-y
|
||||||
v-if="loggedIn == 'true'"
|
v-if="loggedIn"
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn icon v-on="on">
|
<v-btn icon v-on="on">
|
||||||
@ -80,7 +81,7 @@
|
|||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-avatar>
|
<v-list-item-avatar>
|
||||||
<img src="https://cdn.vuetifyjs.com/images/john.jpg" alt="John" />
|
<img src="https://cdn.vuetifyjs.com/images/john.jpg" alt="John"/>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
|
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@ -93,32 +94,29 @@
|
|||||||
|
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn text @click="userInfoDialog = false">Cancel</v-btn>
|
||||||
<v-btn text @click="menu = false">Cancel</v-btn>
|
|
||||||
<v-btn color="primary" text @click="logout">Logout</v-btn>
|
<v-btn color="primary" text @click="logout">Logout</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-card v-if="loggedIn == 'false'">
|
<v-card v-if="!loggedIn">
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
|
|
||||||
<v-row justify="center">
|
<v-row justify="center">
|
||||||
<v-dialog v-model="dialog" width="70%" persistent>
|
<v-dialog v-model="loginDialog" width="70%" persistent>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn color="primary" dark v-on="on">Login</v-btn>
|
<v-btn color="primary" dark v-on="on">Login</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card class="main">
|
<v-card class="main">
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn icon @click="dialog = false">
|
<v-btn icon @click="loginDialog = false">
|
||||||
<v-icon>mdi-window-close</v-icon>
|
<v-icon>mdi-window-close</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
<SignIn v-on:signIn="signIn" v-on:signUp="signUp" />
|
<SignIn v-on:signIn="signIn" v-on:signUp="signUp"/>
|
||||||
<p id="loginError"></p>
|
<p>{{loginError}}</p>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</v-row>
|
</v-row>
|
||||||
@ -126,9 +124,9 @@
|
|||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
|
|
||||||
<!-- Routed pages are inserted here -->
|
<!-- Routed pages are inserted here -->
|
||||||
<v-content>
|
<v-main>
|
||||||
<router-view />
|
<router-view/>
|
||||||
</v-content>
|
</v-main>
|
||||||
|
|
||||||
<!-- Footer on bottom -->
|
<!-- Footer on bottom -->
|
||||||
<v-footer app class="main">
|
<v-footer app class="main">
|
||||||
@ -139,171 +137,94 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SignIn from "./views/SignIn.vue";
|
import SignIn from "./views/SignIn.vue";
|
||||||
import { baseUri } from "./variables.js";
|
import {BASE_URI} from "./globals.js";
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
if (!sessionStorage.getItem("loggedin")) {
|
export default {
|
||||||
sessionStorage.setItem("loggedin", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
components: {
|
||||||
SignIn
|
SignIn
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
source: String
|
source: String
|
||||||
},
|
},
|
||||||
data: () => ({
|
data() {
|
||||||
drawer: null,
|
return {
|
||||||
dialog: false,
|
sideNavDrawer: false,
|
||||||
menu: false,
|
loginDialog: false,
|
||||||
loggedIn: sessionStorage.getItem("loggedin"),
|
userInfoDialog: false,
|
||||||
fullname:
|
loggedIn: false,
|
||||||
sessionStorage.getItem("firstname") +
|
fullname: "",
|
||||||
" " +
|
loginError: ""
|
||||||
sessionStorage.getItem("lastname")
|
}
|
||||||
}),
|
},
|
||||||
methods: {
|
methods: {
|
||||||
forward() {
|
handleLogoClick() {
|
||||||
this.$router.push("/");
|
if (this.$router.currentRoute.path !== "/") {
|
||||||
},
|
this.$router.replace("/")
|
||||||
signIn(loginData) {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
sessionStorage.setItem(
|
|
||||||
"jwt",
|
|
||||||
this.getResponseHeader("Authorization")
|
|
||||||
);
|
|
||||||
sessionStorage.setItem("loggedin", true);
|
|
||||||
} else if (this.status != 200 && this.status != 0) {
|
|
||||||
document.getElementById("loginError").innerHTML =
|
|
||||||
"Login not successfull";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open("POST", baseUri + "/login", false);
|
|
||||||
xhttp.send(
|
|
||||||
'{"username": "' +
|
|
||||||
loginData.username +
|
|
||||||
'", "password": "' +
|
|
||||||
loginData.password +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if (sessionStorage.getItem("loggedin") == "true") {
|
|
||||||
sessionStorage.setItem("haveData", true);
|
|
||||||
var whoxhttp = new XMLHttpRequest();
|
|
||||||
whoxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var userInformation = JSON.parse(whoxhttp.responseText);
|
|
||||||
sessionStorage.setItem("firstname", userInformation.firstname);
|
|
||||||
sessionStorage.setItem("lastname", userInformation.lastname);
|
|
||||||
sessionStorage.setItem("username", userInformation.username);
|
|
||||||
sessionStorage.setItem("userIDOwn", userInformation.id);
|
|
||||||
|
|
||||||
this.fullname =
|
|
||||||
sessionStorage.getItem("firstname") +
|
|
||||||
" " +
|
|
||||||
sessionStorage.getItem("lastname");
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
whoxhttp.open("GET", baseUri + "/whoami", false);
|
|
||||||
|
|
||||||
whoxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
whoxhttp.send(null);
|
|
||||||
location.reload();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
signUp(signupData) {
|
async signIn(loginData) {
|
||||||
var xhttp = new XMLHttpRequest();
|
try {
|
||||||
|
let loginResponse = await axios.post(BASE_URI + "/login", loginData)
|
||||||
|
sessionStorage.setItem("jwt", loginResponse.headers.authorization)
|
||||||
|
sessionStorage.setItem("loggedin", JSON.stringify(true))
|
||||||
|
this.loggedIn = true
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
let whoAmIResponse = await axios.get(BASE_URI + "/whoami")
|
||||||
if ((this.status == 201) & (this.readyState == 4)) {
|
sessionStorage.setItem("firstname", whoAmIResponse.data.firstname)
|
||||||
location.reload();
|
sessionStorage.setItem("lastname", whoAmIResponse.data.lastname)
|
||||||
} else if (this.status != 201 && this.status != 0) {
|
sessionStorage.setItem("username", whoAmIResponse.data.username)
|
||||||
document.getElementById("loginError").innerHTML =
|
sessionStorage.setItem("userIDOwn", whoAmIResponse.data.id)
|
||||||
"The username already exist";
|
this.fullname = `${whoAmIResponse.data.firstname} ${whoAmIResponse.data.lastname}`
|
||||||
|
} catch (e) {
|
||||||
|
this.loginError = "Login not successful"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async signUp(signupData) {
|
||||||
|
try {
|
||||||
|
await axios.post(BASE_URI + "/sign-up", signupData)
|
||||||
|
await this.signIn({username: signupData.username, password: signupData.password})
|
||||||
|
} catch (e) {
|
||||||
|
this.loginError = "Sign-up not successful"
|
||||||
}
|
}
|
||||||
};
|
|
||||||
xhttp.open("POST", baseUri + "/sign-up", true);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"firstname": "' +
|
|
||||||
signupData.firstname +
|
|
||||||
'", "lastname": "' +
|
|
||||||
signupData.lastname +
|
|
||||||
'", "username": "' +
|
|
||||||
signupData.username +
|
|
||||||
'", "password": "' +
|
|
||||||
signupData.password +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
logout() {
|
logout() {
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
sessionStorage.setItem("loggedin", false);
|
|
||||||
location.reload();
|
location.reload();
|
||||||
},
|
},
|
||||||
toAccounts() {
|
async toAccounts() {
|
||||||
sessionStorage.setItem(
|
let usersResponse = await axios.get(BASE_URI + "/users/search/byUsername", {
|
||||||
"timeTrackAccountListUser",
|
params: {
|
||||||
sessionStorage.getItem("username")
|
username: sessionStorage.getItem("username")
|
||||||
);
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var usersInformation = JSON.parse(xhttp.responseText);
|
|
||||||
sessionStorage.setItem(
|
|
||||||
"timeTrackAccountListUserId",
|
|
||||||
usersInformation._links.self.href
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
xhttp.open(
|
sessionStorage.setItem("timeTrackAccountListUser", sessionStorage.getItem("username"))
|
||||||
"GET",
|
sessionStorage.setItem("timeTrackAccountListUserId", usersResponse.data._links.self.href)
|
||||||
baseUri +
|
|
||||||
"/users/search/byUsername?username=" +
|
|
||||||
sessionStorage.getItem("username"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
this.users = JSON.parse(sessionStorage.getItem("users"));
|
|
||||||
|
|
||||||
if (this.$route.path == "/timetrackaccounts" ) {
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.$router.currentRoute.path === "/timetrackaccounts") {
|
||||||
location.reload();
|
location.reload();
|
||||||
|
} else {
|
||||||
}else{
|
await this.$router.push("/timetrackaccounts");
|
||||||
this.$router.push("/timetrackaccounts");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$vuetify.theme.dark = true;
|
this.$vuetify.theme.dark = true;
|
||||||
|
this.loggedIn = JSON.parse(sessionStorage.getItem("loggedin"))
|
||||||
|
this.fullname = sessionStorage.getItem("firstname") + " " + sessionStorage.getItem("lastname")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.link {
|
.link {
|
||||||
color: #f1f1f1f1;
|
color: #f1f1f1f1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.v-application {
|
|
||||||
|
.v-application {
|
||||||
font-family: "Montserrat", sans-serif;
|
font-family: "Montserrat", sans-serif;
|
||||||
background-color: var(--v-background-base) !important;
|
background-color: var(--v-background-base) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
1
frontend/src/globals.js
Normal file
1
frontend/src/globals.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const BASE_URI = 'http://plesk.icaotix.de:5000'
|
@ -2,6 +2,16 @@ import Vue from "vue";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import vuetify from './plugins/vuetify';
|
import vuetify from './plugins/vuetify';
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
// JWT will be injected automatically when available
|
||||||
|
axios.interceptors.request.use(config => {
|
||||||
|
let token = sessionStorage.getItem("jwt")
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ export default new Vuetify({
|
|||||||
accent: '#8c9eff',
|
accent: '#8c9eff',
|
||||||
error: '#b71c1c',
|
error: '#b71c1c',
|
||||||
|
|
||||||
|
|
||||||
main: '#272727',
|
main: '#272727',
|
||||||
main_accent: '#202020',
|
main_accent: '#202020',
|
||||||
footer: '#404040',
|
footer: '#404040',
|
||||||
@ -23,6 +22,6 @@ export default new Vuetify({
|
|||||||
background_soft: '#171717',
|
background_soft: '#171717',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dark:true
|
dark: true
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import VueRouter from "vue-router";
|
import VueRouter from "vue-router";
|
||||||
import Home from "../views/Home.vue";
|
import Home from "../views/Home.vue";
|
||||||
import missing from "../views/missing.vue";
|
import missing from "../views/Missing.vue";
|
||||||
import TimeRecords from "../views/TimeRecords.vue";
|
import TimeRecords from "../views/timerecords/TimeRecords.vue";
|
||||||
import About from "../views/About.vue";
|
import About from "../views/About.vue";
|
||||||
import StatisticOverview from "../views/StatisticOverview.vue";
|
import StatisticOverview from "../views/statistics/StatisticOverview.vue";
|
||||||
import Users from "../views/Users.vue";
|
import Users from "../views/admin/Users.vue";
|
||||||
import EditUser from "../views/EditUser.vue";
|
import EditUser from "../views/admin/EditUser.vue";
|
||||||
import TimeTrackAccounts from "../views/TimeTrackAccounts.vue";
|
import TimeTrackAccounts from "../views/timetrackaccounts/TimeTrackAccounts.vue";
|
||||||
import EditTimeTrackAccount from "../views/EditTimeTrackAccount.vue"
|
import EditTimeTrackAccount from "../views/timetrackaccounts/EditTimeTrackAccount.vue"
|
||||||
import CreateTimeTrackAccount from "../views/CreateTimeTrackAccount.vue"
|
import CreateTimeTrackAccount from "../views/timetrackaccounts/CreateTimeTrackAccount.vue"
|
||||||
import EditTimerecord from "../views/EditTimerecord.vue"
|
import EditTimerecord from "../views/timerecords/EditTimerecord.vue"
|
||||||
import CreateTimerecord from "../views/CreateTimerecord.vue"
|
import CreateTimerecord from "../views/timerecords/CreateTimerecord.vue"
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -19,90 +20,67 @@ const routes = [
|
|||||||
path: "/",
|
path: "/",
|
||||||
name: "Home",
|
name: "Home",
|
||||||
component: Home,
|
component: Home,
|
||||||
meta: {
|
meta: {title: "Geo Timetracking - Home"}
|
||||||
title: "Geo Timetracking - Home",
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/timerecords",
|
path: "/timerecords",
|
||||||
name: "TimeRecords",
|
name: "TimeRecords",
|
||||||
component: TimeRecords,
|
component: TimeRecords,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Time Records'}
|
||||||
title: 'Geo Timetracking - Time Records',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/about",
|
path: "/about",
|
||||||
name: "About",
|
name: "About",
|
||||||
component: About,
|
component: About,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - About'}
|
||||||
title: 'Geo Timetracking - About',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/statistics",
|
path: "/statistics",
|
||||||
name: "Statistics",
|
name: "Statistics",
|
||||||
component: StatisticOverview,
|
component: StatisticOverview,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Statistics'}
|
||||||
title: 'Geo Timetracking - Statistics',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/users",
|
path: "/users",
|
||||||
name: "Users",
|
name: "Users",
|
||||||
component: Users,
|
component: Users,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Users'}
|
||||||
title: 'Geo Timetracking - Users',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/edituser",
|
path: "/edituser",
|
||||||
name: "EditUser",
|
name: "EditUser",
|
||||||
component: EditUser,
|
component: EditUser,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Edit User'}
|
||||||
title: 'Geo Timetracking - Edit User',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/timetrackaccounts",
|
path: "/timetrackaccounts",
|
||||||
name: "TimeTrack Accounts",
|
name: "TimeTrack Accounts",
|
||||||
component: TimeTrackAccounts,
|
component: TimeTrackAccounts,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - TimeTrack Accounts'}
|
||||||
title: 'Geo Timetracking - TimeTrack Accounts',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/edittimetrackaccount",
|
path: "/edittimetrackaccount",
|
||||||
name: "Edit TimeTrack Account",
|
name: "Edit TimeTrack Account",
|
||||||
component: EditTimeTrackAccount,
|
component: EditTimeTrackAccount,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Edit TimeTrack Account'}
|
||||||
title: 'Geo Timetracking - Edit TimeTrack Account',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/createtimetrackaccount",
|
path: "/createtimetrackaccount",
|
||||||
name: "Create TimeTrack Account",
|
name: "Create TimeTrack Account",
|
||||||
component: CreateTimeTrackAccount,
|
component: CreateTimeTrackAccount,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Create TimeTrack Accounts'}
|
||||||
title: 'Geo Timetracking - Create TimeTrack Accounts',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/edittimerecord",
|
path: "/edittimerecord",
|
||||||
name: "EditTimerecord",
|
name: "EditTimerecord",
|
||||||
component: EditTimerecord,
|
component: EditTimerecord,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Edit Time Record'}
|
||||||
title: 'Geo Timetracking - Edit Time Record',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/createtimerecord",
|
path: "/createtimerecord",
|
||||||
name: "CreateTimerecord",
|
name: "CreateTimerecord",
|
||||||
component: CreateTimerecord,
|
component: CreateTimerecord,
|
||||||
meta: {
|
meta: {title: 'Geo Timetracking - Create Time Record'}
|
||||||
title: 'Geo Timetracking - Create Time Record',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export const baseUri = 'http://plesk.icaotix.de:5000'
|
|
@ -1,47 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<v-row class="ma-5">
|
<v-row class="ma-5">
|
||||||
<v-col cols="3" ></v-col>
|
<v-col cols="3"></v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card>
|
<v-card>
|
||||||
<p
|
<p class="text-center logowhite--text">This is a ubiquitous computing project.</p>
|
||||||
class="text-center logowhite--text"
|
<p class="text-center logowhite--text">It was created by the team TacocaT.</p>
|
||||||
style="font-size:20pt"
|
|
||||||
>This is a ubiquitous computing project.</p>
|
|
||||||
<p
|
|
||||||
class="text-center logowhite--text"
|
|
||||||
style="font-size:20pt"
|
|
||||||
>It was created by the team TacocaT.</p>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3"></v-col>
|
<v-col cols="3"></v-col>
|
||||||
<v-col cols="3"></v-col>
|
<v-col cols="3"></v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card>
|
<v-card>
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Team TacocaT</p>
|
<p class="text-center logowhite--text font-size-larger">Team TacocaT</p>
|
||||||
<br />
|
<br/>
|
||||||
<p
|
<p class="text-center logowhite--text">Backend Developer: Marcel Schwarz</p>
|
||||||
class="text-center logowhite--text"
|
<p class="text-center logowhite--text">Android Developer: Tobias Wieck</p>
|
||||||
style="font-size:20pt"
|
<p class="text-center logowhite--text">Frontend Developer: Simon Kellner, Tim Zieger</p>
|
||||||
>Backend Developer: Marcel Schwarz</p>
|
</v-card>
|
||||||
<p
|
</v-col>
|
||||||
class="text-center logowhite--text"
|
</v-row>
|
||||||
style="font-size:20pt"
|
<v-row class="ma-5" v-if="todos">
|
||||||
>Android Developer: Tobias Wieck</p>
|
<v-col cols="12">
|
||||||
<p
|
<v-card :key="todo.id" v-for="todo of todos">
|
||||||
class="text-center logowhite--text"
|
<p>
|
||||||
style="font-size:20pt"
|
<v-icon color="white" v-if="!todo.completed">mdi-checkbox-blank-circle-outline</v-icon>
|
||||||
>Frontend Developer: Simon Kellner, Tim Zieger</p>
|
<v-icon color="white" v-else>mdi-check-circle</v-icon>
|
||||||
|
{{todo.title}}
|
||||||
|
</p>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
// @ is an alias to /src
|
|
||||||
|
|
||||||
export default {
|
<script>
|
||||||
name: "About"
|
import axios from "axios"
|
||||||
};
|
|
||||||
|
export default {
|
||||||
|
name: "About",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
todos: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let response = await axios.get("https://jsonplaceholder.typicode.com/todos")
|
||||||
|
this.todos = response.data
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-larger {
|
||||||
|
font-size: 30pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
<template >
|
|
||||||
<v-container id = "createAccountListen">
|
|
||||||
<v-card align-center>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
|
||||||
|
|
||||||
<v-form>
|
|
||||||
<v-text-field
|
|
||||||
label="name"
|
|
||||||
v-model="newname"
|
|
||||||
name="name"
|
|
||||||
prepend-icon="mdi-account-tie"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="revenue"
|
|
||||||
v-model="newrevenue"
|
|
||||||
name="revenue"
|
|
||||||
prepend-icon="mdi-currency-usd"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="description"
|
|
||||||
v-model="newdescription"
|
|
||||||
name="description"
|
|
||||||
prepend-icon="mdi-information-variant"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="addAccount()">Add</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { baseUri } from "../variables";
|
|
||||||
export default {
|
|
||||||
name: "CreateTimeTrackAccount",
|
|
||||||
data: () => ({
|
|
||||||
user: sessionStorage.getItem("timeTrackAccountListUserId"),
|
|
||||||
newname: "",
|
|
||||||
newrevenue: "",
|
|
||||||
newdescription: "",
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addAccount() {
|
|
||||||
if (this.newname != "" || this.newrevenue != "" || this.newdescription != "") {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var suc;
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 201) & (this.readyState == 4)) {
|
|
||||||
suc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("POST", baseUri + "/accounts", false);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"user": "' +
|
|
||||||
this.user +
|
|
||||||
'", "revenue": "' +
|
|
||||||
this.newrevenue +
|
|
||||||
'", "name": "' +
|
|
||||||
this.newname +
|
|
||||||
'", "description": "' +
|
|
||||||
this.newdescription +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if (suc == true ) {
|
|
||||||
this.$router.push("/timetrackaccounts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
var listen = document.getElementById('createAccountListen');
|
|
||||||
listen.addEventListener("keyup", e => {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.addAccount();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,270 +0,0 @@
|
|||||||
<template >
|
|
||||||
<v-container id="createRecordListen">
|
|
||||||
<v-card align-center>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="6">
|
|
||||||
<v-select
|
|
||||||
v-model="newtype"
|
|
||||||
:items="types"
|
|
||||||
menu-props="auto"
|
|
||||||
label="Type"
|
|
||||||
hide-details
|
|
||||||
prepend-icon="mdi-currency-usd"
|
|
||||||
single-line
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="6">
|
|
||||||
<v-select
|
|
||||||
v-model="accountname"
|
|
||||||
:items="accounts"
|
|
||||||
menu-props="auto"
|
|
||||||
label="Account"
|
|
||||||
hide-details
|
|
||||||
prepend-icon="mdi-account-tie"
|
|
||||||
single-line
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-menu
|
|
||||||
ref="menu"
|
|
||||||
v-model="menu"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
:nudge-right="40"
|
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
min-width="290px"
|
|
||||||
>
|
|
||||||
<template v-slot:activator="{ on }">
|
|
||||||
<v-text-field
|
|
||||||
v-model="newstartdate"
|
|
||||||
label="Startdate"
|
|
||||||
prepend-icon="mdi-calendar-range"
|
|
||||||
readonly
|
|
||||||
v-on="on"
|
|
||||||
></v-text-field>
|
|
||||||
</template>
|
|
||||||
<v-date-picker v-model="newstartdate" @input="menu = false" no-title scrollable>
|
|
||||||
|
|
||||||
</v-date-picker>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-menu
|
|
||||||
ref="menu2"
|
|
||||||
v-model="menu2"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
:nudge-right="40"
|
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
min-width="290px"
|
|
||||||
>
|
|
||||||
<template v-slot:activator="{ on }">
|
|
||||||
<v-text-field
|
|
||||||
v-model="newenddate"
|
|
||||||
label="Enddate"
|
|
||||||
prepend-icon="mdi-calendar-range"
|
|
||||||
readonly
|
|
||||||
v-on="on"
|
|
||||||
></v-text-field>
|
|
||||||
</template>
|
|
||||||
<v-date-picker v-model="newenddate" @input="menu2 = false" no-title scrollable>
|
|
||||||
|
|
||||||
</v-date-picker>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-menu
|
|
||||||
ref="menutime"
|
|
||||||
v-model="menutime"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
:nudge-right="40"
|
|
||||||
:return-value.sync="timestart"
|
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
max-width="290px"
|
|
||||||
min-width="290px"
|
|
||||||
>
|
|
||||||
<template v-slot:activator="{ on }">
|
|
||||||
<v-text-field
|
|
||||||
v-model="timestart"
|
|
||||||
label="Starttime"
|
|
||||||
prepend-icon="mdi-clock-outline"
|
|
||||||
readonly
|
|
||||||
v-on="on"
|
|
||||||
></v-text-field>
|
|
||||||
</template>
|
|
||||||
<v-time-picker
|
|
||||||
v-if="menutime"
|
|
||||||
v-model="timestart"
|
|
||||||
full-width
|
|
||||||
format="24hr"
|
|
||||||
@click:minute="$refs.menutime.save(timestart)"
|
|
||||||
></v-time-picker>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-menu
|
|
||||||
ref="menutime2"
|
|
||||||
v-model="menutime2"
|
|
||||||
:close-on-content-click="false"
|
|
||||||
:nudge-right="40"
|
|
||||||
:return-value.sync="timeend"
|
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
max-width="290px"
|
|
||||||
min-width="290px"
|
|
||||||
>
|
|
||||||
<template v-slot:activator="{ on }">
|
|
||||||
<v-text-field
|
|
||||||
v-model="timeend"
|
|
||||||
label="Endtime"
|
|
||||||
prepend-icon="mdi-clock-outline"
|
|
||||||
readonly
|
|
||||||
v-on="on"
|
|
||||||
></v-text-field>
|
|
||||||
</template>
|
|
||||||
<v-time-picker
|
|
||||||
v-if="menutime2"
|
|
||||||
v-model="timeend"
|
|
||||||
full-width
|
|
||||||
format="24hr"
|
|
||||||
@click:minute="$refs.menutime2.save(timeend)"
|
|
||||||
></v-time-picker>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="addRecord()">Add</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { baseUri } from "../variables";
|
|
||||||
export default {
|
|
||||||
name: "CreateTimeTrackAccount",
|
|
||||||
data: () => ({
|
|
||||||
timestart: null,
|
|
||||||
menutime: false,
|
|
||||||
timeend: null,
|
|
||||||
menutime2: false,
|
|
||||||
menu: false,
|
|
||||||
menu2: false,
|
|
||||||
types: [ "PAID" ,"BREAK"],
|
|
||||||
accounts: "",
|
|
||||||
user: sessionStorage.getItem("timeTrackAccountListUserId"),
|
|
||||||
newstartdate: "",
|
|
||||||
newenddate: "",
|
|
||||||
newtype: "",
|
|
||||||
accountname: ""
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addRecord() {
|
|
||||||
var account = "";
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
account = JSON.parse(accountxhttp.responseText);
|
|
||||||
account = account._links.self.href;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsernameAndName?username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&account=" +
|
|
||||||
this.accountname,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
accountxhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
accountxhttp.send(null);
|
|
||||||
|
|
||||||
if (this.newstartdate != "" && this.newenddate != "" && this.newdate != "" && account != "") {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var suc;
|
|
||||||
this.newstartdate = this.newstartdate + "T" + this.timestart;
|
|
||||||
this.newenddate = this.newenddate + "T" + this.timeend;
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 201) & (this.readyState == 4)) {
|
|
||||||
suc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("POST", baseUri + "/records", false);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"startdate": "' +
|
|
||||||
this.newstartdate +
|
|
||||||
'", "enddate": "' +
|
|
||||||
this.newenddate +
|
|
||||||
'", "type": "' +
|
|
||||||
this.newtype +
|
|
||||||
'", "account": "' +
|
|
||||||
account +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if (suc == true) {
|
|
||||||
this.$router.push("/timerecords");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created (){
|
|
||||||
var accountsxhttp = new XMLHttpRequest();
|
|
||||||
var accounts;
|
|
||||||
accountsxhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
accounts = JSON.parse(accountsxhttp.responseText);
|
|
||||||
accounts = accounts._embedded.accounts;
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountsxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsername?username=" +
|
|
||||||
sessionStorage.getItem("username") ,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
accountsxhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
accountsxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
accountsxhttp.send(null);
|
|
||||||
var accountnames =[];
|
|
||||||
for (let index = 0; index < accounts.length; index++) {
|
|
||||||
accountnames[index] = accounts[index].name;
|
|
||||||
|
|
||||||
}
|
|
||||||
this.accounts =accountnames;
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
var listen = document.getElementById("createRecordListen");
|
|
||||||
|
|
||||||
listen.addEventListener("keyup", e => {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.addRecord();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,129 +0,0 @@
|
|||||||
<template >
|
|
||||||
<v-container id = "editAccountListen">
|
|
||||||
<v-card align-center>
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Account to edit:</p>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">{{name}}</p>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<v-card align-center>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
|
||||||
|
|
||||||
<v-form>
|
|
||||||
<v-text-field
|
|
||||||
label="name"
|
|
||||||
v-model="newname"
|
|
||||||
name="name"
|
|
||||||
prepend-icon="mdi-account-tie"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="revenue"
|
|
||||||
v-model="newrevenue"
|
|
||||||
name="revenue"
|
|
||||||
prepend-icon="mdi-currency-usd"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="description"
|
|
||||||
v-model="newdescription"
|
|
||||||
name="description"
|
|
||||||
prepend-icon="mdi-information-variant"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="editAccount()">Edit</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "EditTimeTrackAccount",
|
|
||||||
data: () => ({
|
|
||||||
name: "",
|
|
||||||
revenue: "",
|
|
||||||
description: "",
|
|
||||||
newname: "",
|
|
||||||
newrevenue: "",
|
|
||||||
newdescription: "",
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
editAccount() {
|
|
||||||
var link = sessionStorage.getItem("timeTrackAccountEditSelfLink");
|
|
||||||
|
|
||||||
if (this.newname != this.name || this.newrevenue != this.revenue || this.newdescription != this.description) {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var suc;
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
suc =true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("PATCH", link, false);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"name": "' +
|
|
||||||
this.newname +
|
|
||||||
'", "revenue": "' +
|
|
||||||
this.newrevenue +
|
|
||||||
'", "description": "' +
|
|
||||||
this.newdescription +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if(suc ==true){
|
|
||||||
this.$router.push( "/timetrackaccounts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
var account;
|
|
||||||
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
account = JSON.parse(accountxhttp.responseText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
sessionStorage.getItem("timeTrackAccountEditSelfLink"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
this.name = account.name;
|
|
||||||
this.revenue = account.revenue;
|
|
||||||
this.description = account.description;
|
|
||||||
|
|
||||||
this.newname = account.name;
|
|
||||||
this.newrevenue = account.revenue;
|
|
||||||
this.newdescription = account.description;
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
var listen = document.getElementById('editAccountListen');
|
|
||||||
listen.addEventListener("keyup", e => {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.editAccount();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,242 +0,0 @@
|
|||||||
<template >
|
|
||||||
<v-container>
|
|
||||||
<v-card align-center>
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">User to edit:</p>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">{{username}}</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
<v-card align-center>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">User Information</p>
|
|
||||||
|
|
||||||
<v-form>
|
|
||||||
<v-text-field
|
|
||||||
label="firstname"
|
|
||||||
v-model="firstname"
|
|
||||||
name="firstname"
|
|
||||||
prepend-icon="mdi-account"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="lastname"
|
|
||||||
v-model="lastname"
|
|
||||||
name="lastname"
|
|
||||||
prepend-icon="mdi-account"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="editUserInformation()">Edit</v-btn>
|
|
||||||
</div>
|
|
||||||
<p id="noudata"></p>
|
|
||||||
</v-card>
|
|
||||||
<v-card align-center>
|
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Location</p>
|
|
||||||
|
|
||||||
<v-form>
|
|
||||||
<v-text-field
|
|
||||||
label="latitude"
|
|
||||||
v-model="latitude"
|
|
||||||
name="latitude"
|
|
||||||
prepend-icon="mdi-map-marker"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="longitude"
|
|
||||||
v-model="longitude"
|
|
||||||
name="longitude"
|
|
||||||
prepend-icon="mdi-map-marker"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
label="radius"
|
|
||||||
v-model="radius"
|
|
||||||
name="radius"
|
|
||||||
prepend-icon="mdi-map-marker-radius"
|
|
||||||
type="text"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
<div class="text-center ma-3">
|
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="editLocation()">Edit</v-btn>
|
|
||||||
</div>
|
|
||||||
<p id="locationnotcomplete"></p>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { baseUri} from "../variables";
|
|
||||||
export default {
|
|
||||||
name: "Edituser",
|
|
||||||
data: () => ({
|
|
||||||
firstname: "",
|
|
||||||
lastname: "",
|
|
||||||
longitude: "",
|
|
||||||
latitude: "",
|
|
||||||
radius: "",
|
|
||||||
username: sessionStorage.getItem("usernameedit")
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
editUserInformation() {
|
|
||||||
var link = sessionStorage.getItem("edituser");
|
|
||||||
document.getElementById("noudata").innerHTML = "";
|
|
||||||
var suc;
|
|
||||||
if (this.firstname != "" && this.lastname != "") {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
suc= true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("PATCH", link, false);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"firstname": "' +
|
|
||||||
this.firstname +
|
|
||||||
'", "lastname": "' +
|
|
||||||
this.lastname +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if(suc == true ){
|
|
||||||
this.$router.push("/users");
|
|
||||||
}
|
|
||||||
} else if (this.firstname != "") {
|
|
||||||
var xhttpfirst = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttpfirst.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
suc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttpfirst.open("PATCH", link, false);
|
|
||||||
xhttpfirst.setRequestHeader("Content-Type", "application/json");
|
|
||||||
xhttpfirst.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
xhttpfirst.send("{" + '"firstname": "' + this.firstname + '"}');
|
|
||||||
if(suc == true ){
|
|
||||||
this.$router.push("/users");
|
|
||||||
}
|
|
||||||
} else if (this.lastname != "") {
|
|
||||||
var xhttplast = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttplast.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
suc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttplast.open("PATCH", link, false);
|
|
||||||
xhttplast.setRequestHeader("Content-Type", "application/json");
|
|
||||||
xhttplast.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
xhttplast.send("{" + '"lastname": "' + this.lastname + '"}');
|
|
||||||
if(suc == true ){
|
|
||||||
this.$router.push("/users");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById("noudata").innerHTML = "please fill out at lest one field";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editLocation() {
|
|
||||||
var numericerror = false;
|
|
||||||
var link = sessionStorage.getItem("edituser");
|
|
||||||
if (this.longitude != "" && this.latitude != "" && this.radius) {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if ((this.status == 201) & (this.readyState == 4)) {
|
|
||||||
var location = JSON.parse(xhttp.responseText);
|
|
||||||
sessionStorage.setItem("locationlink", location._links.self.href);
|
|
||||||
} else if(this.readyState == 4 ){
|
|
||||||
|
|
||||||
|
|
||||||
numericerror = true;
|
|
||||||
document.getElementById("locationnotcomplete").innerHTML = "Only numeric values are allowed";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("POST", baseUri + "/locations", false);
|
|
||||||
xhttp.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
xhttp.send(
|
|
||||||
"{" +
|
|
||||||
'"latitude": "' +
|
|
||||||
this.latitude +
|
|
||||||
'", "longitude": "' +
|
|
||||||
this.longitude +
|
|
||||||
'", "radius": "' +
|
|
||||||
this.radius +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
if (numericerror == false) {
|
|
||||||
|
|
||||||
var xhttpadd = new XMLHttpRequest();
|
|
||||||
var suc;
|
|
||||||
xhttpadd.onreadystatechange = function() {
|
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
|
||||||
suc = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttpadd.open("PATCH", link, false);
|
|
||||||
xhttpadd.setRequestHeader("Content-Type", "application/json");
|
|
||||||
xhttpadd.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
xhttpadd.send(
|
|
||||||
"{" +
|
|
||||||
'"location": "' +
|
|
||||||
sessionStorage.getItem("locationlink") +
|
|
||||||
'"}'
|
|
||||||
);
|
|
||||||
if (suc == true) {
|
|
||||||
this.$router.push("/users");
|
|
||||||
}
|
|
||||||
document.getElementById("locationnotcomplete").innerHTML = "";
|
|
||||||
sessionStorage.removeItem("locationlink");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById("locationnotcomplete").innerHTML = "please fill out all fields";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var userxhttp = new XMLHttpRequest();
|
|
||||||
userxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var username = JSON.parse(userxhttp.responseText);
|
|
||||||
sessionStorage.setItem(
|
|
||||||
"usernameedit",
|
|
||||||
username.username
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
userxhttp.open("GET",sessionStorage.getItem("edituser"), false);
|
|
||||||
|
|
||||||
userxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
userxhttp.send(null);
|
|
||||||
this.username = sessionStorage.getItem("usernameedit");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,42 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<v-row v-if="loggedIn == 'true'">
|
<v-row v-if="loggedIn">
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card class="pa-3">
|
<v-card class="pa-3">
|
||||||
<p style="font-size:30pt">User Information</p>
|
<p class="larger-text">User Information</p>
|
||||||
<p style="font-size:15pt"> Username: {{username}}</p>
|
<p>Username: {{username}}</p>
|
||||||
<p style="font-size:15pt">Firstname: {{firstname}}</p>
|
<p>Firstname: {{firstname}}</p>
|
||||||
<p style="font-size:15pt">Lastname: {{lastname}}</p>
|
<p>Lastname: {{lastname}}</p>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card class="pa-3">
|
<v-card class="pa-3">
|
||||||
<div v-if="haveLocation == true">
|
<div v-if="location">
|
||||||
<p style="font-size:30pt">Location</p>
|
<p class="larger-text">Location</p>
|
||||||
<p style="font-size:15pt">Longitude: {{location.longitude}}</p>
|
<p>Longitude: {{location.longitude}}</p>
|
||||||
<p style="font-size:15pt">Latitude: {{location.latitude}}</p>
|
<p>Latitude: {{location.latitude}}</p>
|
||||||
<p style="font-size:15pt">Radius: {{location.radius}}</p>
|
<p>Radius: {{location.radius}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="haveLocation == false">
|
<div v-else>
|
||||||
<p style="font-size:30pt">Location</p>
|
<p class="larger-text">Location</p>
|
||||||
<p>No location set</p>
|
<p>No location set</p>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card>
|
<v-card>
|
||||||
<p class="pa-2" style="font-size:30pt">Today</p>
|
<p class="pa-2 larger-text">Today</p>
|
||||||
<div :key="today._links.self.href" v-for="today in todaysRecord">
|
<div :key="today._links.self.href" v-for="today in todaysRecord">
|
||||||
<v-row no-gutters align="center">
|
<v-row align="center" no-gutters>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-card color="background" elevation="0" class="ma-2">
|
<v-card class="ma-2" color="background" elevation="0">
|
||||||
<pre><v-icon color="green" v-bind:class="{'d-none':today.type == 'BREAK'}">mdi-currency-usd</v-icon><v-icon
|
<div>
|
||||||
color="red"
|
<v-icon color="green" v-if="today.type === 'PAID'">mdi-currency-usd</v-icon>
|
||||||
v-bind:class="{'d-none':today.type == 'PAID'}"
|
<v-icon color="red" v-if="today.type === 'BREAK'">mdi-currency-usd-off</v-icon>
|
||||||
>mdi-currency-usd-off</v-icon>{{" " + today.type}}</pre>
|
{{today.type}}
|
||||||
<pre><v-icon color="primary">mdi-clock-outline</v-icon>{{" Start " + today.startdate}}</pre>
|
</div>
|
||||||
|
<div>
|
||||||
<pre><v-icon color="primary">mdi-clock-outline</v-icon>{{" End " + today.enddate}}</pre>
|
<v-icon color="primary">mdi-clock-outline</v-icon>
|
||||||
|
Start: {{today.startdate}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<v-icon color="primary">mdi-clock-outline</v-icon>
|
||||||
|
End: {{today.enddate}}
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@ -46,135 +52,82 @@
|
|||||||
|
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-card>
|
<v-card>
|
||||||
<p class="pa-2" style="font-size:30pt">Accounts</p>
|
<p class="pa-2 larger-text">Accounts</p>
|
||||||
<div
|
<div :key="timeTrackAccount._links.self.href" v-for="timeTrackAccount in timeTrackAccounts">
|
||||||
:key="timeTrackAccount._links.self.href"
|
<v-row align="center" no-gutters>
|
||||||
v-for="timeTrackAccount in timeTrackAccounts"
|
|
||||||
>
|
|
||||||
<v-row no-gutters align="center">
|
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-card color="background" elevation="0" class="ma-2">
|
<v-card class="ma-2" color="background" elevation="0">
|
||||||
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" Account " + timeTrackAccount.name}}</pre>
|
<div>
|
||||||
|
<v-icon color="primary">mdi-account-tie</v-icon>
|
||||||
<pre><v-icon color="primary">mdi-currency-usd</v-icon>{{" Revenue " + timeTrackAccount.revenue}}</pre>
|
Account: {{timeTrackAccount.name}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<v-icon color="primary">mdi-currency-usd</v-icon>
|
||||||
|
Revenue: {{timeTrackAccount.revenue}}
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-card v-if="loggedIn == 'false'" class="pa-3">
|
<v-card class="pa-3" v-if="!loggedIn">
|
||||||
<p style="font-size:20pt">Welcome to Geo Timetracking</p>
|
<p class="larger-text">Welcome to Geo Timetracking</p>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {BASE_URI} from "../globals.js";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
import { baseUri } from "../variables.js";
|
|
||||||
export default {
|
|
||||||
name: "Home",
|
name: "Home",
|
||||||
components: {},
|
data() {
|
||||||
data: () => ({
|
return {
|
||||||
|
loggedIn: JSON.parse(sessionStorage.getItem("loggedin")),
|
||||||
username: sessionStorage.getItem("username"),
|
username: sessionStorage.getItem("username"),
|
||||||
firstname: sessionStorage.getItem("firstname"),
|
firstname: sessionStorage.getItem("firstname"),
|
||||||
lastname: sessionStorage.getItem("lastname"),
|
lastname: sessionStorage.getItem("lastname"),
|
||||||
todaysRecord: "",
|
todaysRecord: "",
|
||||||
loggedIn: sessionStorage.getItem("loggedin"),
|
|
||||||
timeTrackAccounts: "",
|
timeTrackAccounts: "",
|
||||||
location: "",
|
location: "",
|
||||||
haveLocation: ""
|
haveLocation: ""
|
||||||
}),
|
|
||||||
created() {
|
|
||||||
if(sessionStorage.getItem("loggedin") == "true"){
|
|
||||||
//Get todays records
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var today;
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
today = JSON.parse(xhttp.responseText);
|
|
||||||
today = today._embedded.records;
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
xhttp.open("GET", baseUri + "/records/search/today", false);
|
async mounted() {
|
||||||
|
if (this.loggedIn) {
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
|
|
||||||
for (let index = 0; index < today.length; index++) {
|
|
||||||
var record = today[index];
|
|
||||||
|
|
||||||
var start = record.startdate;
|
|
||||||
|
|
||||||
start = start.split("T");
|
|
||||||
today[index].startdate = start[1];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var end = record.enddate;
|
let responses = await Promise.all([
|
||||||
end = end.split("T");
|
axios.get(BASE_URI + "/records/search/today"),
|
||||||
today[index].enddate = end[1];
|
axios.get(BASE_URI + "/accounts/search/findByUsername", {params: {username: this.username}}),
|
||||||
} catch (e) {
|
axios.get(BASE_URI + `/users/${sessionStorage.getItem("userIDOwn")}/location`)
|
||||||
today[index].enddate = "pending";
|
])
|
||||||
}
|
|
||||||
|
let today = responses[0].data._embedded.records
|
||||||
|
for (let record of today) {
|
||||||
|
record.startdate = record.startdate.split("T")[1]
|
||||||
|
record.enddate = record.enddate ? record.enddate.split("T")[1] : "pending"
|
||||||
}
|
}
|
||||||
this.todaysRecord = today;
|
this.todaysRecord = today;
|
||||||
|
|
||||||
|
this.timeTrackAccounts = responses[1].data._embedded.accounts
|
||||||
var timeTrackAccountsTMP;
|
this.location = responses[2].data
|
||||||
var accountxhttp = new XMLHttpRequest();
|
} catch (e) {
|
||||||
accountxhttp.onreadystatechange = function() {
|
console.log(e)
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
}
|
||||||
timeTrackAccountsTMP = JSON.parse(accountxhttp.responseText);
|
}
|
||||||
timeTrackAccountsTMP = timeTrackAccountsTMP._embedded.accounts;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsername?username=" +
|
|
||||||
sessionStorage.getItem("username"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
|
|
||||||
this.timeTrackAccounts = timeTrackAccountsTMP;
|
|
||||||
|
|
||||||
//Get Location
|
|
||||||
var location;
|
|
||||||
var locSuc = false;
|
|
||||||
var locationxhttp = new XMLHttpRequest();
|
|
||||||
locationxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
locSuc = true;
|
|
||||||
location = JSON.parse(locationxhttp.responseText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
locationxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri + "/users/" + sessionStorage.getItem("userIDOwn") + "/location",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
locationxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
locationxhttp.send(null);
|
|
||||||
|
|
||||||
|
|
||||||
this.haveLocation = locSuc;
|
|
||||||
this.location = location;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p {
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.larger-text {
|
||||||
|
font-size: 30pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {}
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
@ -1,57 +1,49 @@
|
|||||||
<template >
|
<template>
|
||||||
<v-container class="fill-height" id="signInListen">
|
<v-container @keyup.enter="handleEnter" class="fill-height">
|
||||||
<v-row align="center" justify="center">
|
<v-row align="center" justify="center">
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-card elevation="0">
|
<v-card elevation="0">
|
||||||
<v-window v-model="step">
|
<v-window v-model="visiblePane">
|
||||||
<v-window-item :value="1">
|
<v-window-item :value="1">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="8" class="main">
|
<v-col class="main" cols="12" md="8">
|
||||||
<v-card-text class="mt-1">
|
<v-card-text class="mt-1">
|
||||||
<h1 class="text-center display-2 logowhite--text">Sign in</h1>
|
<h1 class="text-center display-2 logowhite--text">Sign in</h1>
|
||||||
|
|
||||||
<v-form>
|
<v-form>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
label="Username"
|
label="Username"
|
||||||
v-model="username"
|
|
||||||
name="Username"
|
name="Username"
|
||||||
prepend-icon="mdi-account"
|
prepend-icon="mdi-account"
|
||||||
type="text"
|
type="text"
|
||||||
color="primary"
|
v-model="signInData.username"
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
id="password"
|
id="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
v-model="password"
|
|
||||||
name="Password"
|
name="Password"
|
||||||
prepend-icon="mdi-key"
|
prepend-icon="mdi-key"
|
||||||
type="password"
|
type="password"
|
||||||
color="primary"
|
v-model="signInData.password"
|
||||||
/>
|
/>
|
||||||
</v-form>
|
</v-form>
|
||||||
<p id="missing"></p>
|
<p>{{errorMessage}}</p>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="signin()" >Sign In</v-btn>
|
<v-btn @click="signin" color="logowhite" dark outlined rounded>Sign In</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4" class="main">
|
<v-col class="main" cols="12" md="4">
|
||||||
<v-card-text class="white--text mt-1">
|
<v-card-text class="white--text mt-1">
|
||||||
<h1 class="text-center display-1 primary--text">No account yet?</h1>
|
<h1 class="text-center display-1 primary--text">No account yet?</h1>
|
||||||
<div class="text-center ma-12">
|
<div class="text-center ma-12">
|
||||||
<v-avatar min-width="100" size="230">
|
<v-avatar min-width="100" size="230">
|
||||||
<img src="../assets/logo_gt.svg" alt="John" />
|
<img alt="John" src="../assets/logo_gt.svg"/>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-10">
|
<div class="text-center mt-10">
|
||||||
<v-btn
|
<v-btn @click="switchPane(2)" class="primary--text" dark outlined rounded>Create Account</v-btn>
|
||||||
class="primary--text"
|
|
||||||
rounded
|
|
||||||
outlined
|
|
||||||
dark
|
|
||||||
@click="step++"
|
|
||||||
>Create Account</v-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-col>
|
</v-col>
|
||||||
@ -59,60 +51,60 @@
|
|||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item :value="2">
|
<v-window-item :value="2">
|
||||||
<v-row class="fill-heigth">
|
<v-row class="fill-heigth">
|
||||||
<v-col cols="12" md="4" class="main">
|
<v-col class="main" cols="12" md="4">
|
||||||
<v-card-text class="white--text mt-1">
|
<v-card-text class="white--text mt-1">
|
||||||
<h1 class="text-center display-1 primary--text">Welcome Back</h1>
|
<h1 class="text-center display-1 primary--text">Welcome Back</h1>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<div class="text-center ma-12">
|
<div class="text-center ma-12">
|
||||||
<v-avatar min-width="100" size="230">
|
<v-avatar min-width="100" size="230">
|
||||||
<img src="../assets/logo_gt.svg" alt="John" />
|
<img alt="John" src="../assets/logo_gt.svg"/>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
<v-btn class="primary--text" rounded outlined dark @click="step--">Sign In</v-btn>
|
<v-btn @click="switchPane(1)" class="primary--text" dark outlined rounded>Sign In</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="8" class="main">
|
<v-col class="main" cols="12" md="8">
|
||||||
<v-card-text class="mt-12">
|
<v-card-text class="mt-12">
|
||||||
<h1 class="text-center display-2 logowhite--text">Create Account</h1>
|
<h1 class="text-center display-2 logowhite--text">Create Account</h1>
|
||||||
<v-form>
|
<v-form>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary "
|
||||||
label="Firstname"
|
label="Firstname"
|
||||||
name="FirstnameR"
|
name="FirstnameR"
|
||||||
v-model="firstname"
|
|
||||||
prepend-icon="mdi-account-box"
|
prepend-icon="mdi-account-box"
|
||||||
type="text"
|
type="text"
|
||||||
color="primary "
|
v-model="signUpData.firstname"
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
label="Lastname"
|
label="Lastname"
|
||||||
name="LastnameR"
|
name="LastnameR"
|
||||||
v-model="lastname"
|
|
||||||
prepend-icon="mdi-account-box"
|
prepend-icon="mdi-account-box"
|
||||||
type="text"
|
type="text"
|
||||||
color="primary"
|
v-model="signUpData.lastname"
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
label="Username"
|
label="Username"
|
||||||
name="UsernameR"
|
name="UsernameR"
|
||||||
v-model="usernameR"
|
|
||||||
prepend-icon="mdi-account"
|
prepend-icon="mdi-account"
|
||||||
type="text"
|
type="text"
|
||||||
color="primary"
|
v-model="signUpData.username"
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
label="Password"
|
label="Password"
|
||||||
name="PasswordR"
|
name="PasswordR"
|
||||||
v-model="passwordR"
|
|
||||||
prepend-icon="mdi-key"
|
prepend-icon="mdi-key"
|
||||||
type="password"
|
type="password"
|
||||||
color="primary"
|
v-model="signUpData.password"
|
||||||
/>
|
/>
|
||||||
</v-form>
|
</v-form>
|
||||||
<p id="missingR"></p>
|
<p>{{errorMessage}}</p>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<div class="text-center mt-n5">
|
<div class="text-center mt-n5">
|
||||||
<v-btn color="logowhite" rounded outlined v-on:click="signup()" >Sign Up</v-btn>
|
<v-btn @click="signup" color="logowhite" outlined rounded>Sign Up</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@ -125,68 +117,49 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
export default {
|
||||||
export default {
|
|
||||||
data: () => ({
|
|
||||||
step: 1,
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
firstname: "",
|
|
||||||
lastname: "",
|
|
||||||
usernameR: "",
|
|
||||||
passwordR: ""
|
|
||||||
|
|
||||||
}),
|
|
||||||
props: {
|
props: {
|
||||||
source: String
|
source: String
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errorMessage: "",
|
||||||
|
visiblePane: 1,
|
||||||
|
signInData: {username: "", password: ""},
|
||||||
|
signUpData: {firstname: "", lastname: "", username: "", password: ""}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
signin() {
|
signin() {
|
||||||
if (this.username != "" && this.password != "") {
|
if (Object.values(this.signInData).every(item => item)) {
|
||||||
document.getElementById("missing").innerHTML= "";
|
this.errorMessage = ""
|
||||||
const loginData = {
|
this.$emit("signIn", this.signInData)
|
||||||
username: this.username,
|
} else {
|
||||||
password: this. password
|
this.errorMessage = "Please fill out all fields"
|
||||||
}
|
|
||||||
this.$emit('signIn', loginData);
|
|
||||||
}else {
|
|
||||||
document.getElementById("missing").innerHTML= "Please fill out all fields";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
signup(){
|
|
||||||
if (this.usernameR != "" && this.passwordR != "") {
|
|
||||||
document.getElementById("missingR").innerHTML= "";
|
|
||||||
const signupData = {
|
|
||||||
firstname: this.firstname,
|
|
||||||
lastname: this.lastname,
|
|
||||||
username: this.usernameR,
|
|
||||||
password: this. passwordR
|
|
||||||
}
|
|
||||||
this.$emit('signUp', signupData);
|
|
||||||
}else {
|
|
||||||
document.getElementById("missingR").innerHTML= "Please fill out all fields";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted(){
|
signup() {
|
||||||
var listen = document.getElementById('signInListen');
|
if (Object.values(this.signUpData).every(item => item)) {
|
||||||
listen.addEventListener('keyup', (e) => {
|
this.errorMessage = ""
|
||||||
if (e.keyCode === 13) {
|
this.$emit("signUp", this.signUpData)
|
||||||
e.preventDefault();
|
} else {
|
||||||
if(this.step==1){
|
this.errorMessage = "Please fill out all fields"
|
||||||
this.signin();
|
|
||||||
}else{
|
|
||||||
this.signup();
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
switchPane(num) {
|
||||||
|
this.errorMessage = ""
|
||||||
|
this.visiblePane = num
|
||||||
|
},
|
||||||
|
handleEnter() {
|
||||||
|
if (this.visiblePane === 1) {
|
||||||
|
this.signin()
|
||||||
|
} else {
|
||||||
|
this.signup()
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-row class="ma-5">
|
|
||||||
<v-col cols="4">
|
|
||||||
<WorkPausePie />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="4">
|
|
||||||
<account-pie />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="4">
|
|
||||||
<revenue-pie/>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12">
|
|
||||||
<MonthSummary />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12">
|
|
||||||
<WeekSummary />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import RevenuePie from "./charts/RevenuePie.vue"
|
|
||||||
import WeekSummary from "./charts/WeekSummary.vue";
|
|
||||||
import MonthSummary from "./charts/MonthSummary.vue";
|
|
||||||
import WorkPausePie from "./charts/WorkPausePie.vue";
|
|
||||||
import AccountPie from "./charts/AccountPie.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "StatisticOverview",
|
|
||||||
components: {
|
|
||||||
RevenuePie,
|
|
||||||
WeekSummary,
|
|
||||||
MonthSummary,
|
|
||||||
WorkPausePie,
|
|
||||||
AccountPie
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -1,164 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<div v-bind:key="timeRecord._links.self.href" v-for="timeRecord in timeRecords">
|
|
||||||
<TimeRecordItem
|
|
||||||
v-bind:timeRecord="timeRecord"
|
|
||||||
v-on:del-timeRecord="deleteTimeRecord"
|
|
||||||
v-on:edit-timeRecord="editTimeRecord"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="havePrevPage == true" @click="prevPage()">
|
|
||||||
<v-icon>mdi-arrow-left</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="haveNextPage == true" @click="nextPage()">
|
|
||||||
<v-icon>mdi-arrow-right</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="6"></v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="loggedIn == 'true'" fab color="primary" @click="createTimeRecord()">
|
|
||||||
<v-icon>mdi-plus</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import TimeRecordItem from "./TimeRecordItem.vue";
|
|
||||||
import { baseUri } from "../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "TimeRecords",
|
|
||||||
components: {
|
|
||||||
TimeRecordItem
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
timeRecords: "",
|
|
||||||
page: sessionStorage.getItem("page"),
|
|
||||||
haveNextPage: "",
|
|
||||||
havePrevPage: "",
|
|
||||||
loggedIn: sessionStorage.getItem("loggedin")
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
deleteTimeRecord(selfLink) {
|
|
||||||
var userxhttp = new XMLHttpRequest();
|
|
||||||
userxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 204) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
userxhttp.open("DELETE", selfLink, false);
|
|
||||||
|
|
||||||
userxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
userxhttp.send(null);
|
|
||||||
},
|
|
||||||
editTimeRecord(selfLink, end) {
|
|
||||||
if (end == "pending") {
|
|
||||||
alert("It's not possible to edit a running track");
|
|
||||||
} else {
|
|
||||||
sessionStorage.setItem("timeRecordEditSelfLink", selfLink);
|
|
||||||
|
|
||||||
this.$router.push("/edittimerecord");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createTimeRecord(selfLink) {
|
|
||||||
sessionStorage.setItem("timeRecordEditSelfLink", selfLink);
|
|
||||||
|
|
||||||
this.$router.push("/createtimerecord");
|
|
||||||
},
|
|
||||||
nextPage() {
|
|
||||||
var pageTMP = parseInt(this.page) + 1;
|
|
||||||
sessionStorage.setItem("page", pageTMP);
|
|
||||||
location.reload();
|
|
||||||
},
|
|
||||||
prevPage() {
|
|
||||||
var pageTMP = parseInt(this.page) - 1;
|
|
||||||
sessionStorage.setItem("page", pageTMP);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records;
|
|
||||||
var recordLinks;
|
|
||||||
|
|
||||||
if (this.page == null) {
|
|
||||||
this.page = 0;
|
|
||||||
}
|
|
||||||
sessionStorage.removeItem("page");
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
|
|
||||||
records = recordsDB._embedded.records;
|
|
||||||
recordLinks = recordsDB._links;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allForUser?username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&sort=startdate,desc&page=" +
|
|
||||||
this.page +
|
|
||||||
"&projection=overview",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
if (recordLinks.next != undefined) {
|
|
||||||
this.haveNextPage = true;
|
|
||||||
}
|
|
||||||
if (recordLinks.prev != undefined) {
|
|
||||||
this.havePrevPage = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
var time = record.duration;
|
|
||||||
var hours = time / 60;
|
|
||||||
hours = Math.floor(hours);
|
|
||||||
|
|
||||||
var min = time % 60;
|
|
||||||
record.duration = hours + "h " + min + "min";
|
|
||||||
var start = record.startdate;
|
|
||||||
records[index].completeStartDate = start;
|
|
||||||
start = start.split("T");
|
|
||||||
records[index].startdate = start[1];
|
|
||||||
records[index].date = start[0];
|
|
||||||
|
|
||||||
try {
|
|
||||||
var end = record.enddate;
|
|
||||||
end = end.split("T");
|
|
||||||
records[index].enddate = end[1];
|
|
||||||
} catch (e) {
|
|
||||||
records[index].enddate = "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timeRecords = records;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -1,92 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<div v-bind:key="timeTrackAccount.name" v-for="timeTrackAccount in timeTrackAccounts">
|
|
||||||
<TimeTrackAccountItem
|
|
||||||
v-bind:timeTrackAccount="timeTrackAccount"
|
|
||||||
v-on:del-timeTrackAccount="deleteTimeTrackAccount"
|
|
||||||
v-on:edit-timeTrackAccount="editTimeTrackAccount"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="6"></v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="loggedIn == 'true'" fab color="primary" @click="createTimeTrackAccount()">
|
|
||||||
<v-icon>mdi-plus</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { baseUri} from "../variables.js";
|
|
||||||
import TimeTrackAccountItem from "./TimeTrackAccountItem.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "TimeTrackAccounts",
|
|
||||||
components: {
|
|
||||||
TimeTrackAccountItem
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
timeTrackAccounts: "",
|
|
||||||
loggedIn: sessionStorage.getItem("loggedin")
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
deleteTimeTrackAccount(selfLink) {
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 204) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open("DELETE", selfLink , false);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
},
|
|
||||||
editTimeTrackAccount(selfLink) {
|
|
||||||
sessionStorage.setItem("timeTrackAccountEditSelfLink", selfLink);
|
|
||||||
|
|
||||||
this.$router.push( "/edittimetrackaccount");
|
|
||||||
},
|
|
||||||
createTimeTrackAccount() {
|
|
||||||
this.$router.push( "/createtimetrackaccount");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
var accounts;
|
|
||||||
var timeTrackAccountsTMP;
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
accounts = JSON.parse(accountxhttp.responseText);
|
|
||||||
timeTrackAccountsTMP = accounts._embedded.accounts;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsername?username=" +
|
|
||||||
sessionStorage.getItem("timeTrackAccountListUser"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
|
|
||||||
this.timeTrackAccounts = timeTrackAccountsTMP;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -1,108 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<div v-bind:key="user.username" v-for="user in users">
|
|
||||||
<UsersItems v-bind:user="user" v-on:edit-user="edituser" v-on:del-user="deluser" v-on:show-accounts="showAccounts"/>
|
|
||||||
</div>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="havePrevPage == true" @click="prevPage()">
|
|
||||||
<v-icon>mdi-arrow-left</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="1">
|
|
||||||
<v-btn v-if="haveNextPage == true" @click="nextPage()">
|
|
||||||
<v-icon>mdi-arrow-right</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="5"></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { baseUri} from "../variables.js";
|
|
||||||
import UsersItems from "./UsersItems.vue";
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
UsersItems
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
users: JSON.parse(sessionStorage.getItem("users")),
|
|
||||||
dialog: false,
|
|
||||||
page: sessionStorage.getItem("page"),
|
|
||||||
haveNextPage: "",
|
|
||||||
havePrevPage: ""
|
|
||||||
}),
|
|
||||||
methods: {
|
|
||||||
edituser(userlink) {
|
|
||||||
|
|
||||||
sessionStorage.setItem("edituser", userlink);
|
|
||||||
|
|
||||||
this.$router.push( "/edituser");
|
|
||||||
},
|
|
||||||
deluser(selfLink){
|
|
||||||
var userxhttp = new XMLHttpRequest();
|
|
||||||
userxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 204) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
userxhttp.open("DELETE", selfLink , false);
|
|
||||||
|
|
||||||
userxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
userxhttp.send(null);
|
|
||||||
},
|
|
||||||
showAccounts(username, uid) {
|
|
||||||
sessionStorage.setItem("timeTrackAccountListUser", username);
|
|
||||||
sessionStorage.setItem("timeTrackAccountListUserId", uid);
|
|
||||||
|
|
||||||
this.$router.push("/timetrackaccounts");
|
|
||||||
},
|
|
||||||
nextPage() {
|
|
||||||
var pageTMP = parseInt(this.page) + 1;
|
|
||||||
sessionStorage.setItem("page", pageTMP);
|
|
||||||
location.reload();
|
|
||||||
},
|
|
||||||
prevPage() {
|
|
||||||
var pageTMP = parseInt(this.page) - 1;
|
|
||||||
sessionStorage.setItem("page", pageTMP);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var userLinks;
|
|
||||||
if (this.page == null) {
|
|
||||||
this.page = 0;
|
|
||||||
}
|
|
||||||
sessionStorage.removeItem("page");
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var usersInformation = JSON.parse(xhttp.responseText);
|
|
||||||
sessionStorage.setItem(
|
|
||||||
"users",
|
|
||||||
JSON.stringify(usersInformation._embedded.users)
|
|
||||||
);
|
|
||||||
userLinks = usersInformation._links;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("GET", baseUri + "/users?page=" +this.page, false);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
if (userLinks.next != undefined) {
|
|
||||||
this.haveNextPage = true;
|
|
||||||
}
|
|
||||||
if (userLinks.prev != undefined) {
|
|
||||||
this.havePrevPage = true;
|
|
||||||
}
|
|
||||||
this.users = JSON.parse(sessionStorage.getItem("users"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
117
frontend/src/views/admin/EditUser.vue
Normal file
117
frontend/src/views/admin/EditUser.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-card align-center>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<p class="text-center logowhite--text" style="font-size:30pt">Edit the user "{{username}}"</p>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
<v-card align-center>
|
||||||
|
<p class="text-center logowhite--text" style="font-size:30pt">User Information</p>
|
||||||
|
<v-form>
|
||||||
|
<v-text-field :disabled="!userdataReady" color="primary" label="firstname" name="firstname"
|
||||||
|
prepend-icon="mdi-account" type="text"
|
||||||
|
v-model="userdata.firstname"/>
|
||||||
|
<v-text-field :disabled="!userdataReady" color="primary" label="lastname" name="lastname"
|
||||||
|
prepend-icon="mdi-account" type="text"
|
||||||
|
v-model="userdata.lastname"/>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
<p>{{errorText.userdata}}</p>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<v-btn @click="editUserInformation" color="logowhite" dark outlined rounded>Edit</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
<v-card align-center>
|
||||||
|
<p class="text-center logowhite--text" style="font-size:30pt">Location</p>
|
||||||
|
|
||||||
|
<v-form>
|
||||||
|
<v-text-field :disabled="!locationdataReady" color="primary" label="latitude" name="latitude"
|
||||||
|
prepend-icon="mdi-map-marker" type="text"
|
||||||
|
v-model="locationdata.latitude"/>
|
||||||
|
<v-text-field :disabled="!locationdataReady" color="primary" label="longitude" name="longitude"
|
||||||
|
prepend-icon="mdi-map-marker" type="text"
|
||||||
|
v-model="locationdata.longitude"/>
|
||||||
|
<v-text-field :disabled="!locationdataReady" color="primary" label="radius" name="radius"
|
||||||
|
prepend-icon="mdi-map-marker-radius" type="text"
|
||||||
|
v-model="locationdata.radius"/>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
<p>{{errorText.locationdata}}</p>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<v-btn @click="editLocation" color="logowhite" dark outlined rounded>Edit</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {BASE_URI} from "../../globals"
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Edituser",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userdataReady: false,
|
||||||
|
locationdataReady: false,
|
||||||
|
username: "",
|
||||||
|
errorText: {
|
||||||
|
userdata: "",
|
||||||
|
locationdata: ""
|
||||||
|
},
|
||||||
|
userdata: {
|
||||||
|
firstname: "",
|
||||||
|
lastname: "",
|
||||||
|
},
|
||||||
|
locationdata: {
|
||||||
|
longitude: "",
|
||||||
|
latitude: "",
|
||||||
|
radius: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async editUserInformation() {
|
||||||
|
if (Object.values(this.userdata).every(data => data)) {
|
||||||
|
await axios.patch(sessionStorage.getItem("edituser"), this.userdata)
|
||||||
|
await this.$router.push("/users")
|
||||||
|
} else {
|
||||||
|
this.errorText.userdata = "Please fill out all fields."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async editLocation() {
|
||||||
|
if (Object.values(this.locationdata).every(data => data)) {
|
||||||
|
let newLocation = await axios.post(BASE_URI + "/locations", this.locationdata)
|
||||||
|
await axios.patch(sessionStorage.getItem("edituser"), {
|
||||||
|
location: newLocation.data._links.self.href
|
||||||
|
})
|
||||||
|
await this.$router.push("/users")
|
||||||
|
} else {
|
||||||
|
this.errorText.locationdata = "Please fill out all fields with numeric values."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
try {
|
||||||
|
// Fill userdata
|
||||||
|
let user = await axios.get(sessionStorage.getItem("edituser"))
|
||||||
|
sessionStorage.setItem("usernameedit", user.data.username)
|
||||||
|
this.username = user.data.username
|
||||||
|
this.userdata.firstname = user.data.firstname
|
||||||
|
this.userdata.lastname = user.data.lastname
|
||||||
|
this.userdataReady = true
|
||||||
|
|
||||||
|
// fill locationdata
|
||||||
|
let locationResponse = await axios.get(user.data._links.location.href)
|
||||||
|
let {latitude, longitude, radius} = locationResponse.data
|
||||||
|
this.locationdata = {latitude, longitude, radius}
|
||||||
|
this.locationdataReady = true
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response.status === 404) {
|
||||||
|
console.log("User doesn't have a location.")
|
||||||
|
this.locationdata = {latitude: 0, longitude: 0, radius: 0}
|
||||||
|
this.locationdataReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
71
frontend/src/views/admin/Users.vue
Normal file
71
frontend/src/views/admin/Users.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<div v-bind:key="user.username" v-for="user in users">
|
||||||
|
<UsersItems v-bind:user="user" v-on:del-user="deluser" v-on:edit-user="edituser"
|
||||||
|
v-on:show-accounts="showAccounts"/>
|
||||||
|
</div>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="loadPage(prevPage)" v-if="prevPage">
|
||||||
|
<v-icon>mdi-arrow-left</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="loadPage(nextPage)" v-if="nextPage">
|
||||||
|
<v-icon>mdi-arrow-right</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {BASE_URI} from "../../globals.js"
|
||||||
|
import UsersItems from "./UsersItems.vue"
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
UsersItems
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
users: JSON.parse(sessionStorage.getItem("users")),
|
||||||
|
dialog: false,
|
||||||
|
page: sessionStorage.getItem("page"),
|
||||||
|
nextPage: "",
|
||||||
|
prevPage: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
edituser(userlink) {
|
||||||
|
sessionStorage.setItem("edituser", userlink);
|
||||||
|
this.$router.push("/edituser");
|
||||||
|
},
|
||||||
|
async deluser(selfLink) {
|
||||||
|
await axios.delete(selfLink)
|
||||||
|
location.reload()
|
||||||
|
},
|
||||||
|
showAccounts(username, uid) {
|
||||||
|
sessionStorage.setItem("timeTrackAccountListUser", username);
|
||||||
|
sessionStorage.setItem("timeTrackAccountListUserId", uid);
|
||||||
|
this.$router.push("/timetrackaccounts");
|
||||||
|
},
|
||||||
|
async loadPage(url) {
|
||||||
|
let usersResponse = await axios.get(url)
|
||||||
|
this.users = usersResponse.data._embedded?.users
|
||||||
|
this.nextPage = usersResponse.data._links?.next?.href
|
||||||
|
this.prevPage = usersResponse.data._links?.prev?.href
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
await this.loadPage(BASE_URI + "/users")
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response.status === 403) await this.$router.push("/") // Solve later with location guard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -2,13 +2,12 @@
|
|||||||
<v-card outlined>
|
<v-card outlined>
|
||||||
<v-list-item class="main_accent">
|
<v-list-item class="main_accent">
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-row no-gutters align="center">
|
<v-row align="center" no-gutters>
|
||||||
<v-col cols="1">
|
<v-col cols="1">
|
||||||
<v-avatar>
|
<v-avatar>
|
||||||
<img src="https://cdn.vuetifyjs.com/images/john.jpg" alt="John" />
|
<img alt="John" src="https://cdn.vuetifyjs.com/images/john.jpg"/>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="3">
|
<v-col cols="3">
|
||||||
<v-card color="background" elevation="0">
|
<v-card color="background" elevation="0">
|
||||||
<pre><v-icon color="primary">mdi-account</v-icon>{{" " + user.username}}</pre>
|
<pre><v-icon color="primary">mdi-account</v-icon>{{" " + user.username}}</pre>
|
||||||
@ -20,31 +19,24 @@
|
|||||||
<pre><v-icon color="primary">mdi-account-box</v-icon>{{" " + user.firstname + " " +user.lastname}}</pre>
|
<pre><v-icon color="primary">mdi-account-box</v-icon>{{" " + user.firstname + " " +user.lastname}}</pre>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col></v-col>
|
<v-col cols="4"></v-col>
|
||||||
<v-col cols="2"></v-col>
|
|
||||||
<v-col></v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-speed-dial
|
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="editButton">
|
||||||
v-model="fab"
|
|
||||||
transition="slide-x-reverse-transition"
|
|
||||||
direction="left"
|
|
||||||
open-on-hover
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
<template v-slot:activator>
|
||||||
<v-btn v-model="fab" color="background" elevation="0" dark fab>
|
<v-btn color="background" dark elevation="0" fab v-model="editButton">
|
||||||
<v-icon v-if="fab">mdi-close</v-icon>
|
<v-icon v-if="editButton">mdi-close</v-icon>
|
||||||
<v-icon v-else>mdi-pencil</v-icon>
|
<v-icon v-else>mdi-pencil</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-btn fab dark small color="primary" @click="$emit('show-accounts', user.username, user._links.self.href)">
|
<v-btn @click="$emit('show-accounts', user.username, user._links.self.href)" color="primary" dark fab small>
|
||||||
<v-icon>mdi-account-details</v-icon>
|
<v-icon>mdi-account-details</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn fab dark small color="green" @click="$emit('edit-user', user._links.self.href)">
|
<v-btn @click="$emit('edit-user', user._links.self.href)" color="green" dark fab small>
|
||||||
<v-icon>mdi-file-document-edit</v-icon>
|
<v-icon>mdi-file-document-edit</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn fab dark small color="red" @click="$emit('del-user', user._links.self.href)">
|
<v-btn @click="$emit('del-user', user._links.self.href)" color="red" dark fab small>
|
||||||
<v-icon>mdi-delete</v-icon>
|
<v-icon>mdi-delete</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
@ -54,26 +46,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "UsersItems",
|
name: "UsersItems",
|
||||||
props: ["user"],
|
props: ["user"],
|
||||||
data: () => ({
|
data() {
|
||||||
fab: undefined,
|
return {
|
||||||
}),
|
editButton: false
|
||||||
methods: {
|
}
|
||||||
getUid(hrefTmp) {
|
|
||||||
var parts = hrefTmp.split("/");
|
|
||||||
return parts[4];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.v-card {
|
.v-card {
|
||||||
border-color: #131313 !important;
|
border-color: #131313 !important;
|
||||||
border-width: 3px !important;
|
border-width: 3px !important;
|
||||||
border-radius: 10000px !important;
|
border-radius: 10000px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,235 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card color="main_accent" outlined>
|
|
||||||
<div>
|
|
||||||
<p></p>
|
|
||||||
<h3 align="center">Times by type PAID and TimetrackAccount</h3>
|
|
||||||
<div id="chart">
|
|
||||||
<apexchart class="ma-5" type="donut" width="100%" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueApexCharts from "vue-apexcharts";
|
|
||||||
import { baseUri } from "../../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
apexchart: VueApexCharts
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
chartOptions: function() {
|
|
||||||
return {
|
|
||||||
labels: ["Working hours", "Pause hours"],
|
|
||||||
chart: {
|
|
||||||
type: "donut",
|
|
||||||
background: "#202020",
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
tools: {
|
|
||||||
download: true,
|
|
||||||
selection: true,
|
|
||||||
zoom: false,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: false,
|
|
||||||
customIcons: []
|
|
||||||
},
|
|
||||||
autoSelected: "zoom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
followCursor: true,
|
|
||||||
fillSeriesColor: false,
|
|
||||||
x: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: value => {
|
|
||||||
var revenueTMP = 0;
|
|
||||||
for (let index = 0; index < this.series.length; index++) {
|
|
||||||
if (value == this.series[index]) {
|
|
||||||
revenueTMP = this.revenue[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revenueTMP = (revenueTMP / 60) * value * 1.0;
|
|
||||||
revenueTMP = Math.round(revenueTMP * 100) / 100;
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
hours +
|
|
||||||
":" +
|
|
||||||
minutes +
|
|
||||||
":" +
|
|
||||||
seconds +
|
|
||||||
" with " +
|
|
||||||
revenueTMP +
|
|
||||||
"$ revenue."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: [
|
|
||||||
"#0096ff",
|
|
||||||
"#e21d1f",
|
|
||||||
"#03ac13",
|
|
||||||
"#800080",
|
|
||||||
"#f9d71c",
|
|
||||||
"ff1694"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
mode: "dark"
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
position: "left",
|
|
||||||
offsetY: 40
|
|
||||||
},
|
|
||||||
|
|
||||||
responsive: [
|
|
||||||
{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
chart: {
|
|
||||||
width: 300
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: "bottom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
series: [0, 0],
|
|
||||||
revenue: [0, 0]
|
|
||||||
}),
|
|
||||||
created() {
|
|
||||||
// Get all Timetrack accounts for the current user
|
|
||||||
var accounts;
|
|
||||||
var timeTrackAccountsTMP;
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
accounts = JSON.parse(accountxhttp.responseText);
|
|
||||||
timeTrackAccountsTMP = accounts._embedded.accounts;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsername?username=" +
|
|
||||||
sessionStorage.getItem("username"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
|
|
||||||
this.chartOptions.labels = new Array(timeTrackAccountsTMP.length);
|
|
||||||
|
|
||||||
this.series = new Array(timeTrackAccountsTMP.length);
|
|
||||||
this.revenue = new Array(timeTrackAccountsTMP.length);
|
|
||||||
for (let index = 0; index < this.series.length; index++) {
|
|
||||||
this.series[index] = 0;
|
|
||||||
this.revenue[index] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < timeTrackAccountsTMP.length; index++) {
|
|
||||||
this.chartOptions.labels[index] = timeTrackAccountsTMP[index].name;
|
|
||||||
this.revenue[index] = timeTrackAccountsTMP[index].revenue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get and sort all Records to the accounts
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records = new Array(0);
|
|
||||||
var hasNext = true;
|
|
||||||
var page = 0;
|
|
||||||
|
|
||||||
while (hasNext) {
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
recordsDB._embedded.records.forEach(tmpRecord => {
|
|
||||||
records.push(tmpRecord);
|
|
||||||
});
|
|
||||||
page++;
|
|
||||||
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.status == 403) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allForUser?username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&projection=overview" +
|
|
||||||
"&page=" +
|
|
||||||
page +
|
|
||||||
"&size=50",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
for (
|
|
||||||
let indexAccs = 0;
|
|
||||||
indexAccs < this.chartOptions.labels.length;
|
|
||||||
indexAccs++
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
record.account == this.chartOptions.labels[indexAccs] &&
|
|
||||||
record.type == "PAID"
|
|
||||||
) {
|
|
||||||
this.series[indexAccs] += record.duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.v-card {
|
|
||||||
border-radius: 20px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,246 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card color="main_accent" outlined>
|
|
||||||
<div>
|
|
||||||
<p></p>
|
|
||||||
<h3 align="center">My last 30 Days</h3>
|
|
||||||
<div id="chart">
|
|
||||||
<apexchart class="ma-5" type="bar" height="350" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueApexCharts from "vue-apexcharts";
|
|
||||||
import { baseUri } from "../../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "MonthSummary",
|
|
||||||
components: {
|
|
||||||
apexchart: VueApexCharts
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: "Working Hours",
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pause Hours",
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chartOptions: {
|
|
||||||
chart: {
|
|
||||||
type: "bar",
|
|
||||||
stacked: true,
|
|
||||||
background: "#202020",
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
tools: {
|
|
||||||
download: true,
|
|
||||||
selection: true,
|
|
||||||
zoom: false,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: false,
|
|
||||||
customIcons: []
|
|
||||||
},
|
|
||||||
autoSelected: "zoom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
|
||||||
theme: {
|
|
||||||
mode: "dark"
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
followCursor: true,
|
|
||||||
x: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: function(value) {
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: [
|
|
||||||
{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
legend: {
|
|
||||||
position: "bottom",
|
|
||||||
offsetX: -10,
|
|
||||||
offsetY: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
plotOptions: {
|
|
||||||
bar: {
|
|
||||||
horizontal: false,
|
|
||||||
columnWidth: "50%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
type: "datetime",
|
|
||||||
categories: []
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
labels: {
|
|
||||||
formatter: function(value) {
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
position: "left",
|
|
||||||
offsetY: 40
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var daysToDisplay = 31;
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records = new Array(0);
|
|
||||||
var hasNext = true;
|
|
||||||
var page = 0;
|
|
||||||
|
|
||||||
this.chartOptions.xaxis.categories = new Array(daysToDisplay);
|
|
||||||
this.series[0].data = new Array(daysToDisplay);
|
|
||||||
this.series[1].data = new Array(daysToDisplay);
|
|
||||||
|
|
||||||
for (let index = 0; index < daysToDisplay; index++) {
|
|
||||||
this.series[0].data[index] = 0;
|
|
||||||
this.series[1].data[index] = 0;
|
|
||||||
}
|
|
||||||
var today = new Date();
|
|
||||||
var dd = String(today.getDate()).padStart(2, "0");
|
|
||||||
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
|
|
||||||
var yyyy = today.getFullYear();
|
|
||||||
|
|
||||||
for (let index = 0; index < daysToDisplay; index++) {
|
|
||||||
var lastSeven = new Date();
|
|
||||||
lastSeven.setDate(today.getDate() - index);
|
|
||||||
var ddS = String(lastSeven.getDate()).padStart(2, "0");
|
|
||||||
var mmS = String(lastSeven.getMonth() + 1).padStart(2, "0"); //January is 0!
|
|
||||||
var yyyyS = lastSeven.getFullYear();
|
|
||||||
this.chartOptions.xaxis.categories[index] = yyyyS + "-" + mmS + "-" + ddS;
|
|
||||||
}
|
|
||||||
today = yyyy + "-" + mm + "-" + dd;
|
|
||||||
|
|
||||||
while (hasNext) {
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
recordsDB._embedded.records.forEach(tmpRecord => {
|
|
||||||
records.push(tmpRecord);
|
|
||||||
});
|
|
||||||
page++;
|
|
||||||
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.status == 403) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allBetweenAndUser?start=" +
|
|
||||||
this.chartOptions.xaxis.categories[daysToDisplay - 1] +
|
|
||||||
"T00:00:01" +
|
|
||||||
"&end=" +
|
|
||||||
today +
|
|
||||||
"T23:59:59" +
|
|
||||||
"&username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&page=" +
|
|
||||||
page +
|
|
||||||
"&size=50",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
|
|
||||||
for (let indexA = 0; indexA < daysToDisplay; indexA++) {
|
|
||||||
if (
|
|
||||||
record.startdate.split("T")[0] ==
|
|
||||||
this.chartOptions.xaxis.categories[indexA]
|
|
||||||
) {
|
|
||||||
if (record.type == "PAID") {
|
|
||||||
this.series[0].data[indexA] += record.duration;
|
|
||||||
} else {
|
|
||||||
this.series[1].data[indexA] += record.duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dirty fix for known bug from apexcharts
|
|
||||||
this.series[0].data[daysToDisplay - 1] = 0;
|
|
||||||
this.series[1].data[daysToDisplay - 1] = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.v-card {
|
|
||||||
border-radius: 20px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,202 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card color="main_accent" outlined>
|
|
||||||
<div>
|
|
||||||
<p></p>
|
|
||||||
<h3 align="center">Revenue by TimeTrack Account</h3>
|
|
||||||
<div id="chart">
|
|
||||||
<apexchart class="ma-5" type="donut" width="100%" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueApexCharts from "vue-apexcharts";
|
|
||||||
import { baseUri } from "../../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
apexchart: VueApexCharts
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
chartOptions: function() {
|
|
||||||
return {
|
|
||||||
labels: ["Working hours", "Pause hours"],
|
|
||||||
chart: {
|
|
||||||
type: "donut",
|
|
||||||
background: "#202020",
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
tools: {
|
|
||||||
download: true,
|
|
||||||
selection: true,
|
|
||||||
zoom: false,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: false,
|
|
||||||
customIcons: []
|
|
||||||
},
|
|
||||||
autoSelected: "zoom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
followCursor: true,
|
|
||||||
fillSeriesColor: false,
|
|
||||||
x: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: value => {
|
|
||||||
return Math.round(value * 100) / 100 + "$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: [
|
|
||||||
"#0096ff",
|
|
||||||
"#e21d1f",
|
|
||||||
"#03ac13",
|
|
||||||
"#800080",
|
|
||||||
"#f9d71c",
|
|
||||||
"ff1694"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
mode: "dark"
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
position: "left",
|
|
||||||
offsetY: 40
|
|
||||||
},
|
|
||||||
|
|
||||||
responsive: [
|
|
||||||
{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
chart: {
|
|
||||||
width: 300
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: "bottom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
series: [0, 0],
|
|
||||||
revenue: [0, 0]
|
|
||||||
}),
|
|
||||||
created() {
|
|
||||||
// Get all Timetrack accounts for the current user
|
|
||||||
var accounts;
|
|
||||||
var timeTrackAccountsTMP;
|
|
||||||
var accountxhttp = new XMLHttpRequest();
|
|
||||||
accountxhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
accounts = JSON.parse(accountxhttp.responseText);
|
|
||||||
timeTrackAccountsTMP = accounts._embedded.accounts;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
accountxhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/accounts/search/findByUsername?username=" +
|
|
||||||
sessionStorage.getItem("username"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.setRequestHeader(
|
|
||||||
"Authorization",
|
|
||||||
sessionStorage.getItem("jwt")
|
|
||||||
);
|
|
||||||
|
|
||||||
accountxhttp.send(null);
|
|
||||||
|
|
||||||
this.chartOptions.labels = new Array(timeTrackAccountsTMP.length);
|
|
||||||
|
|
||||||
this.series = new Array(timeTrackAccountsTMP.length);
|
|
||||||
this.revenue = new Array(timeTrackAccountsTMP.length);
|
|
||||||
for (let index = 0; index < this.series.length; index++) {
|
|
||||||
this.series[index] = 0;
|
|
||||||
this.revenue[index] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < timeTrackAccountsTMP.length; index++) {
|
|
||||||
this.chartOptions.labels[index] = timeTrackAccountsTMP[index].name;
|
|
||||||
this.revenue[index] = timeTrackAccountsTMP[index].revenue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get and sort all Records to the accounts
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records = new Array(0);
|
|
||||||
var hasNext = true;
|
|
||||||
var page = 0;
|
|
||||||
|
|
||||||
while (hasNext) {
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
recordsDB._embedded.records.forEach(tmpRecord => {
|
|
||||||
records.push(tmpRecord);
|
|
||||||
});
|
|
||||||
page++;
|
|
||||||
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.status == 403) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allForUser?username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&projection=overview" +
|
|
||||||
"&page=" +
|
|
||||||
page +
|
|
||||||
"&size=50",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
for (
|
|
||||||
let indexAccs = 0;
|
|
||||||
indexAccs < this.chartOptions.labels.length;
|
|
||||||
indexAccs++
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
record.account == this.chartOptions.labels[indexAccs] &&
|
|
||||||
record.type == "PAID"
|
|
||||||
) {
|
|
||||||
this.series[indexAccs] += record.duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < this.series.length; index++) {
|
|
||||||
this.series[index] = this.series[index] * this.revenue[index] /60;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.v-card {
|
|
||||||
border-radius: 20px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,247 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card color="main_accent" outlined>
|
|
||||||
<div>
|
|
||||||
<p></p>
|
|
||||||
<h3 align="center">My last 7 Days</h3>
|
|
||||||
<div id="chart">
|
|
||||||
<apexchart class="ma-5" type="bar" height="350" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueApexCharts from "vue-apexcharts";
|
|
||||||
import { baseUri } from "../../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "WeekSummary",
|
|
||||||
components: {
|
|
||||||
apexchart: VueApexCharts
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: "Working Hours",
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pause Hours",
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chartOptions: {
|
|
||||||
chart: {
|
|
||||||
type: "bar",
|
|
||||||
stacked: true,
|
|
||||||
background: "#202020",
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
tools: {
|
|
||||||
download: true,
|
|
||||||
selection: true,
|
|
||||||
zoom: false,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: false,
|
|
||||||
customIcons: []
|
|
||||||
},
|
|
||||||
autoSelected: "zoom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
|
||||||
theme: {
|
|
||||||
mode: "dark"
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
followCursor: true,
|
|
||||||
x: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: function(value) {
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: [
|
|
||||||
{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
legend: {
|
|
||||||
position: "bottom",
|
|
||||||
offsetX: -10,
|
|
||||||
offsetY: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
plotOptions: {
|
|
||||||
bar: {
|
|
||||||
horizontal: false,
|
|
||||||
columnWidth: "30%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
type: "datetime",
|
|
||||||
categories: []
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
labels: {
|
|
||||||
formatter: function(value) {
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
position: "left",
|
|
||||||
offsetY: 40
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
var daysToDisplay = 8;
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records = new Array(0);
|
|
||||||
var hasNext = true;
|
|
||||||
var page = 0;
|
|
||||||
|
|
||||||
this.chartOptions.xaxis.categories = new Array(daysToDisplay);
|
|
||||||
this.series[0].data = new Array(daysToDisplay);
|
|
||||||
this.series[1].data = new Array(daysToDisplay);
|
|
||||||
|
|
||||||
for (let index = 0; index < daysToDisplay; index++) {
|
|
||||||
this.series[0].data[index] = 0;
|
|
||||||
this.series[1].data[index] = 0;
|
|
||||||
}
|
|
||||||
var today = new Date();
|
|
||||||
var dd = String(today.getDate()).padStart(2, "0");
|
|
||||||
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
|
|
||||||
var yyyy = today.getFullYear();
|
|
||||||
|
|
||||||
for (let index = 0; index < daysToDisplay; index++) {
|
|
||||||
var lastSeven = new Date();
|
|
||||||
lastSeven.setDate(today.getDate() - index);
|
|
||||||
var ddS = String(lastSeven.getDate()).padStart(2, "0");
|
|
||||||
var mmS = String(lastSeven.getMonth() + 1).padStart(2, "0"); //January is 0!
|
|
||||||
var yyyyS = lastSeven.getFullYear();
|
|
||||||
this.chartOptions.xaxis.categories[index] = yyyyS + "-" + mmS + "-" + ddS;
|
|
||||||
}
|
|
||||||
today = yyyy + "-" + mm + "-" + dd;
|
|
||||||
|
|
||||||
|
|
||||||
while (hasNext) {
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
recordsDB._embedded.records.forEach(tmpRecord => {
|
|
||||||
records.push(tmpRecord);
|
|
||||||
});
|
|
||||||
page++;
|
|
||||||
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.status == 403) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allBetweenAndUser?start=" +
|
|
||||||
this.chartOptions.xaxis.categories[daysToDisplay - 1] +
|
|
||||||
"T00:00:01" +
|
|
||||||
"&end=" +
|
|
||||||
today +
|
|
||||||
"T23:59:59" +
|
|
||||||
"&username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&page=" +
|
|
||||||
page +
|
|
||||||
"&size=50",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
|
|
||||||
for (let indexA = 0; indexA < daysToDisplay; indexA++) {
|
|
||||||
if (
|
|
||||||
record.startdate.split("T")[0] ==
|
|
||||||
this.chartOptions.xaxis.categories[indexA]
|
|
||||||
) {
|
|
||||||
if (record.type == "PAID") {
|
|
||||||
this.series[0].data[indexA] += record.duration;
|
|
||||||
} else {
|
|
||||||
this.series[1].data[indexA] += record.duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dirty fix for known bug from apexcharts
|
|
||||||
this.series[0].data[daysToDisplay - 1] = 0;
|
|
||||||
this.series[1].data[daysToDisplay - 1] = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.v-card {
|
|
||||||
border-radius: 20px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,166 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card color="main_accent" outlined>
|
|
||||||
<div>
|
|
||||||
<p></p>
|
|
||||||
<h3 align="center">Times by type PAID and BREAK</h3>
|
|
||||||
<div id="chart">
|
|
||||||
<apexchart class="ma-5" type="donut" width="100%" :options="chartOptions" :series="series"></apexchart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueApexCharts from "vue-apexcharts";
|
|
||||||
import { baseUri } from "../../variables.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
apexchart: VueApexCharts
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
series: [0, 0],
|
|
||||||
chartOptions: {
|
|
||||||
labels: ["Working hours", "Pause hours"],
|
|
||||||
chart: {
|
|
||||||
type: "donut",
|
|
||||||
background: "#202020",
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
offsetX: 0,
|
|
||||||
offsetY: 0,
|
|
||||||
tools: {
|
|
||||||
download: true,
|
|
||||||
selection: true,
|
|
||||||
zoom: false,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: false,
|
|
||||||
customIcons: []
|
|
||||||
},
|
|
||||||
autoSelected: "zoom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
followCursor: true,
|
|
||||||
fillSeriesColor: false,
|
|
||||||
x: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
formatter: function(value) {
|
|
||||||
var decimalTimeString = value;
|
|
||||||
var decimalTime = parseFloat(decimalTimeString);
|
|
||||||
decimalTime = decimalTime * 60;
|
|
||||||
var hours = Math.floor(decimalTime / (60 * 60));
|
|
||||||
|
|
||||||
decimalTime = decimalTime - hours * 60 * 60;
|
|
||||||
var minutes = Math.floor(decimalTime / 60);
|
|
||||||
|
|
||||||
decimalTime = decimalTime - minutes * 60;
|
|
||||||
var seconds = Math.round(decimalTime);
|
|
||||||
|
|
||||||
if (hours < 10) {
|
|
||||||
hours = "0" + hours;
|
|
||||||
}
|
|
||||||
if (minutes < 10) {
|
|
||||||
minutes = "0" + minutes;
|
|
||||||
}
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = "0" + seconds;
|
|
||||||
}
|
|
||||||
return hours + ":" + minutes + ":" + seconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
|
||||||
theme: {
|
|
||||||
mode: "dark"
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
position: "left",
|
|
||||||
offsetY: 40
|
|
||||||
},
|
|
||||||
|
|
||||||
responsive: [
|
|
||||||
{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
chart: {
|
|
||||||
width: 300
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: "bottom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
created() {
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
var records = new Array(0);
|
|
||||||
var hasNext = true;
|
|
||||||
var page = 0;
|
|
||||||
|
|
||||||
while (hasNext) {
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var recordsDB = JSON.parse(xhttp.responseText);
|
|
||||||
recordsDB._embedded.records.forEach(tmpRecord => {
|
|
||||||
records.push(tmpRecord)
|
|
||||||
});
|
|
||||||
page++;
|
|
||||||
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.status == 403) {
|
|
||||||
hasNext = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open(
|
|
||||||
"GET",
|
|
||||||
baseUri +
|
|
||||||
"/records/search/allForUser?username=" +
|
|
||||||
sessionStorage.getItem("username") +
|
|
||||||
"&page=" +
|
|
||||||
page +
|
|
||||||
"&projection=overview" +
|
|
||||||
"&size=50",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
|
||||||
|
|
||||||
xhttp.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var paidTime = 0;
|
|
||||||
var breakTime = 0;
|
|
||||||
|
|
||||||
for (let index = 0; index < records.length; index++) {
|
|
||||||
var record = records[index];
|
|
||||||
var type = record.type + "";
|
|
||||||
|
|
||||||
if (type == "PAID") {
|
|
||||||
paidTime += record.duration;
|
|
||||||
} else {
|
|
||||||
breakTime += record.duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.series[0] = paidTime;
|
|
||||||
this.series[1] = breakTime;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.v-card {
|
|
||||||
border-radius: 20px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
20
frontend/src/views/statistics/ApexFormatters.js
Normal file
20
frontend/src/views/statistics/ApexFormatters.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const timeFormatter = value => {
|
||||||
|
let decimalTime = parseFloat(value);
|
||||||
|
|
||||||
|
decimalTime = decimalTime * 60;
|
||||||
|
let hours = Math.floor(decimalTime / (60 * 60));
|
||||||
|
|
||||||
|
decimalTime = decimalTime - hours * 60 * 60;
|
||||||
|
let minutes = Math.floor(decimalTime / 60);
|
||||||
|
|
||||||
|
decimalTime = decimalTime - minutes * 60;
|
||||||
|
let seconds = Math.round(decimalTime);
|
||||||
|
|
||||||
|
hours = hours > 10 ? hours : "0" + hours
|
||||||
|
minutes = minutes > 10 ? minutes : "0" + minutes
|
||||||
|
seconds = seconds > 10 ? seconds : "0" + seconds
|
||||||
|
|
||||||
|
return (hours + ":" + minutes + ":" + seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {timeFormatter};
|
75
frontend/src/views/statistics/StatisticOverview.vue
Normal file
75
frontend/src/views/statistics/StatisticOverview.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-row class="ma-5">
|
||||||
|
<v-col cols="4">
|
||||||
|
<work-pause-pie/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4">
|
||||||
|
<account-pie/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4">
|
||||||
|
<revenue-pie/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row class="ma-5">
|
||||||
|
<v-col cols="12">
|
||||||
|
<month-summary/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row class="ma-5">
|
||||||
|
<v-col cols="12">
|
||||||
|
<week-summary :records="records"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import RevenuePie from "./charts/RevenuePie.vue"
|
||||||
|
import WeekSummary from "./charts/WeekSummary.vue";
|
||||||
|
import MonthSummary from "./charts/MonthSummary.vue";
|
||||||
|
import WorkPausePie from "./charts/WorkPausePie.vue";
|
||||||
|
import AccountPie from "./charts/AccountPie.vue";
|
||||||
|
import axios from "axios"
|
||||||
|
import {BASE_URI} from "../../globals"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "StatisticOverview",
|
||||||
|
components: {
|
||||||
|
RevenuePie,
|
||||||
|
WeekSummary,
|
||||||
|
MonthSummary,
|
||||||
|
WorkPausePie,
|
||||||
|
AccountPie
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
accounts: [],
|
||||||
|
records: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let recordsResponse
|
||||||
|
let link = BASE_URI + "/records/search/allForUser?"
|
||||||
|
+ "username=" + sessionStorage.getItem("username") + "&"
|
||||||
|
+ "projection=overview"
|
||||||
|
do {
|
||||||
|
recordsResponse = await axios.get(link)
|
||||||
|
link = recordsResponse.data._links?.next?.href
|
||||||
|
this.records.push(...recordsResponse.data._embedded.records)
|
||||||
|
} while (link)
|
||||||
|
|
||||||
|
let accountsResponse
|
||||||
|
link = BASE_URI + "/accounts/search/findByUsername?"
|
||||||
|
+ "username=" + sessionStorage.getItem("username")
|
||||||
|
do {
|
||||||
|
accountsResponse = await axios.get(link)
|
||||||
|
link = accountsResponse.data._links?.next?.href
|
||||||
|
this.accounts.push(...accountsResponse.data._embedded.accounts)
|
||||||
|
} while(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
177
frontend/src/views/statistics/charts/AccountPie.vue
Normal file
177
frontend/src/views/statistics/charts/AccountPie.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<v-card color="main_accent" outlined>
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
<h3 align="center">Times by type PAID and TimetrackAccount</h3>
|
||||||
|
<div id="chart">
|
||||||
|
<apexchart :options="chartOptions" :series="series" class="ma-5" type="donut" width="100%"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from "vue-apexcharts";
|
||||||
|
import {BASE_URI} from "../../../globals.js";
|
||||||
|
import {timeFormatter} from "../ApexFormatters";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
apexchart: VueApexCharts
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
chartOptions: function () {
|
||||||
|
return {
|
||||||
|
labels: ["Working hours", "Pause hours"],
|
||||||
|
chart: {
|
||||||
|
type: "donut",
|
||||||
|
background: "#202020",
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
tools: {
|
||||||
|
download: true,
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: false,
|
||||||
|
customIcons: []
|
||||||
|
},
|
||||||
|
autoSelected: "zoom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
followCursor: true,
|
||||||
|
fillSeriesColor: false,
|
||||||
|
x: {show: false},
|
||||||
|
y: {
|
||||||
|
formatter: value => {
|
||||||
|
let idxInRevenue = this.series.findIndex(revenue => revenue === value)
|
||||||
|
let revenue = (this.revenue[idxInRevenue] / 60) * value;
|
||||||
|
revenue = Math.ceil(revenue);
|
||||||
|
return (timeFormatter(value) + " with " + revenue + "$ revenue.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#0096ff", "#e21d1f", "#03ac13", "#800080", "#f9d71c", "#ff1694"],
|
||||||
|
theme: {mode: "dark"},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: "left",
|
||||||
|
offsetY: 40
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
series: [0, 0],
|
||||||
|
revenue: [0, 0]
|
||||||
|
}),
|
||||||
|
created() {
|
||||||
|
// Get all Timetrack accounts for the current user
|
||||||
|
var accounts;
|
||||||
|
var timeTrackAccountsTMP;
|
||||||
|
var accountxhttp = new XMLHttpRequest();
|
||||||
|
accountxhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
accounts = JSON.parse(accountxhttp.responseText);
|
||||||
|
timeTrackAccountsTMP = accounts._embedded.accounts;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
accountxhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/accounts/search/findByUsername?username=" +
|
||||||
|
sessionStorage.getItem("username"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
accountxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
accountxhttp.send(null);
|
||||||
|
|
||||||
|
this.chartOptions.labels = new Array(timeTrackAccountsTMP.length);
|
||||||
|
|
||||||
|
this.series = new Array(timeTrackAccountsTMP.length);
|
||||||
|
this.revenue = new Array(timeTrackAccountsTMP.length);
|
||||||
|
for (let index = 0; index < this.series.length; index++) {
|
||||||
|
this.series[index] = 0;
|
||||||
|
this.revenue[index] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < timeTrackAccountsTMP.length; index++) {
|
||||||
|
this.chartOptions.labels[index] = timeTrackAccountsTMP[index].name;
|
||||||
|
this.revenue[index] = timeTrackAccountsTMP[index].revenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and sort all Records to the accounts
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var records = new Array(0);
|
||||||
|
var hasNext = true;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var recordsDB = JSON.parse(xhttp.responseText);
|
||||||
|
recordsDB._embedded.records.forEach(tmpRecord => {
|
||||||
|
records.push(tmpRecord);
|
||||||
|
});
|
||||||
|
page++;
|
||||||
|
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.status == 403) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/records/search/allForUser?username=" +
|
||||||
|
sessionStorage.getItem("username") +
|
||||||
|
"&projection=overview" +
|
||||||
|
"&page=" +
|
||||||
|
page +
|
||||||
|
"&size=50",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
xhttp.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < records.length; index++) {
|
||||||
|
var record = records[index];
|
||||||
|
for (let indexAccs = 0; indexAccs < this.chartOptions.labels.length; indexAccs++) {
|
||||||
|
if (record.account == this.chartOptions.labels[indexAccs] && record.type == "PAID") {
|
||||||
|
this.series[indexAccs] += record.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
194
frontend/src/views/statistics/charts/MonthSummary.vue
Normal file
194
frontend/src/views/statistics/charts/MonthSummary.vue
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<template>
|
||||||
|
<v-card color="main_accent" outlined>
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
<h3 align="center">My last 30 Days</h3>
|
||||||
|
<div id="chart">
|
||||||
|
<apexchart :options="chartOptions" :series="series" class="ma-5" height="350" type="bar"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from "vue-apexcharts";
|
||||||
|
import {BASE_URI} from "../../../globals.js";
|
||||||
|
import {timeFormatter} from "../ApexFormatters";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MonthSummary",
|
||||||
|
components: {
|
||||||
|
apexchart: VueApexCharts
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Working Hours",
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pause Hours",
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
chartOptions: {
|
||||||
|
chart: {
|
||||||
|
type: "bar",
|
||||||
|
stacked: true,
|
||||||
|
background: "#202020",
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
tools: {
|
||||||
|
download: true,
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: false,
|
||||||
|
customIcons: []
|
||||||
|
},
|
||||||
|
autoSelected: "zoom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
||||||
|
theme: {mode: "dark"},
|
||||||
|
dataLabels: {enabled: false},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
followCursor: true,
|
||||||
|
x: {show: true},
|
||||||
|
y: {formatter: timeFormatter}
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
offsetX: -10,
|
||||||
|
offsetY: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: false,
|
||||||
|
columnWidth: "50%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
categories: []
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {formatter: timeFormatter}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: "left",
|
||||||
|
offsetY: 40
|
||||||
|
},
|
||||||
|
fill: {opacity: 1}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
var daysToDisplay = 31;
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var records = new Array(0);
|
||||||
|
var hasNext = true;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
this.chartOptions.xaxis.categories = new Array(daysToDisplay);
|
||||||
|
this.series[0].data = new Array(daysToDisplay);
|
||||||
|
this.series[1].data = new Array(daysToDisplay);
|
||||||
|
|
||||||
|
for (let index = 0; index < daysToDisplay; index++) {
|
||||||
|
this.series[0].data[index] = 0;
|
||||||
|
this.series[1].data[index] = 0;
|
||||||
|
}
|
||||||
|
var today = new Date();
|
||||||
|
var dd = String(today.getDate()).padStart(2, "0");
|
||||||
|
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
|
||||||
|
var yyyy = today.getFullYear();
|
||||||
|
|
||||||
|
for (let index = 0; index < daysToDisplay; index++) {
|
||||||
|
var lastSeven = new Date();
|
||||||
|
lastSeven.setDate(today.getDate() - index);
|
||||||
|
var ddS = String(lastSeven.getDate()).padStart(2, "0");
|
||||||
|
var mmS = String(lastSeven.getMonth() + 1).padStart(2, "0"); //January is 0!
|
||||||
|
var yyyyS = lastSeven.getFullYear();
|
||||||
|
this.chartOptions.xaxis.categories[index] = yyyyS + "-" + mmS + "-" + ddS;
|
||||||
|
}
|
||||||
|
today = yyyy + "-" + mm + "-" + dd;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var recordsDB = JSON.parse(xhttp.responseText);
|
||||||
|
recordsDB._embedded.records.forEach(tmpRecord => {
|
||||||
|
records.push(tmpRecord);
|
||||||
|
});
|
||||||
|
page++;
|
||||||
|
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.status == 403) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/records/search/allBetweenAndUser?start=" +
|
||||||
|
this.chartOptions.xaxis.categories[daysToDisplay - 1] +
|
||||||
|
"T00:00:01" +
|
||||||
|
"&end=" +
|
||||||
|
today +
|
||||||
|
"T23:59:59" +
|
||||||
|
"&username=" +
|
||||||
|
sessionStorage.getItem("username") +
|
||||||
|
"&page=" +
|
||||||
|
page +
|
||||||
|
"&size=50",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
|
||||||
|
xhttp.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < records.length; index++) {
|
||||||
|
var record = records[index];
|
||||||
|
|
||||||
|
for (let indexA = 0; indexA < daysToDisplay; indexA++) {
|
||||||
|
if (record.startdate.split("T")[0] == this.chartOptions.xaxis.categories[indexA]) {
|
||||||
|
if (record.type == "PAID") {
|
||||||
|
this.series[0].data[indexA] += record.duration;
|
||||||
|
} else {
|
||||||
|
this.series[1].data[indexA] += record.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dirty fix for known bug from apexcharts
|
||||||
|
this.series[0].data[daysToDisplay - 1] = 0;
|
||||||
|
this.series[1].data[daysToDisplay - 1] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
173
frontend/src/views/statistics/charts/RevenuePie.vue
Normal file
173
frontend/src/views/statistics/charts/RevenuePie.vue
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<v-card color="main_accent" outlined>
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
<h3 align="center">Revenue by TimeTrack Account</h3>
|
||||||
|
<div id="chart">
|
||||||
|
<apexchart :options="chartOptions" :series="series" class="ma-5" type="donut" width="100%"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from "vue-apexcharts";
|
||||||
|
import {BASE_URI} from "../../../globals.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
apexchart: VueApexCharts
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
chartOptions: function () {
|
||||||
|
return {
|
||||||
|
labels: ["Working hours", "Pause hours"],
|
||||||
|
chart: {
|
||||||
|
type: "donut",
|
||||||
|
background: "#202020",
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
tools: {
|
||||||
|
download: true,
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: false,
|
||||||
|
customIcons: []
|
||||||
|
},
|
||||||
|
autoSelected: "zoom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
followCursor: true,
|
||||||
|
fillSeriesColor: false,
|
||||||
|
x: {show: false},
|
||||||
|
y: {formatter: value => Math.ceil(value) + "$"}
|
||||||
|
},
|
||||||
|
colors: ["#0096ff", "#e21d1f", "#03ac13", "#800080", "#f9d71c", "ff1694"],
|
||||||
|
theme: {mode: "dark"},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: "left",
|
||||||
|
offsetY: 40
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
series: [0, 0],
|
||||||
|
revenue: [0, 0]
|
||||||
|
}),
|
||||||
|
created() {
|
||||||
|
// Get all Timetrack accounts for the current user
|
||||||
|
var accounts;
|
||||||
|
var timeTrackAccountsTMP;
|
||||||
|
var accountxhttp = new XMLHttpRequest();
|
||||||
|
accountxhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
accounts = JSON.parse(accountxhttp.responseText);
|
||||||
|
timeTrackAccountsTMP = accounts._embedded.accounts;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
accountxhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/accounts/search/findByUsername?username=" +
|
||||||
|
sessionStorage.getItem("username"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
accountxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
accountxhttp.send(null);
|
||||||
|
|
||||||
|
this.chartOptions.labels = new Array(timeTrackAccountsTMP.length);
|
||||||
|
|
||||||
|
this.series = new Array(timeTrackAccountsTMP.length);
|
||||||
|
this.revenue = new Array(timeTrackAccountsTMP.length);
|
||||||
|
for (let index = 0; index < this.series.length; index++) {
|
||||||
|
this.series[index] = 0;
|
||||||
|
this.revenue[index] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < timeTrackAccountsTMP.length; index++) {
|
||||||
|
this.chartOptions.labels[index] = timeTrackAccountsTMP[index].name;
|
||||||
|
this.revenue[index] = timeTrackAccountsTMP[index].revenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and sort all Records to the accounts
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var records = new Array(0);
|
||||||
|
var hasNext = true;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var recordsDB = JSON.parse(xhttp.responseText);
|
||||||
|
recordsDB._embedded.records.forEach(tmpRecord => {
|
||||||
|
records.push(tmpRecord);
|
||||||
|
});
|
||||||
|
page++;
|
||||||
|
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.status == 403) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/records/search/allForUser?username=" +
|
||||||
|
sessionStorage.getItem("username") +
|
||||||
|
"&projection=overview" +
|
||||||
|
"&page=" +
|
||||||
|
page +
|
||||||
|
"&size=50",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
xhttp.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < records.length; index++) {
|
||||||
|
var record = records[index];
|
||||||
|
for (let indexAccs = 0; indexAccs < this.chartOptions.labels.length; indexAccs++) {
|
||||||
|
if (record.account == this.chartOptions.labels[indexAccs] && record.type == "PAID") {
|
||||||
|
this.series[indexAccs] += record.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < this.series.length; index++) {
|
||||||
|
this.series[index] = this.series[index] * this.revenue[index] / 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
214
frontend/src/views/statistics/charts/WeekSummary.vue
Normal file
214
frontend/src/views/statistics/charts/WeekSummary.vue
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
<template>
|
||||||
|
<v-card color="main_accent" outlined>
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
<h3 align="center">My last 7 Days</h3>
|
||||||
|
<div>
|
||||||
|
<apexchart ref="chart" :options="chartOptions" :series="series" class="ma-5" height="350" type="bar"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from "vue-apexcharts";
|
||||||
|
import {BASE_URI} from "../../../globals.js";
|
||||||
|
import {timeFormatter} from "../ApexFormatters";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "WeekSummary",
|
||||||
|
props: [
|
||||||
|
"records"
|
||||||
|
],
|
||||||
|
components: {
|
||||||
|
apexchart: VueApexCharts
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Working Hours",
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pause Hours",
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
chartOptions: {
|
||||||
|
chart: {
|
||||||
|
type: "bar",
|
||||||
|
stacked: true,
|
||||||
|
background: "#202020",
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
tools: {
|
||||||
|
download: true,
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: false,
|
||||||
|
customIcons: []
|
||||||
|
},
|
||||||
|
autoSelected: "zoom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
||||||
|
theme: {mode: "dark"},
|
||||||
|
dataLabels: {enabled: false},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
followCursor: true,
|
||||||
|
x: {show: false},
|
||||||
|
y: {formatter: timeFormatter}
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
offsetX: -10,
|
||||||
|
offsetY: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: false,
|
||||||
|
columnWidth: "30%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
categories: []
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {formatter: timeFormatter}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: "left",
|
||||||
|
offsetY: 40
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
let firstDate = new Date()
|
||||||
|
firstDate.setDate(firstDate.getDate() - 7)
|
||||||
|
|
||||||
|
let interestingData = this.records.filter(record => new Date(record.startdate) > firstDate)
|
||||||
|
if (interestingData.length === 0) return
|
||||||
|
let test = new Date(interestingData[0].startdate)
|
||||||
|
let dateFormat = new Intl.DateTimeFormat("de-DE", {day: "2-digit", month: "short"})
|
||||||
|
console.log(dateFormat.format(test))
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.series[1].data[4] = 6000
|
||||||
|
this.$refs.chart.refresh()
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
var daysToDisplay = 8;
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var records = new Array(0);
|
||||||
|
var hasNext = true;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
this.chartOptions.xaxis.categories = new Array(daysToDisplay);
|
||||||
|
this.series[0].data = new Array(daysToDisplay);
|
||||||
|
this.series[1].data = new Array(daysToDisplay);
|
||||||
|
|
||||||
|
for (let index = 0; index < daysToDisplay; index++) {
|
||||||
|
this.series[0].data[index] = 0;
|
||||||
|
this.series[1].data[index] = 0;
|
||||||
|
}
|
||||||
|
var today = new Date();
|
||||||
|
var dd = String(today.getDate()).padStart(2, "0");
|
||||||
|
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
|
||||||
|
var yyyy = today.getFullYear();
|
||||||
|
|
||||||
|
for (let index = 0; index < daysToDisplay; index++) {
|
||||||
|
var lastSeven = new Date();
|
||||||
|
lastSeven.setDate(today.getDate() - index);
|
||||||
|
var ddS = String(lastSeven.getDate()).padStart(2, "0");
|
||||||
|
var mmS = String(lastSeven.getMonth() + 1).padStart(2, "0"); //January is 0!
|
||||||
|
var yyyyS = lastSeven.getFullYear();
|
||||||
|
this.chartOptions.xaxis.categories[index] = yyyyS + "-" + mmS + "-" + ddS;
|
||||||
|
}
|
||||||
|
today = yyyy + "-" + mm + "-" + dd;
|
||||||
|
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var recordsDB = JSON.parse(xhttp.responseText);
|
||||||
|
recordsDB._embedded.records.forEach(tmpRecord => {
|
||||||
|
records.push(tmpRecord);
|
||||||
|
});
|
||||||
|
page++;
|
||||||
|
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.status == 403) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/records/search/allBetweenAndUser?start=" +
|
||||||
|
this.chartOptions.xaxis.categories[daysToDisplay - 1] +
|
||||||
|
"T00:00:01" +
|
||||||
|
"&end=" +
|
||||||
|
today +
|
||||||
|
"T23:59:59" +
|
||||||
|
"&username=" +
|
||||||
|
sessionStorage.getItem("username") +
|
||||||
|
"&page=" +
|
||||||
|
page +
|
||||||
|
"&size=50",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
|
||||||
|
xhttp.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (let index = 0; index < records.length; index++) {
|
||||||
|
var record = records[index];
|
||||||
|
|
||||||
|
for (let indexA = 0; indexA < daysToDisplay; indexA++) {
|
||||||
|
if (record.startdate.split("T")[0] == this.chartOptions.xaxis.categories[indexA]) {
|
||||||
|
if (record.type == "PAID") {
|
||||||
|
this.series[0].data[indexA] += record.duration;
|
||||||
|
} else {
|
||||||
|
this.series[1].data[indexA] += record.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dirty fix for known bug from apexcharts
|
||||||
|
this.series[0].data[daysToDisplay - 1] = 0;
|
||||||
|
this.series[1].data[daysToDisplay - 1] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
136
frontend/src/views/statistics/charts/WorkPausePie.vue
Normal file
136
frontend/src/views/statistics/charts/WorkPausePie.vue
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<v-card color="main_accent" outlined>
|
||||||
|
<div>
|
||||||
|
<p></p>
|
||||||
|
<h3 align="center">Times by type PAID and BREAK</h3>
|
||||||
|
<div id="chart">
|
||||||
|
<apexchart :options="chartOptions" :series="series" class="ma-5" type="donut" width="100%"></apexchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueApexCharts from "vue-apexcharts";
|
||||||
|
import {BASE_URI} from "../../../globals.js";
|
||||||
|
import {timeFormatter} from "../ApexFormatters";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
apexchart: VueApexCharts
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
series: [0, 0],
|
||||||
|
chartOptions: {
|
||||||
|
labels: ["Working hours", "Pause hours"],
|
||||||
|
chart: {
|
||||||
|
type: "donut",
|
||||||
|
background: "#202020",
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
tools: {
|
||||||
|
download: true,
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: false,
|
||||||
|
customIcons: []
|
||||||
|
},
|
||||||
|
autoSelected: "zoom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
followCursor: true,
|
||||||
|
fillSeriesColor: false,
|
||||||
|
x: {show: false},
|
||||||
|
y: {formatter: timeFormatter}
|
||||||
|
},
|
||||||
|
colors: ["#0096ff", "#e21d1f", "#546E7A", "#E91E63", "#FF9800"],
|
||||||
|
theme: {mode: "dark"},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
position: "left",
|
||||||
|
offsetY: 40
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
created() {
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
var records = new Array(0);
|
||||||
|
var hasNext = true;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var recordsDB = JSON.parse(xhttp.responseText);
|
||||||
|
recordsDB._embedded.records.forEach(tmpRecord => {
|
||||||
|
records.push(tmpRecord)
|
||||||
|
});
|
||||||
|
page++;
|
||||||
|
if (recordsDB.page.number == recordsDB.page.totalPages - 1) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.status == 403) {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open(
|
||||||
|
"GET",
|
||||||
|
BASE_URI +
|
||||||
|
"/records/search/allForUser?username=" +
|
||||||
|
sessionStorage.getItem("username") +
|
||||||
|
"&page=" +
|
||||||
|
page +
|
||||||
|
"&projection=overview" +
|
||||||
|
"&size=50",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
xhttp.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var paidTime = 0;
|
||||||
|
var breakTime = 0;
|
||||||
|
|
||||||
|
for (let index = 0; index < records.length; index++) {
|
||||||
|
var record = records[index];
|
||||||
|
var type = record.type + "";
|
||||||
|
|
||||||
|
if (type == "PAID") {
|
||||||
|
paidTime += record.duration;
|
||||||
|
} else {
|
||||||
|
breakTime += record.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.series[0] = paidTime;
|
||||||
|
this.series[1] = breakTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
127
frontend/src/views/timerecords/CreateTimerecord.vue
Normal file
127
frontend/src/views/timerecords/CreateTimerecord.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<v-container @keyup.enter.prevent="addRecord">
|
||||||
|
<v-card align-center>
|
||||||
|
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-select :items="types" hide-details label="Type" menu-props="auto" prepend-icon="mdi-currency-usd"
|
||||||
|
single-line v-model="entryData.type"></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-select :items="accounts" hide-details label="Account" menu-props="auto" prepend-icon="mdi-account-tie"
|
||||||
|
single-line v-model="entryData.account"></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-menu :close-on-content-click="false" :nudge-right="40" min-width="290px"
|
||||||
|
offset-y ref="showDatepickerStartdate" transition="scale-transition" v-model="showDatepickerStartdate">
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-text-field label="Startdate" prepend-icon="mdi-calendar-range" readonly v-model="entryData.startdate"
|
||||||
|
v-on="on"></v-text-field>
|
||||||
|
</template>
|
||||||
|
<v-date-picker @input="showDatepickerStartdate = false" no-title scrollable
|
||||||
|
v-model="entryData.startdate"></v-date-picker>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-menu :close-on-content-click="false" :nudge-right="40"
|
||||||
|
min-width="290px" offset-y ref="showDatepickerEnddate"
|
||||||
|
transition="scale-transition" v-model="showDatepickerEnddate">
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-text-field label="Enddate" prepend-icon="mdi-calendar-range"
|
||||||
|
readonly v-model="entryData.enddate" v-on="on"></v-text-field>
|
||||||
|
</template>
|
||||||
|
<v-date-picker @input="showDatepickerEnddate = false" no-title scrollable
|
||||||
|
v-model="entryData.enddate"></v-date-picker>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-menu :close-on-content-click="false" :nudge-right="40" :return-value.sync="entryData.starttime"
|
||||||
|
max-width="290px" min-width="290px" offset-y ref="showTimepickerStarttime"
|
||||||
|
transition="scale-transition" v-model="showTimepickerStarttime">
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-text-field label="Starttime" prepend-icon="mdi-clock-outline" readonly v-model="entryData.starttime"
|
||||||
|
v-on="on"></v-text-field>
|
||||||
|
</template>
|
||||||
|
<v-time-picker @click:minute="$refs.showTimepickerStarttime.save(entryData.starttime)" format="24hr" full-width v-if="showTimepickerStarttime"
|
||||||
|
v-model="entryData.starttime"></v-time-picker>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-menu :close-on-content-click="false" :nudge-right="40" :return-value.sync="entryData.endtime"
|
||||||
|
max-width="290px" min-width="290px" offset-y ref="showTimepickerEndtime"
|
||||||
|
transition="scale-transition" v-model="showTimepickerEndtime">
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-text-field label="Endtime" prepend-icon="mdi-clock-outline" readonly v-model="entryData.endtime"
|
||||||
|
v-on="on"></v-text-field>
|
||||||
|
</template>
|
||||||
|
<v-time-picker @click:minute="$refs.showTimepickerEndtime.save(entryData.endtime)" format="24hr" full-width v-if="showTimepickerEndtime"
|
||||||
|
v-model="entryData.endtime"></v-time-picker>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<v-btn @click="addRecord" color="logowhite" dark outlined rounded>Add</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {BASE_URI} from "../../globals";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CreateTimeTrackAccount",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
entryData: {
|
||||||
|
type: "",
|
||||||
|
account: "",
|
||||||
|
startdate: "",
|
||||||
|
starttime: "",
|
||||||
|
enddate: "",
|
||||||
|
endtime: ""
|
||||||
|
},
|
||||||
|
types: ["PAID", "BREAK"],
|
||||||
|
accounts: [],
|
||||||
|
showTimepickerStarttime: false,
|
||||||
|
showTimepickerEndtime: false,
|
||||||
|
showDatepickerStartdate: false,
|
||||||
|
showDatepickerEnddate: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async addRecord() {
|
||||||
|
// Map accountname to corresponding backend url
|
||||||
|
let accountsResponse = await axios.get(BASE_URI + "/accounts/search/findByUsernameAndName", {
|
||||||
|
params: {
|
||||||
|
username: sessionStorage.getItem("username"),
|
||||||
|
account: this.entryData.account
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Object.values(this.entryData).every(entry => entry)) {
|
||||||
|
await axios.post(BASE_URI + "/records", {
|
||||||
|
startdate: `${this.entryData.startdate}T${this.entryData.starttime}`,
|
||||||
|
enddate: `${this.entryData.enddate}T${this.entryData.endtime}`,
|
||||||
|
type: this.entryData.type,
|
||||||
|
account: accountsResponse.data._links.self.href
|
||||||
|
})
|
||||||
|
await this.$router.push("/timerecords")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
let accountsResponse = await axios.get(BASE_URI + "/accounts/search/findByUsername", {
|
||||||
|
params: {
|
||||||
|
username: sessionStorage.getItem("username")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.accounts = accountsResponse.data._embedded.accounts.map(value => value.name)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -1,139 +1,138 @@
|
|||||||
<template >
|
<template>
|
||||||
<v-container id="editRecordListen">
|
<v-container id="editRecordListen">
|
||||||
<v-card align-center>
|
<v-card align-center>
|
||||||
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
||||||
|
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-select
|
<v-select
|
||||||
v-model="newtype"
|
|
||||||
:items="types"
|
:items="types"
|
||||||
menu-props="auto"
|
|
||||||
label="Type"
|
|
||||||
hide-details
|
hide-details
|
||||||
|
label="Type"
|
||||||
|
menu-props="auto"
|
||||||
prepend-icon="mdi-currency-usd"
|
prepend-icon="mdi-currency-usd"
|
||||||
single-line
|
single-line
|
||||||
|
v-model="newtype"
|
||||||
></v-select>
|
></v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" >
|
<v-col cols="12" sm="6">
|
||||||
<v-menu
|
<v-menu
|
||||||
ref="menu"
|
|
||||||
v-model="menu"
|
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-right="40"
|
:nudge-right="40"
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
min-width="290px"
|
min-width="290px"
|
||||||
|
offset-y
|
||||||
|
ref="menu"
|
||||||
|
transition="scale-transition"
|
||||||
|
v-model="menu"
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newstartdate"
|
|
||||||
label="Startdate"
|
label="Startdate"
|
||||||
prepend-icon="mdi-calendar-range"
|
prepend-icon="mdi-calendar-range"
|
||||||
readonly
|
readonly
|
||||||
|
v-model="newstartdate"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-date-picker v-model="newstartdate" @input="menu = false" no-title scrollable>
|
<v-date-picker @input="menu = false" no-title scrollable v-model="newstartdate">
|
||||||
|
|
||||||
</v-date-picker>
|
</v-date-picker>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" >
|
<v-col cols="12" sm="6">
|
||||||
<v-menu
|
<v-menu
|
||||||
ref="menu2"
|
|
||||||
v-model="menu2"
|
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-right="40"
|
:nudge-right="40"
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
min-width="290px"
|
min-width="290px"
|
||||||
|
offset-y
|
||||||
|
ref="menu2"
|
||||||
|
transition="scale-transition"
|
||||||
|
v-model="menu2"
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newenddate"
|
|
||||||
label="Enddate"
|
label="Enddate"
|
||||||
prepend-icon="mdi-calendar-range"
|
prepend-icon="mdi-calendar-range"
|
||||||
readonly
|
readonly
|
||||||
|
v-model="newenddate"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-date-picker v-model="newenddate" @input="menu2 = false" no-title scrollable>
|
<v-date-picker @input="menu2 = false" no-title scrollable v-model="newenddate"></v-date-picker>
|
||||||
|
|
||||||
</v-date-picker>
|
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="11" sm="6">
|
<v-col cols="11" sm="6">
|
||||||
<v-menu
|
<v-menu
|
||||||
ref="menutime"
|
|
||||||
v-model="menutime"
|
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-right="40"
|
:nudge-right="40"
|
||||||
:return-value.sync="timestart"
|
:return-value.sync="timestart"
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
max-width="290px"
|
max-width="290px"
|
||||||
min-width="290px"
|
min-width="290px"
|
||||||
|
offset-y
|
||||||
|
ref="menutime"
|
||||||
|
transition="scale-transition"
|
||||||
|
v-model="menutime"
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="timestart"
|
|
||||||
label="Starttime"
|
label="Starttime"
|
||||||
prepend-icon="mdi-clock-outline"
|
prepend-icon="mdi-clock-outline"
|
||||||
readonly
|
readonly
|
||||||
|
v-model="timestart"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-time-picker
|
<v-time-picker
|
||||||
|
@click:minute="$refs.menutime.save(timestart)"
|
||||||
|
format="24hr"
|
||||||
|
full-width
|
||||||
v-if="menutime"
|
v-if="menutime"
|
||||||
v-model="timestart"
|
v-model="timestart"
|
||||||
full-width
|
|
||||||
format="24hr"
|
|
||||||
@click:minute="$refs.menutime.save(timestart)"
|
|
||||||
></v-time-picker>
|
></v-time-picker>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="11" sm="6">
|
<v-col cols="11" sm="6">
|
||||||
<v-menu
|
<v-menu
|
||||||
ref="menutime2"
|
|
||||||
v-model="menutime2"
|
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-right="40"
|
:nudge-right="40"
|
||||||
:return-value.sync="timeend"
|
:return-value.sync="timeend"
|
||||||
transition="scale-transition"
|
|
||||||
offset-y
|
|
||||||
max-width="290px"
|
max-width="290px"
|
||||||
min-width="290px"
|
min-width="290px"
|
||||||
|
offset-y
|
||||||
|
ref="menutime2"
|
||||||
|
transition="scale-transition"
|
||||||
|
v-model="menutime2"
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="timeend"
|
|
||||||
label="Endtime"
|
label="Endtime"
|
||||||
prepend-icon="mdi-clock-outline"
|
prepend-icon="mdi-clock-outline"
|
||||||
readonly
|
readonly
|
||||||
|
v-model="timeend"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<v-time-picker
|
<v-time-picker
|
||||||
|
@click:minute="$refs.menutime2.save(timeend)"
|
||||||
|
format="24hr"
|
||||||
|
full-width
|
||||||
v-if="menutime2"
|
v-if="menutime2"
|
||||||
v-model="timeend"
|
v-model="timeend"
|
||||||
full-width
|
|
||||||
format="24hr"
|
|
||||||
@click:minute="$refs.menutime2.save(timeend)"
|
|
||||||
></v-time-picker>
|
></v-time-picker>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<div class="text-center ma-3">
|
<div class="text-center ma-3">
|
||||||
<v-btn rounded color="logowhite" outlined dark v-on:click="editRecord()">Edit</v-btn>
|
<v-btn color="logowhite" dark outlined rounded v-on:click="editRecord()">Edit</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "EditTimeTrackAccount",
|
name: "EditTimeTrackAccount",
|
||||||
data: () => ({
|
data: () => ({
|
||||||
timestart: null,
|
timestart: null,
|
||||||
@ -142,7 +141,7 @@ export default {
|
|||||||
menutime2: false,
|
menutime2: false,
|
||||||
menu: false,
|
menu: false,
|
||||||
menu2: false,
|
menu2: false,
|
||||||
types: [ "PAID" ,"BREAK"],
|
types: ["PAID", "BREAK"],
|
||||||
type: "",
|
type: "",
|
||||||
stardate: "",
|
stardate: "",
|
||||||
enddate: "",
|
enddate: "",
|
||||||
@ -160,7 +159,7 @@ export default {
|
|||||||
this.newenddate = this.newenddate + "T" + this.timeend;
|
this.newenddate = this.newenddate + "T" + this.timeend;
|
||||||
var xhttp = new XMLHttpRequest();
|
var xhttp = new XMLHttpRequest();
|
||||||
var suc;
|
var suc;
|
||||||
xhttp.onreadystatechange = function() {
|
xhttp.onreadystatechange = function () {
|
||||||
if ((this.status == 200) & (this.readyState == 4)) {
|
if ((this.status == 200) & (this.readyState == 4)) {
|
||||||
suc = true;
|
suc = true;
|
||||||
}
|
}
|
||||||
@ -189,17 +188,12 @@ export default {
|
|||||||
var recordxhttp = new XMLHttpRequest();
|
var recordxhttp = new XMLHttpRequest();
|
||||||
var record;
|
var record;
|
||||||
|
|
||||||
recordxhttp.onreadystatechange = function() {
|
recordxhttp.onreadystatechange = function () {
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
record = JSON.parse(recordxhttp.responseText);
|
record = JSON.parse(recordxhttp.responseText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
recordxhttp.open(
|
recordxhttp.open("GET", sessionStorage.getItem("timeRecordEditSelfLink"), false);
|
||||||
"GET",
|
|
||||||
sessionStorage.getItem("timeRecordEditSelfLink"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
recordxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
recordxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||||
|
|
||||||
recordxhttp.send(null);
|
recordxhttp.send(null);
|
||||||
@ -225,10 +219,9 @@ export default {
|
|||||||
listen.addEventListener("keyup", e => {
|
listen.addEventListener("keyup", e => {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.editRecord();
|
this.editRecord();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
@ -2,16 +2,13 @@
|
|||||||
<v-card outlined>
|
<v-card outlined>
|
||||||
<v-list-item class="main_accent">
|
<v-list-item class="main_accent">
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<!-- <v-col cols="2" >
|
|
||||||
<v-card color="background" elevation="0">
|
|
||||||
<pre><v-icon color="primary">mdi-calendar-range</v-icon>{{" " + timeRecord.date}}</pre>
|
|
||||||
</v-card>
|
|
||||||
</v-col> -->
|
|
||||||
<h3 justify-center>{{timeRecord.date}}</h3>
|
<h3 justify-center>{{timeRecord.date}}</h3>
|
||||||
<v-row no-gutters align="center">
|
<v-row align="center" no-gutters>
|
||||||
<v-col cols="2">
|
<v-col cols="2">
|
||||||
<v-card color="background" elevation="0">
|
<v-card color="background" elevation="0">
|
||||||
<pre><v-icon color="green" v-bind:class="{'d-none':timeRecord.type == 'BREAK'}">mdi-currency-usd</v-icon><v-icon color="red" v-bind:class="{'d-none':timeRecord.type == 'PAID'}">mdi-currency-usd-off</v-icon>{{" " + timeRecord.type}}</pre>
|
<v-icon color="green" v-if="timeRecord.type === 'PAID'">mdi-currency-usd</v-icon>
|
||||||
|
<v-icon color="red" v-if="timeRecord.type === 'BREAK'">mdi-currency-usd-off</v-icon>
|
||||||
|
{{timeRecord.type}}
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col></v-col>
|
<v-col></v-col>
|
||||||
@ -42,22 +39,18 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-speed-dial
|
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="fab">
|
||||||
v-model="fab"
|
|
||||||
transition="slide-x-reverse-transition"
|
|
||||||
direction="left"
|
|
||||||
open-on-hover
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
<template v-slot:activator>
|
||||||
<v-btn v-model="fab" color="background" elevation="0" dark fab>
|
<v-btn color="background" dark elevation="0" fab v-model="fab">
|
||||||
<v-icon v-if="fab">mdi-close</v-icon>
|
<v-icon v-if="fab">mdi-close</v-icon>
|
||||||
<v-icon v-else>mdi-pencil</v-icon>
|
<v-icon v-else>mdi-pencil</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-btn fab dark small color="green" @click="$emit('edit-timeRecord', timeRecord._links.self.href, timeRecord.enddate)">
|
<v-btn @click="$emit('edit-timeRecord', timeRecord._links.self.href, timeRecord.enddate)" color="green" dark fab
|
||||||
|
small>
|
||||||
<v-icon>mdi-file-document-edit</v-icon>
|
<v-icon>mdi-file-document-edit</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn fab dark small color="red" @click="$emit('del-timeRecord', timeRecord._links.self.href)">
|
<v-btn @click="$emit('del-timeRecord', timeRecord._links.self.href)" color="red" dark fab small>
|
||||||
<v-icon>mdi-delete</v-icon>
|
<v-icon>mdi-delete</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
@ -67,16 +60,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "TimeRecordItem",
|
name: "TimeRecordItem",
|
||||||
props: ["timeRecord"]
|
props: ["timeRecord"],
|
||||||
};
|
data() {
|
||||||
|
return {
|
||||||
|
fab: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.v-card {
|
.v-card {
|
||||||
border-color: #131313 !important;
|
border-color: #131313 !important;
|
||||||
border-width: 3px !important;
|
border-width: 3px !important;
|
||||||
border-radius: 10000px !important;
|
border-radius: 10000px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
101
frontend/src/views/timerecords/TimeRecords.vue
Normal file
101
frontend/src/views/timerecords/TimeRecords.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<div :key="timeRecord._links.self.href" v-for="timeRecord in timeRecords">
|
||||||
|
<TimeRecordItem :timeRecord="timeRecord"
|
||||||
|
@del-timeRecord="deleteTimeRecord"
|
||||||
|
@edit-timeRecord="editTimeRecord"/>
|
||||||
|
</div>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="loadPage(prevPage)" v-if="prevPage">
|
||||||
|
<v-icon>mdi-arrow-left</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="loadPage(nextPage)" v-if="nextPage">
|
||||||
|
<v-icon>mdi-arrow-right</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"></v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="createTimeRecord" color="primary" fab v-if="loggedIn">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TimeRecordItem from "./TimeRecordItem.vue";
|
||||||
|
import {BASE_URI} from "../../globals.js";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TimeRecords",
|
||||||
|
components: {
|
||||||
|
TimeRecordItem
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timeRecords: "",
|
||||||
|
nextPage: "",
|
||||||
|
prevPage: "",
|
||||||
|
loggedIn: JSON.parse(sessionStorage.getItem("loggedin"))
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async deleteTimeRecord(selfLink) {
|
||||||
|
try {
|
||||||
|
await axios.delete(selfLink)
|
||||||
|
this.timeRecords = this.timeRecords.filter(record => record._links.self.href !== selfLink)
|
||||||
|
} catch (e) {
|
||||||
|
alert("Delete failed!")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadPage(url) {
|
||||||
|
try {
|
||||||
|
let recordsResponse = await axios.get(url)
|
||||||
|
this.timeRecords = recordsResponse.data._embedded.records
|
||||||
|
this.timeRecords.map(record => {
|
||||||
|
record.duration = `${Math.floor(record.duration / 60)}h ${record.duration % 60} min`
|
||||||
|
record.date = record.startdate.split("T")[0]
|
||||||
|
record.startdate = record.startdate.split("T")[1]
|
||||||
|
record.enddate = record.enddate ? record.enddate.split("T")[1] : "pending"
|
||||||
|
})
|
||||||
|
this.nextPage = recordsResponse.data._links?.next?.href
|
||||||
|
this.prevPage = recordsResponse.data._links?.prev?.href
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response.status === 403) await this.$router.push("/") // Solve later with location guard
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editTimeRecord(selfLink, end) {
|
||||||
|
if (end === "pending") {
|
||||||
|
alert("It's not possible to edit a running track")
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem("timeRecordEditSelfLink", selfLink)
|
||||||
|
this.$router.push("/edittimerecord")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createTimeRecord(selfLink) {
|
||||||
|
sessionStorage.setItem("timeRecordEditSelfLink", selfLink)
|
||||||
|
this.$router.push("/createtimerecord")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let url = BASE_URI + "/records/search/allForUser?"
|
||||||
|
+ "username=" + sessionStorage.getItem("username") + "&"
|
||||||
|
+ "sort=startdate,desc" + "&"
|
||||||
|
+ "projection=overview"
|
||||||
|
await this.loadPage(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<v-container @keyup.enter.prevent="addAccount">
|
||||||
|
<v-card align-center>
|
||||||
|
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
||||||
|
<v-form>
|
||||||
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
|
label="name"
|
||||||
|
name="name"
|
||||||
|
prepend-icon="mdi-account-tie"
|
||||||
|
type="text"
|
||||||
|
v-model="accountData.name"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
|
label="revenue"
|
||||||
|
name="revenue"
|
||||||
|
prepend-icon="mdi-currency-usd"
|
||||||
|
type="text"
|
||||||
|
v-model="accountData.revenue"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
color="primary"
|
||||||
|
label="description"
|
||||||
|
name="description"
|
||||||
|
prepend-icon="mdi-information-variant"
|
||||||
|
type="text"
|
||||||
|
v-model="accountData.description"
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<v-btn @click="addAccount" color="logowhite" dark outlined rounded>Add</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {BASE_URI} from "../../globals"
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CreateTimeTrackAccount",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: sessionStorage.getItem("timeTrackAccountListUserId"),
|
||||||
|
accountData: {name: "", revenue: "", description: ""}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async addAccount() {
|
||||||
|
if (Object.values(this.accountData).every(data => data)) {
|
||||||
|
let postData = {
|
||||||
|
user: this.user,
|
||||||
|
...this.accountData
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await axios.post(BASE_URI + "/accounts", postData)
|
||||||
|
await this.$router.push("/timetrackaccounts")
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Error " + err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<v-container @keyup.prevent.enter="editAccount">
|
||||||
|
<v-card align-center v-if="isInactive || errorMessage">
|
||||||
|
<p class="text-center logowhite--text">{{errorMessage}}</p>
|
||||||
|
</v-card>
|
||||||
|
<v-card align-center v-else>
|
||||||
|
<p class="text-center logowhite--text">Edit details for {{accountData.name}}</p>
|
||||||
|
<v-form>
|
||||||
|
<v-text-field :disabled="isInactive" color="primary" label="name" name="name"
|
||||||
|
prepend-icon="mdi-account-tie" type="text" v-model="accountData.name"/>
|
||||||
|
<v-text-field :disabled="isInactive" color="primary" label="revenue" name="revenue"
|
||||||
|
prepend-icon="mdi-currency-usd" type="text" v-model="accountData.revenue"/>
|
||||||
|
<v-text-field :disabled="isInactive" color="primary" label="description" name="description"
|
||||||
|
prepend-icon="mdi-information-variant" type="text" v-model="accountData.description"/>
|
||||||
|
</v-form>
|
||||||
|
<div class="text-center ma-3">
|
||||||
|
<v-btn @click="editAccount" color="logowhite" dark outlined rounded>Edit</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "EditTimeTrackAccount",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isInactive: true,
|
||||||
|
accountData: {name: "", revenue: "", description: ""},
|
||||||
|
errorMessage: "Fetching Data..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async editAccount() {
|
||||||
|
let link = sessionStorage.getItem("timeTrackAccountEditSelfLink")
|
||||||
|
if (Object.values(this.accountData).some(val => val)) {
|
||||||
|
try {
|
||||||
|
await axios.patch(link, this.accountData)
|
||||||
|
await this.$router.push("/timetrackaccounts")
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Error while updating: " + err)
|
||||||
|
this.errorMessage = "Error while updating!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
let link = sessionStorage.getItem("timeTrackAccountEditSelfLink")
|
||||||
|
try {
|
||||||
|
let response = await axios.get(link)
|
||||||
|
this.accountData = response.data
|
||||||
|
this.isInactive = false
|
||||||
|
this.errorMessage = ""
|
||||||
|
} catch (err) {
|
||||||
|
console.log("An error occurred while fetching the data: " + err)
|
||||||
|
this.errorMessage = "An error occurred while fetching the data!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p {
|
||||||
|
font-size: 30pt;
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,7 +2,7 @@
|
|||||||
<v-card outlined>
|
<v-card outlined>
|
||||||
<v-list-item class="main_accent">
|
<v-list-item class="main_accent">
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-row no-gutters align="center">
|
<v-row align="center" no-gutters>
|
||||||
<v-col cols="3">
|
<v-col cols="3">
|
||||||
<v-card color="background" elevation="0">
|
<v-card color="background" elevation="0">
|
||||||
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" " + timeTrackAccount.name}}</pre>
|
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" " + timeTrackAccount.name}}</pre>
|
||||||
@ -24,22 +24,19 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-speed-dial
|
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="fab">
|
||||||
v-model="fab"
|
|
||||||
transition="slide-x-reverse-transition"
|
|
||||||
direction="left"
|
|
||||||
open-on-hover
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
<template v-slot:activator>
|
||||||
<v-btn v-model="fab" color="background" elevation="0" dark fab>
|
<v-btn color="background" dark elevation="0" fab v-model="fab">
|
||||||
<v-icon v-if="fab">mdi-close</v-icon>
|
<v-icon v-if="fab">mdi-close</v-icon>
|
||||||
<v-icon v-else>mdi-pencil</v-icon>
|
<v-icon v-else>mdi-pencil</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-btn fab dark small color="green" @click="$emit('edit-timeTrackAccount', timeTrackAccount._links.self.href)">
|
<v-btn @click="$emit('edit-timeTrackAccount', timeTrackAccount._links.self.href)" color="green" dark fab
|
||||||
|
small>
|
||||||
<v-icon>mdi-file-document-edit</v-icon>
|
<v-icon>mdi-file-document-edit</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn fab dark small color="red" @click="$emit('del-timeTrackAccount', timeTrackAccount._links.self.href)">
|
<v-btn @click="$emit('del-timeTrackAccount', timeTrackAccount._links.self.href)" color="red" dark fab
|
||||||
|
small>
|
||||||
<v-icon>mdi-delete</v-icon>
|
<v-icon>mdi-delete</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
@ -49,19 +46,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "TimeTrackAccountItem",
|
name: "TimeTrackAccountItem",
|
||||||
props: ["timeTrackAccount"],
|
props: ["timeTrackAccount"],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
fab: undefined
|
fab: undefined
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.v-card {
|
.v-card {
|
||||||
border-color: #131313 !important;
|
border-color: #131313 !important;
|
||||||
border-width: 3px !important;
|
border-width: 3px !important;
|
||||||
border-radius: 10000px !important;
|
border-radius: 10000px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
67
frontend/src/views/timetrackaccounts/TimeTrackAccounts.vue
Normal file
67
frontend/src/views/timetrackaccounts/TimeTrackAccounts.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<div v-bind:key="timeTrackAccount.name" v-for="timeTrackAccount in timeTrackAccounts">
|
||||||
|
<TimeTrackAccountItem
|
||||||
|
v-bind:timeTrackAccount="timeTrackAccount"
|
||||||
|
v-on:del-timeTrackAccount="deleteTimeTrackAccount"
|
||||||
|
v-on:edit-timeTrackAccount="editTimeTrackAccount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"></v-col>
|
||||||
|
<v-col cols="1">
|
||||||
|
<v-btn @click="createTimeTrackAccount()" color="primary" fab v-if="loggedIn == 'true'">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="5"></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {BASE_URI} from "../../globals.js";
|
||||||
|
import TimeTrackAccountItem from "./TimeTrackAccountItem.vue";
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TimeTrackAccounts",
|
||||||
|
components: {
|
||||||
|
TimeTrackAccountItem
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timeTrackAccounts: "",
|
||||||
|
loggedIn: sessionStorage.getItem("loggedin")
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async deleteTimeTrackAccount(selfLink) {
|
||||||
|
try {
|
||||||
|
await axios.delete(selfLink)
|
||||||
|
this.timeTrackAccounts = this.timeTrackAccounts.filter(account => account._links.self.href !== selfLink)
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editTimeTrackAccount(selfLink) {
|
||||||
|
sessionStorage.setItem("timeTrackAccountEditSelfLink", selfLink);
|
||||||
|
this.$router.push("/edittimetrackaccount")
|
||||||
|
},
|
||||||
|
createTimeTrackAccount() {
|
||||||
|
this.$router.push("/createtimetrackaccount")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let accountsResponse = await axios.get(
|
||||||
|
BASE_URI
|
||||||
|
+ "/accounts/search/findByUsername?username="
|
||||||
|
+ sessionStorage.getItem("username")
|
||||||
|
)
|
||||||
|
this.timeTrackAccounts = accountsResponse.data._embedded.accounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user