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"?>
|
||||
<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" />
|
||||
</component>
|
||||
<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",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@ -8,27 +8,28 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"apexcharts": "^3.19.0",
|
||||
"core-js": "^3.6.4",
|
||||
"apexcharts": "^3.20.2",
|
||||
"axios": "^0.19.2",
|
||||
"core-js": "^3.6.5",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-apexcharts": "^1.5.3",
|
||||
"vue-router": "^3.1.6",
|
||||
"vuetify": "^2.2.11"
|
||||
"vue": "^2.6.12",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-router": "^3.4.3",
|
||||
"vuetify": "^2.3.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"@vue/cli-plugin-babel": "^4.5.6",
|
||||
"@vue/cli-plugin-eslint": "^4.5.6",
|
||||
"@vue/cli-plugin-router": "^4.5.6",
|
||||
"@vue/cli-service": "^4.5.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"sass": "^1.19.0",
|
||||
"sass": "^1.26.11",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue-cli-plugin-vuetify": "~2.0.5",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.3.0"
|
||||
"vue-cli-plugin-vuetify": "^2.0.7",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuetify-loader": "^1.6.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="app">
|
||||
<v-app id="geotimetracking">
|
||||
<!-- 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-item link to="/">
|
||||
<v-list-item-action>
|
||||
@ -12,7 +12,7 @@
|
||||
<v-list-item-title>Home</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</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-icon>mdi-clock</v-icon>
|
||||
</v-list-item-action>
|
||||
@ -20,7 +20,7 @@
|
||||
<v-list-item-title>Time Records</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</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-icon>mdi-chart-bar</v-icon>
|
||||
</v-list-item-action>
|
||||
@ -28,7 +28,7 @@
|
||||
<v-list-item-title>Statistics</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</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-icon>mdi-account-details</v-icon>
|
||||
</v-list-item-action>
|
||||
@ -44,7 +44,7 @@
|
||||
<v-list-item-title>About</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</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-icon>mdi-account-group</v-icon>
|
||||
</v-list-item-action>
|
||||
@ -57,18 +57,19 @@
|
||||
|
||||
<!-- Top menu bar -->
|
||||
<v-app-bar app clipped-left class="main" elevation="10">
|
||||
<v-app-bar-nav-icon @click.stop="drawer = !drawer" />
|
||||
<v-img @click="forward" src="./assets/logo.svg" max-height="100%" max-width="100" contain></v-img>
|
||||
<v-app-bar-nav-icon @click.stop="sideNavDrawer = !sideNavDrawer"/>
|
||||
<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-spacer></v-spacer>
|
||||
|
||||
<!-- Menu with account icon -->
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
v-model="userInfoDialog"
|
||||
:close-on-content-click="false"
|
||||
:nudge-width="200"
|
||||
offset-y
|
||||
v-if="loggedIn == 'true'"
|
||||
v-if="loggedIn"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on">
|
||||
@ -80,7 +81,7 @@
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<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-content>
|
||||
@ -93,32 +94,29 @@
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn text @click="menu = false">Cancel</v-btn>
|
||||
<v-btn text @click="userInfoDialog = false">Cancel</v-btn>
|
||||
<v-btn color="primary" text @click="logout">Logout</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<v-card v-if="loggedIn == 'false'">
|
||||
<v-card v-if="!loggedIn">
|
||||
<!-- Modal -->
|
||||
|
||||
<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 }">
|
||||
<v-btn color="primary" dark v-on="on">Login</v-btn>
|
||||
</template>
|
||||
<v-card class="main">
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog = false">
|
||||
<v-btn icon @click="loginDialog = false">
|
||||
<v-icon>mdi-window-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<SignIn v-on:signIn="signIn" v-on:signUp="signUp" />
|
||||
<p id="loginError"></p>
|
||||
<SignIn v-on:signIn="signIn" v-on:signUp="signUp"/>
|
||||
<p>{{loginError}}</p>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
@ -126,9 +124,9 @@
|
||||
</v-app-bar>
|
||||
|
||||
<!-- Routed pages are inserted here -->
|
||||
<v-content>
|
||||
<router-view />
|
||||
</v-content>
|
||||
<v-main>
|
||||
<router-view/>
|
||||
</v-main>
|
||||
|
||||
<!-- Footer on bottom -->
|
||||
<v-footer app class="main">
|
||||
@ -139,171 +137,94 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SignIn from "./views/SignIn.vue";
|
||||
import { baseUri } from "./variables.js";
|
||||
import SignIn from "./views/SignIn.vue";
|
||||
import {BASE_URI} from "./globals.js";
|
||||
import axios from 'axios'
|
||||
|
||||
if (!sessionStorage.getItem("loggedin")) {
|
||||
sessionStorage.setItem("loggedin", false);
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SignIn
|
||||
},
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
data: () => ({
|
||||
drawer: null,
|
||||
dialog: false,
|
||||
menu: false,
|
||||
loggedIn: sessionStorage.getItem("loggedin"),
|
||||
fullname:
|
||||
sessionStorage.getItem("firstname") +
|
||||
" " +
|
||||
sessionStorage.getItem("lastname")
|
||||
}),
|
||||
methods: {
|
||||
forward() {
|
||||
this.$router.push("/");
|
||||
export default {
|
||||
components: {
|
||||
SignIn
|
||||
},
|
||||
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";
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sideNavDrawer: false,
|
||||
loginDialog: false,
|
||||
userInfoDialog: false,
|
||||
loggedIn: false,
|
||||
fullname: "",
|
||||
loginError: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleLogoClick() {
|
||||
if (this.$router.currentRoute.path !== "/") {
|
||||
this.$router.replace("/")
|
||||
}
|
||||
};
|
||||
},
|
||||
async signIn(loginData) {
|
||||
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.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);
|
||||
let whoAmIResponse = await axios.get(BASE_URI + "/whoami")
|
||||
sessionStorage.setItem("firstname", whoAmIResponse.data.firstname)
|
||||
sessionStorage.setItem("lastname", whoAmIResponse.data.lastname)
|
||||
sessionStorage.setItem("username", whoAmIResponse.data.username)
|
||||
sessionStorage.setItem("userIDOwn", whoAmIResponse.data.id)
|
||||
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"
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
sessionStorage.clear();
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
signUp(signupData) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
},
|
||||
async toAccounts() {
|
||||
let usersResponse = await axios.get(BASE_URI + "/users/search/byUsername", {
|
||||
params: {
|
||||
username: sessionStorage.getItem("username")
|
||||
}
|
||||
})
|
||||
sessionStorage.setItem("timeTrackAccountListUser", sessionStorage.getItem("username"))
|
||||
sessionStorage.setItem("timeTrackAccountListUserId", usersResponse.data._links.self.href)
|
||||
|
||||
xhttp.onreadystatechange = function() {
|
||||
if ((this.status == 201) & (this.readyState == 4)) {
|
||||
if (this.$router.currentRoute.path === "/timetrackaccounts") {
|
||||
location.reload();
|
||||
} else if (this.status != 201 && this.status != 0) {
|
||||
document.getElementById("loginError").innerHTML =
|
||||
"The username already exist";
|
||||
} else {
|
||||
await this.$router.push("/timetrackaccounts");
|
||||
}
|
||||
};
|
||||
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() {
|
||||
sessionStorage.clear();
|
||||
sessionStorage.setItem("loggedin", false);
|
||||
location.reload();
|
||||
},
|
||||
toAccounts() {
|
||||
sessionStorage.setItem(
|
||||
"timeTrackAccountListUser",
|
||||
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(
|
||||
"GET",
|
||||
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" ) {
|
||||
|
||||
|
||||
location.reload();
|
||||
|
||||
}else{
|
||||
this.$router.push("/timetrackaccounts");
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$vuetify.theme.dark = true;
|
||||
this.loggedIn = JSON.parse(sessionStorage.getItem("loggedin"))
|
||||
this.fullname = sessionStorage.getItem("firstname") + " " + sessionStorage.getItem("lastname")
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$vuetify.theme.dark = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.link {
|
||||
color: #f1f1f1f1;
|
||||
text-decoration: none;
|
||||
}
|
||||
.v-application {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
background-color: var(--v-background-base) !important;
|
||||
}
|
||||
.link {
|
||||
color: #f1f1f1f1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v-application {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
background-color: var(--v-background-base) !important;
|
||||
}
|
||||
</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 router from "./router";
|
||||
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;
|
||||
|
||||
|
@ -4,25 +4,24 @@ import Vuetify from 'vuetify/lib';
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: true
|
||||
},
|
||||
themes: {
|
||||
dark: {
|
||||
primary: '#0096ff',
|
||||
secondary: '#b0bec5',
|
||||
accent: '#8c9eff',
|
||||
error: '#b71c1c',
|
||||
|
||||
|
||||
main: '#272727',
|
||||
main_accent: '#202020',
|
||||
footer: '#404040',
|
||||
background: '#131313',
|
||||
background_soft: '#171717',
|
||||
},
|
||||
},
|
||||
dark:true
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: true
|
||||
},
|
||||
themes: {
|
||||
dark: {
|
||||
primary: '#0096ff',
|
||||
secondary: '#b0bec5',
|
||||
accent: '#8c9eff',
|
||||
error: '#b71c1c',
|
||||
|
||||
main: '#272727',
|
||||
main_accent: '#202020',
|
||||
footer: '#404040',
|
||||
background: '#131313',
|
||||
background_soft: '#171717',
|
||||
},
|
||||
},
|
||||
dark: true
|
||||
},
|
||||
});
|
||||
|
@ -1,17 +1,18 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
import missing from "../views/missing.vue";
|
||||
import TimeRecords from "../views/TimeRecords.vue";
|
||||
import missing from "../views/Missing.vue";
|
||||
import TimeRecords from "../views/timerecords/TimeRecords.vue";
|
||||
import About from "../views/About.vue";
|
||||
import StatisticOverview from "../views/StatisticOverview.vue";
|
||||
import Users from "../views/Users.vue";
|
||||
import EditUser from "../views/EditUser.vue";
|
||||
import TimeTrackAccounts from "../views/TimeTrackAccounts.vue";
|
||||
import EditTimeTrackAccount from "../views/EditTimeTrackAccount.vue"
|
||||
import CreateTimeTrackAccount from "../views/CreateTimeTrackAccount.vue"
|
||||
import EditTimerecord from "../views/EditTimerecord.vue"
|
||||
import CreateTimerecord from "../views/CreateTimerecord.vue"
|
||||
import StatisticOverview from "../views/statistics/StatisticOverview.vue";
|
||||
import Users from "../views/admin/Users.vue";
|
||||
import EditUser from "../views/admin/EditUser.vue";
|
||||
import TimeTrackAccounts from "../views/timetrackaccounts/TimeTrackAccounts.vue";
|
||||
import EditTimeTrackAccount from "../views/timetrackaccounts/EditTimeTrackAccount.vue"
|
||||
import CreateTimeTrackAccount from "../views/timetrackaccounts/CreateTimeTrackAccount.vue"
|
||||
import EditTimerecord from "../views/timerecords/EditTimerecord.vue"
|
||||
import CreateTimerecord from "../views/timerecords/CreateTimerecord.vue"
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
@ -19,90 +20,67 @@ const routes = [
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
meta: {
|
||||
title: "Geo Timetracking - Home",
|
||||
}
|
||||
meta: {title: "Geo Timetracking - Home"}
|
||||
},
|
||||
{
|
||||
path: "/timerecords",
|
||||
name: "TimeRecords",
|
||||
component: TimeRecords,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Time Records',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Time Records'}
|
||||
},
|
||||
|
||||
{
|
||||
path: "/about",
|
||||
name: "About",
|
||||
component: About,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - About',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - About'}
|
||||
},
|
||||
{
|
||||
path: "/statistics",
|
||||
name: "Statistics",
|
||||
component: StatisticOverview,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Statistics',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Statistics'}
|
||||
},
|
||||
{
|
||||
path: "/users",
|
||||
name: "Users",
|
||||
component: Users,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Users',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Users'}
|
||||
},
|
||||
{
|
||||
path: "/edituser",
|
||||
name: "EditUser",
|
||||
component: EditUser,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Edit User',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Edit User'}
|
||||
},
|
||||
{
|
||||
path: "/timetrackaccounts",
|
||||
name: "TimeTrack Accounts",
|
||||
component: TimeTrackAccounts,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - TimeTrack Accounts',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - TimeTrack Accounts'}
|
||||
},
|
||||
{
|
||||
path: "/edittimetrackaccount",
|
||||
name: "Edit TimeTrack Account",
|
||||
component: EditTimeTrackAccount,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Edit TimeTrack Account',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Edit TimeTrack Account'}
|
||||
},
|
||||
{
|
||||
path: "/createtimetrackaccount",
|
||||
name: "Create TimeTrack Account",
|
||||
component: CreateTimeTrackAccount,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Create TimeTrack Accounts',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Create TimeTrack Accounts'}
|
||||
},
|
||||
{
|
||||
path: "/edittimerecord",
|
||||
name: "EditTimerecord",
|
||||
component: EditTimerecord,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Edit Time Record',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Edit Time Record'}
|
||||
},
|
||||
{
|
||||
path: "/createtimerecord",
|
||||
name: "CreateTimerecord",
|
||||
component: CreateTimerecord,
|
||||
meta: {
|
||||
title: 'Geo Timetracking - Create Time Record',
|
||||
}
|
||||
meta: {title: 'Geo Timetracking - Create Time Record'}
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
|
@ -1 +0,0 @@
|
||||
export const baseUri = 'http://plesk.icaotix.de:5000'
|
@ -1,47 +1,63 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row class="ma-5">
|
||||
<v-col cols="3" ></v-col>
|
||||
<v-col cols="3"></v-col>
|
||||
<v-col cols="6">
|
||||
<v-card>
|
||||
<p
|
||||
class="text-center logowhite--text"
|
||||
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>
|
||||
<p class="text-center logowhite--text">This is a ubiquitous computing project.</p>
|
||||
<p class="text-center logowhite--text">It was created by the team TacocaT.</p>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="3"></v-col>
|
||||
<v-col cols="3"></v-col>
|
||||
<v-col cols="6">
|
||||
<v-card>
|
||||
<p class="text-center logowhite--text" style="font-size:30pt">Team TacocaT</p>
|
||||
<br />
|
||||
<p
|
||||
class="text-center logowhite--text"
|
||||
style="font-size:20pt"
|
||||
>Backend Developer: Marcel Schwarz</p>
|
||||
<p
|
||||
class="text-center logowhite--text"
|
||||
style="font-size:20pt"
|
||||
>Android Developer: Tobias Wieck</p>
|
||||
<p
|
||||
class="text-center logowhite--text"
|
||||
style="font-size:20pt"
|
||||
>Frontend Developer: Simon Kellner, Tim Zieger</p>
|
||||
<p class="text-center logowhite--text font-size-larger">Team TacocaT</p>
|
||||
<br/>
|
||||
<p class="text-center logowhite--text">Backend Developer: Marcel Schwarz</p>
|
||||
<p class="text-center logowhite--text">Android Developer: Tobias Wieck</p>
|
||||
<p class="text-center logowhite--text">Frontend Developer: Simon Kellner, Tim Zieger</p>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="ma-5" v-if="todos">
|
||||
<v-col cols="12">
|
||||
<v-card :key="todo.id" v-for="todo of todos">
|
||||
<p>
|
||||
<v-icon color="white" v-if="!todo.completed">mdi-checkbox-blank-circle-outline</v-icon>
|
||||
<v-icon color="white" v-else>mdi-check-circle</v-icon>
|
||||
{{todo.title}}
|
||||
</p>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
|
||||
export default {
|
||||
name: "About"
|
||||
};
|
||||
<script>
|
||||
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>
|
||||
|
||||
<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>
|
||||
<v-container fluid>
|
||||
<v-row v-if="loggedIn == 'true'">
|
||||
<v-row v-if="loggedIn">
|
||||
<v-col cols="6">
|
||||
<v-card class="pa-3">
|
||||
<p style="font-size:30pt">User Information</p>
|
||||
<p style="font-size:15pt"> Username: {{username}}</p>
|
||||
<p style="font-size:15pt">Firstname: {{firstname}}</p>
|
||||
<p style="font-size:15pt">Lastname: {{lastname}}</p>
|
||||
<p class="larger-text">User Information</p>
|
||||
<p>Username: {{username}}</p>
|
||||
<p>Firstname: {{firstname}}</p>
|
||||
<p>Lastname: {{lastname}}</p>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-col cols="6">
|
||||
<v-card class="pa-3">
|
||||
<div v-if="haveLocation == true">
|
||||
<p style="font-size:30pt">Location</p>
|
||||
<p style="font-size:15pt">Longitude: {{location.longitude}}</p>
|
||||
<p style="font-size:15pt">Latitude: {{location.latitude}}</p>
|
||||
<p style="font-size:15pt">Radius: {{location.radius}}</p>
|
||||
<div v-if="location">
|
||||
<p class="larger-text">Location</p>
|
||||
<p>Longitude: {{location.longitude}}</p>
|
||||
<p>Latitude: {{location.latitude}}</p>
|
||||
<p>Radius: {{location.radius}}</p>
|
||||
</div>
|
||||
<div v-if="haveLocation == false">
|
||||
<p style="font-size:30pt">Location</p>
|
||||
<div v-else>
|
||||
<p class="larger-text">Location</p>
|
||||
<p>No location set</p>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<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">
|
||||
<v-row no-gutters align="center">
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-card color="background" elevation="0" class="ma-2">
|
||||
<pre><v-icon color="green" v-bind:class="{'d-none':today.type == 'BREAK'}">mdi-currency-usd</v-icon><v-icon
|
||||
color="red"
|
||||
v-bind:class="{'d-none':today.type == 'PAID'}"
|
||||
>mdi-currency-usd-off</v-icon>{{" " + today.type}}</pre>
|
||||
<pre><v-icon color="primary">mdi-clock-outline</v-icon>{{" Start " + today.startdate}}</pre>
|
||||
|
||||
<pre><v-icon color="primary">mdi-clock-outline</v-icon>{{" End " + today.enddate}}</pre>
|
||||
<v-card class="ma-2" color="background" elevation="0">
|
||||
<div>
|
||||
<v-icon color="green" v-if="today.type === 'PAID'">mdi-currency-usd</v-icon>
|
||||
<v-icon color="red" v-if="today.type === 'BREAK'">mdi-currency-usd-off</v-icon>
|
||||
{{today.type}}
|
||||
</div>
|
||||
<div>
|
||||
<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-col>
|
||||
</v-row>
|
||||
@ -46,135 +52,82 @@
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card>
|
||||
<p class="pa-2" style="font-size:30pt">Accounts</p>
|
||||
<div
|
||||
:key="timeTrackAccount._links.self.href"
|
||||
v-for="timeTrackAccount in timeTrackAccounts"
|
||||
>
|
||||
<v-row no-gutters align="center">
|
||||
<p class="pa-2 larger-text">Accounts</p>
|
||||
<div :key="timeTrackAccount._links.self.href" v-for="timeTrackAccount in timeTrackAccounts">
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-card color="background" elevation="0" class="ma-2">
|
||||
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" Account " + timeTrackAccount.name}}</pre>
|
||||
|
||||
<pre><v-icon color="primary">mdi-currency-usd</v-icon>{{" Revenue " + timeTrackAccount.revenue}}</pre>
|
||||
<v-card class="ma-2" color="background" elevation="0">
|
||||
<div>
|
||||
<v-icon color="primary">mdi-account-tie</v-icon>
|
||||
Account: {{timeTrackAccount.name}}
|
||||
</div>
|
||||
<div>
|
||||
<v-icon color="primary">mdi-currency-usd</v-icon>
|
||||
Revenue: {{timeTrackAccount.revenue}}
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
<v-card v-if="loggedIn == 'false'" class="pa-3">
|
||||
<p style="font-size:20pt">Welcome to Geo Timetracking</p>
|
||||
<v-card class="pa-3" v-if="!loggedIn">
|
||||
<p class="larger-text">Welcome to Geo Timetracking</p>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {BASE_URI} from "../globals.js";
|
||||
import axios from "axios"
|
||||
|
||||
|
||||
import { baseUri } from "../variables.js";
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {},
|
||||
data: () => ({
|
||||
username: sessionStorage.getItem("username"),
|
||||
firstname: sessionStorage.getItem("firstname"),
|
||||
lastname: sessionStorage.getItem("lastname"),
|
||||
todaysRecord: "",
|
||||
loggedIn: sessionStorage.getItem("loggedin"),
|
||||
timeTrackAccounts: "",
|
||||
location: "",
|
||||
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;
|
||||
export default {
|
||||
name: "Home",
|
||||
data() {
|
||||
return {
|
||||
loggedIn: JSON.parse(sessionStorage.getItem("loggedin")),
|
||||
username: sessionStorage.getItem("username"),
|
||||
firstname: sessionStorage.getItem("firstname"),
|
||||
lastname: sessionStorage.getItem("lastname"),
|
||||
todaysRecord: "",
|
||||
timeTrackAccounts: "",
|
||||
location: "",
|
||||
haveLocation: ""
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", baseUri + "/records/search/today", false);
|
||||
},
|
||||
async mounted() {
|
||||
if (this.loggedIn) {
|
||||
try {
|
||||
let responses = await Promise.all([
|
||||
axios.get(BASE_URI + "/records/search/today"),
|
||||
axios.get(BASE_URI + "/accounts/search/findByUsername", {params: {username: this.username}}),
|
||||
axios.get(BASE_URI + `/users/${sessionStorage.getItem("userIDOwn")}/location`)
|
||||
])
|
||||
|
||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||
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;
|
||||
|
||||
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 {
|
||||
var end = record.enddate;
|
||||
end = end.split("T");
|
||||
today[index].enddate = end[1];
|
||||
} catch (e) {
|
||||
today[index].enddate = "pending";
|
||||
this.timeTrackAccounts = responses[1].data._embedded.accounts
|
||||
this.location = responses[2].data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.todaysRecord = today;
|
||||
|
||||
|
||||
var timeTrackAccountsTMP;
|
||||
var accountxhttp = new XMLHttpRequest();
|
||||
accountxhttp.onreadystatechange = function() {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
.larger-text {
|
||||
font-size: 30pt;
|
||||
}
|
||||
</style>
|
||||
|
@ -3,9 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
@ -1,57 +1,49 @@
|
||||
<template >
|
||||
<v-container class="fill-height" id="signInListen">
|
||||
<template>
|
||||
<v-container @keyup.enter="handleEnter" class="fill-height">
|
||||
<v-row align="center" justify="center">
|
||||
<v-col cols="12">
|
||||
<v-card elevation="0">
|
||||
<v-window v-model="step">
|
||||
<v-window v-model="visiblePane">
|
||||
<v-window-item :value="1">
|
||||
<v-row>
|
||||
<v-col cols="12" md="8" class="main">
|
||||
<v-col class="main" cols="12" md="8">
|
||||
<v-card-text class="mt-1">
|
||||
<h1 class="text-center display-2 logowhite--text">Sign in</h1>
|
||||
|
||||
<v-form>
|
||||
<v-form>
|
||||
<v-text-field
|
||||
color="primary"
|
||||
label="Username"
|
||||
v-model="username"
|
||||
name="Username"
|
||||
prepend-icon="mdi-account"
|
||||
type="text"
|
||||
color="primary"
|
||||
v-model="signInData.username"
|
||||
/>
|
||||
<v-text-field
|
||||
color="primary"
|
||||
id="password"
|
||||
label="Password"
|
||||
v-model="password"
|
||||
name="Password"
|
||||
prepend-icon="mdi-key"
|
||||
type="password"
|
||||
color="primary"
|
||||
v-model="signInData.password"
|
||||
/>
|
||||
</v-form>
|
||||
<p id="missing"></p>
|
||||
<p>{{errorMessage}}</p>
|
||||
</v-card-text>
|
||||
<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>
|
||||
</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">
|
||||
<h1 class="text-center display-1 primary--text">No account yet?</h1>
|
||||
<div class="text-center ma-12">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-10">
|
||||
<v-btn
|
||||
class="primary--text"
|
||||
rounded
|
||||
outlined
|
||||
dark
|
||||
@click="step++"
|
||||
>Create Account</v-btn>
|
||||
<v-btn @click="switchPane(2)" class="primary--text" dark outlined rounded>Create Account</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-col>
|
||||
@ -59,60 +51,60 @@
|
||||
</v-window-item>
|
||||
<v-window-item :value="2">
|
||||
<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">
|
||||
<h1 class="text-center display-1 primary--text">Welcome Back</h1>
|
||||
</v-card-text>
|
||||
<div class="text-center ma-12">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8" class="main">
|
||||
<v-col class="main" cols="12" md="8">
|
||||
<v-card-text class="mt-12">
|
||||
<h1 class="text-center display-2 logowhite--text">Create Account</h1>
|
||||
<v-form>
|
||||
<v-text-field
|
||||
color="primary "
|
||||
label="Firstname"
|
||||
name="FirstnameR"
|
||||
v-model="firstname"
|
||||
prepend-icon="mdi-account-box"
|
||||
type="text"
|
||||
color="primary "
|
||||
v-model="signUpData.firstname"
|
||||
/>
|
||||
<v-text-field
|
||||
color="primary"
|
||||
label="Lastname"
|
||||
name="LastnameR"
|
||||
v-model="lastname"
|
||||
prepend-icon="mdi-account-box"
|
||||
type="text"
|
||||
color="primary"
|
||||
v-model="signUpData.lastname"
|
||||
/>
|
||||
<v-text-field
|
||||
color="primary"
|
||||
label="Username"
|
||||
name="UsernameR"
|
||||
v-model="usernameR"
|
||||
prepend-icon="mdi-account"
|
||||
type="text"
|
||||
color="primary"
|
||||
v-model="signUpData.username"
|
||||
/>
|
||||
<v-text-field
|
||||
color="primary"
|
||||
label="Password"
|
||||
name="PasswordR"
|
||||
v-model="passwordR"
|
||||
prepend-icon="mdi-key"
|
||||
type="password"
|
||||
color="primary"
|
||||
v-model="signUpData.password"
|
||||
/>
|
||||
</v-form>
|
||||
<p id="missingR"></p>
|
||||
<p>{{errorMessage}}</p>
|
||||
</v-card-text>
|
||||
<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>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@ -125,68 +117,49 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
step: 1,
|
||||
username: "",
|
||||
password: "",
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
usernameR: "",
|
||||
passwordR: ""
|
||||
|
||||
}),
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
methods: {
|
||||
signin() {
|
||||
if (this.username != "" && this.password != "") {
|
||||
document.getElementById("missing").innerHTML= "";
|
||||
const loginData = {
|
||||
username: this.username,
|
||||
password: this. password
|
||||
}
|
||||
this.$emit('signIn', loginData);
|
||||
}else {
|
||||
document.getElementById("missing").innerHTML= "Please fill out all fields";
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: String
|
||||
},
|
||||
signup(){
|
||||
if (this.usernameR != "" && this.passwordR != "") {
|
||||
document.getElementById("missingR").innerHTML= "";
|
||||
const signupData = {
|
||||
firstname: this.firstname,
|
||||
lastname: this.lastname,
|
||||
username: this.usernameR,
|
||||
password: this. passwordR
|
||||
data() {
|
||||
return {
|
||||
errorMessage: "",
|
||||
visiblePane: 1,
|
||||
signInData: {username: "", password: ""},
|
||||
signUpData: {firstname: "", lastname: "", username: "", password: ""}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
signin() {
|
||||
if (Object.values(this.signInData).every(item => item)) {
|
||||
this.errorMessage = ""
|
||||
this.$emit("signIn", this.signInData)
|
||||
} else {
|
||||
this.errorMessage = "Please fill out all fields"
|
||||
}
|
||||
},
|
||||
signup() {
|
||||
if (Object.values(this.signUpData).every(item => item)) {
|
||||
this.errorMessage = ""
|
||||
this.$emit("signUp", this.signUpData)
|
||||
} else {
|
||||
this.errorMessage = "Please fill out all fields"
|
||||
}
|
||||
},
|
||||
switchPane(num) {
|
||||
this.errorMessage = ""
|
||||
this.visiblePane = num
|
||||
},
|
||||
handleEnter() {
|
||||
if (this.visiblePane === 1) {
|
||||
this.signin()
|
||||
} else {
|
||||
this.signup()
|
||||
}
|
||||
this.$emit('signUp', signupData);
|
||||
}else {
|
||||
document.getElementById("missingR").innerHTML= "Please fill out all fields";
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
var listen = document.getElementById('signInListen');
|
||||
listen.addEventListener('keyup', (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
if(this.step==1){
|
||||
this.signin();
|
||||
}else{
|
||||
this.signup();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
<style scoped>
|
||||
</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-list-item class="main_accent">
|
||||
<v-list-item-content>
|
||||
<v-row no-gutters align="center">
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col cols="1">
|
||||
<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-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-card color="background" elevation="0">
|
||||
<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>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col></v-col>
|
||||
<v-col cols="2"></v-col>
|
||||
<v-col></v-col>
|
||||
<v-col cols="4"></v-col>
|
||||
</v-row>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-speed-dial
|
||||
v-model="fab"
|
||||
transition="slide-x-reverse-transition"
|
||||
direction="left"
|
||||
open-on-hover
|
||||
>
|
||||
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="editButton">
|
||||
<template v-slot:activator>
|
||||
<v-btn v-model="fab" color="background" elevation="0" dark fab>
|
||||
<v-icon v-if="fab">mdi-close</v-icon>
|
||||
<v-btn color="background" dark elevation="0" fab v-model="editButton">
|
||||
<v-icon v-if="editButton">mdi-close</v-icon>
|
||||
<v-icon v-else>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</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-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-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-btn>
|
||||
</v-speed-dial>
|
||||
@ -54,26 +46,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "UsersItems",
|
||||
props: ["user"],
|
||||
data: () => ({
|
||||
fab: undefined,
|
||||
}),
|
||||
methods: {
|
||||
getUid(hrefTmp) {
|
||||
var parts = hrefTmp.split("/");
|
||||
return parts[4];
|
||||
|
||||
export default {
|
||||
name: "UsersItems",
|
||||
props: ["user"],
|
||||
data() {
|
||||
return {
|
||||
editButton: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</style>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</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,176 +1,175 @@
|
||||
<template >
|
||||
<template>
|
||||
<v-container id="editRecordListen">
|
||||
<v-card align-center>
|
||||
<p class="text-center logowhite--text" style="font-size:30pt">Details</p>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-col cols="6">
|
||||
<v-select
|
||||
v-model="newtype"
|
||||
:items="types"
|
||||
menu-props="auto"
|
||||
label="Type"
|
||||
hide-details
|
||||
label="Type"
|
||||
menu-props="auto"
|
||||
prepend-icon="mdi-currency-usd"
|
||||
single-line
|
||||
v-model="newtype"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" >
|
||||
<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
|
||||
:nudge-right="40"
|
||||
min-width="290px"
|
||||
offset-y
|
||||
ref="menu"
|
||||
transition="scale-transition"
|
||||
v-model="menu"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-text-field
|
||||
v-model="newstartdate"
|
||||
label="Startdate"
|
||||
prepend-icon="mdi-calendar-range"
|
||||
readonly
|
||||
v-model="newstartdate"
|
||||
v-on="on"
|
||||
></v-text-field>
|
||||
</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-menu>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" >
|
||||
<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"
|
||||
offset-y
|
||||
ref="menu2"
|
||||
transition="scale-transition"
|
||||
v-model="menu2"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-text-field
|
||||
v-model="newenddate"
|
||||
label="Enddate"
|
||||
prepend-icon="mdi-calendar-range"
|
||||
readonly
|
||||
v-model="newenddate"
|
||||
v-on="on"
|
||||
></v-text-field>
|
||||
</template>
|
||||
<v-date-picker v-model="newenddate" @input="menu2 = false" no-title scrollable>
|
||||
|
||||
</v-date-picker>
|
||||
<v-date-picker @input="menu2 = false" no-title scrollable v-model="newenddate"></v-date-picker>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="11" 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"
|
||||
offset-y
|
||||
ref="menutime"
|
||||
transition="scale-transition"
|
||||
v-model="menutime"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-text-field
|
||||
v-model="timestart"
|
||||
label="Starttime"
|
||||
prepend-icon="mdi-clock-outline"
|
||||
readonly
|
||||
v-model="timestart"
|
||||
v-on="on"
|
||||
></v-text-field>
|
||||
</template>
|
||||
<v-time-picker
|
||||
@click:minute="$refs.menutime.save(timestart)"
|
||||
format="24hr"
|
||||
full-width
|
||||
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="11" 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"
|
||||
offset-y
|
||||
ref="menutime2"
|
||||
transition="scale-transition"
|
||||
v-model="menutime2"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-text-field
|
||||
v-model="timeend"
|
||||
label="Endtime"
|
||||
prepend-icon="mdi-clock-outline"
|
||||
readonly
|
||||
v-model="timeend"
|
||||
v-on="on"
|
||||
></v-text-field>
|
||||
</template>
|
||||
<v-time-picker
|
||||
@click:minute="$refs.menutime2.save(timeend)"
|
||||
format="24hr"
|
||||
full-width
|
||||
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="editRecord()">Edit</v-btn>
|
||||
<v-btn color="logowhite" dark outlined rounded v-on:click="editRecord()">Edit</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "EditTimeTrackAccount",
|
||||
data: () => ({
|
||||
timestart: null,
|
||||
menutime: false,
|
||||
timeend: null,
|
||||
menutime2: false,
|
||||
menu: false,
|
||||
menu2: false,
|
||||
types: [ "PAID" ,"BREAK"],
|
||||
type: "",
|
||||
stardate: "",
|
||||
enddate: "",
|
||||
newtype: "",
|
||||
newstartdate: new Date().toISOString().substr(0, 10),
|
||||
newenddate: new Date().toISOString().substr(0, 10)
|
||||
}),
|
||||
export default {
|
||||
name: "EditTimeTrackAccount",
|
||||
data: () => ({
|
||||
timestart: null,
|
||||
menutime: false,
|
||||
timeend: null,
|
||||
menutime2: false,
|
||||
menu: false,
|
||||
menu2: false,
|
||||
types: ["PAID", "BREAK"],
|
||||
type: "",
|
||||
stardate: "",
|
||||
enddate: "",
|
||||
newtype: "",
|
||||
newstartdate: new Date().toISOString().substr(0, 10),
|
||||
newenddate: new Date().toISOString().substr(0, 10)
|
||||
}),
|
||||
|
||||
methods: {
|
||||
editRecord() {
|
||||
var link = sessionStorage.getItem("timeRecordEditSelfLink");
|
||||
methods: {
|
||||
editRecord() {
|
||||
var link = sessionStorage.getItem("timeRecordEditSelfLink");
|
||||
|
||||
if (this.newname != this.name || this.newstartdate != this.startdate || this.newenddate != this.enddate) {
|
||||
this.newstartdate = this.newstartdate + "T" + this.timestart;
|
||||
this.newenddate = this.newenddate + "T" + this.timeend;
|
||||
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");
|
||||
if (this.newname != this.name || this.newstartdate != this.startdate || this.newenddate != this.enddate) {
|
||||
this.newstartdate = this.newstartdate + "T" + this.timestart;
|
||||
this.newenddate = this.newenddate + "T" + this.timeend;
|
||||
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(
|
||||
"{" +
|
||||
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||
xhttp.send(
|
||||
"{" +
|
||||
' "startdate": "' +
|
||||
this.newstartdate +
|
||||
'", "enddate": "' +
|
||||
@ -178,57 +177,51 @@ export default {
|
||||
'", "type": "' +
|
||||
this.newtype +
|
||||
'"}'
|
||||
);
|
||||
if (suc == true) {
|
||||
this.$router.push("/timerecords");
|
||||
);
|
||||
if (suc == true) {
|
||||
this.$router.push("/timerecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
var recordxhttp = new XMLHttpRequest();
|
||||
var record;
|
||||
|
||||
recordxhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
record = JSON.parse(recordxhttp.responseText);
|
||||
}
|
||||
};
|
||||
recordxhttp.open("GET", sessionStorage.getItem("timeRecordEditSelfLink"), false);
|
||||
recordxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||
|
||||
recordxhttp.send(null);
|
||||
this.type = record.type;
|
||||
this.startdate = record.startdate;
|
||||
|
||||
this.newtype = record.type;
|
||||
var start = record.startdate;
|
||||
start = start.split("T");
|
||||
var starttime = start[1];
|
||||
var startdate = start[0];
|
||||
this.newstartdate = startdate;
|
||||
this.timestart = starttime;
|
||||
var end = record.enddate;
|
||||
end = end.split("T");
|
||||
var endtime = end[1];
|
||||
var enddate = end[0];
|
||||
this.newenddate = enddate;
|
||||
this.timeend = endtime;
|
||||
},
|
||||
mounted() {
|
||||
var listen = document.getElementById("editRecordListen");
|
||||
listen.addEventListener("keyup", e => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
this.editRecord();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
var recordxhttp = new XMLHttpRequest();
|
||||
var record;
|
||||
|
||||
recordxhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
record = JSON.parse(recordxhttp.responseText);
|
||||
}
|
||||
};
|
||||
recordxhttp.open(
|
||||
"GET",
|
||||
sessionStorage.getItem("timeRecordEditSelfLink"),
|
||||
false
|
||||
);
|
||||
|
||||
recordxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
|
||||
|
||||
recordxhttp.send(null);
|
||||
this.type = record.type;
|
||||
this.startdate = record.startdate;
|
||||
|
||||
this.newtype = record.type;
|
||||
var start = record.startdate;
|
||||
start = start.split("T");
|
||||
var starttime = start[1];
|
||||
var startdate = start[0];
|
||||
this.newstartdate = startdate;
|
||||
this.timestart = starttime;
|
||||
var end = record.enddate;
|
||||
end = end.split("T");
|
||||
var endtime = end[1];
|
||||
var enddate = end[0];
|
||||
this.newenddate = enddate;
|
||||
this.timeend = endtime;
|
||||
},
|
||||
mounted() {
|
||||
var listen = document.getElementById("editRecordListen");
|
||||
listen.addEventListener("keyup", e => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
|
||||
this.editRecord();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
};
|
||||
</script>
|
@ -2,16 +2,13 @@
|
||||
<v-card outlined>
|
||||
<v-list-item class="main_accent">
|
||||
<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>
|
||||
<v-row no-gutters align="center">
|
||||
<h3 justify-center>{{timeRecord.date}}</h3>
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col cols="2">
|
||||
<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-col>
|
||||
<v-col></v-col>
|
||||
@ -33,7 +30,7 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col></v-col>
|
||||
<v-col cols="2">
|
||||
<v-col cols="2">
|
||||
<v-card color="background" elevation="0">
|
||||
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" " + timeRecord.account}}</pre>
|
||||
</v-card>
|
||||
@ -42,22 +39,18 @@
|
||||
</v-row>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-speed-dial
|
||||
v-model="fab"
|
||||
transition="slide-x-reverse-transition"
|
||||
direction="left"
|
||||
open-on-hover
|
||||
>
|
||||
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="fab">
|
||||
<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-else>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</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-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-btn>
|
||||
</v-speed-dial>
|
||||
@ -67,16 +60,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TimeRecordItem",
|
||||
props: ["timeRecord"]
|
||||
};
|
||||
export default {
|
||||
name: "TimeRecordItem",
|
||||
props: ["timeRecord"],
|
||||
data() {
|
||||
return {
|
||||
fab: false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</style>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</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-list-item class="main_accent">
|
||||
<v-list-item-content>
|
||||
<v-row no-gutters align="center">
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col cols="3">
|
||||
<v-card color="background" elevation="0">
|
||||
<pre><v-icon color="primary">mdi-account-tie</v-icon>{{" " + timeTrackAccount.name}}</pre>
|
||||
@ -24,22 +24,19 @@
|
||||
</v-row>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-speed-dial
|
||||
v-model="fab"
|
||||
transition="slide-x-reverse-transition"
|
||||
direction="left"
|
||||
open-on-hover
|
||||
>
|
||||
<v-speed-dial direction="left" open-on-hover transition="slide-x-reverse-transition" v-model="fab">
|
||||
<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-else>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</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-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-btn>
|
||||
</v-speed-dial>
|
||||
@ -49,19 +46,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TimeTrackAccountItem",
|
||||
props: ["timeTrackAccount"],
|
||||
data: () => ({
|
||||
fab: undefined
|
||||
}),
|
||||
};
|
||||
export default {
|
||||
name: "TimeTrackAccountItem",
|
||||
props: ["timeTrackAccount"],
|
||||
data: () => ({
|
||||
fab: undefined
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</style>
|
||||
.v-card {
|
||||
border-color: #131313 !important;
|
||||
border-width: 3px !important;
|
||||
border-radius: 10000px !important;
|
||||
}
|
||||
</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