Compare commits

..

36 Commits

Author SHA1 Message Date
3450ce6f87 Make StatisticsOverview and WeekSummary async 2020-09-20 03:11:48 +02:00
9fd0055f36 Dependency upgrades 2020-09-20 03:11:12 +02:00
bc88c632be Make timetrackAccounts load async
Update a package
2020-07-31 02:14:11 +02:00
4131213723 Rework Timerecords page to be async and responsive 2020-07-30 22:19:40 +02:00
33506a4d9e Rework admin page to be async and responsive 2020-07-27 02:32:43 +02:00
933e0e7c0c Rework editUser to be async 2020-07-27 00:56:37 +02:00
b298ac5815 Lint project again, mostly reordering html directives 2020-07-26 23:35:58 +02:00
49260a8829 Reorder project file-structure 2020-07-26 23:32:48 +02:00
af7edf2b45 Move statistics route and components 2020-07-26 23:26:33 +02:00
5613b2a61e Format editTimetrackAccount file a bit more 2020-07-26 23:21:57 +02:00
651698d833 Rework javascript of createTimerecord page 2020-07-26 23:07:03 +02:00
420ab2d278 Make app.vue root component async 2020-07-26 22:13:57 +02:00
5b3ae837dc Fix some warnings and rename variables 2020-07-26 20:36:44 +02:00
80d16b15d8 Rework home to get data async with axios
Some layout adjustments
2020-07-26 20:09:57 +02:00
0cb3cbe2d3 Rework editTimetrackAccount to be async and uses axios 2020-07-26 03:26:35 +02:00
ee1f4c5222 Use async await for createTimetrackAccount 2020-07-26 03:26:06 +02:00
5748cc8410 Add axios auth interceptor 2020-07-26 03:26:06 +02:00
24240164ad Change createTimetrackAccount to use axios 2020-07-26 03:26:06 +02:00
bd9eae2dff Rework signIn dialog 2020-07-26 01:59:36 +02:00
8c62028cc4 Update vuetify 2020-07-23 22:36:02 +02:00
a69489b53f Install axios 2020-07-22 01:18:10 +02:00
6a1f3b84cd Update dependencies 2020-07-22 00:52:27 +02:00
1f8e8069cb Rename baseUri to BASE_URI, some smaller adjustments 2020-07-22 00:38:00 +02:00
417ab3da50 Extract duplicate formatter code out of chart definitions 2020-07-22 00:15:47 +02:00
a73740f400 Remove buggy pre tag to preserve formatting
use v-if instead of v-bind:class
2020-07-22 00:15:47 +02:00
695df651e1 Format App.vue 2020-07-21 23:09:16 +02:00
cfad59d139 Format Sign in and statistics component 2020-07-21 23:04:10 +02:00
6f9e9141b7 Format Home component 2020-07-21 23:01:35 +02:00
3321f4a58a Format User related stuff 2020-07-21 22:57:58 +02:00
ebd5493e46 Format TimetrackAccount related things 2020-07-21 21:59:44 +02:00
286d1c7fda Format Timerecord related things 2020-07-21 21:26:49 +02:00
e13d157dd6 Format About and missing, some css adjustments 2020-07-19 22:55:00 +02:00
33a0b0840b Reformat chart components 2020-07-19 22:40:52 +02:00
a0a4e1ca97 Format plugins and router 2020-07-19 22:33:04 +02:00
b6645e148e Add editorconfig 2020-07-19 22:25:00 +02:00
24a7907d26 Update packages, fix project name 2020-07-19 22:13:03 +02:00
47 changed files with 5260 additions and 3996 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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">
@ -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>
<p>{{loginError}}</p>
</v-card>
</v-dialog>
</v-row>
@ -126,9 +124,9 @@
</v-app-bar>
<!-- Routed pages are inserted here -->
<v-content>
<v-main>
<router-view/>
</v-content>
</v-main>
<!-- Footer on bottom -->
<v-footer app class="main">
@ -140,11 +138,8 @@
<script>
import SignIn from "./views/SignIn.vue";
import { baseUri } from "./variables.js";
if (!sessionStorage.getItem("loggedin")) {
sessionStorage.setItem("loggedin", false);
}
import {BASE_URI} from "./globals.js";
import axios from 'axios'
export default {
components: {
@ -153,155 +148,81 @@ export default {
props: {
source: String
},
data: () => ({
drawer: null,
dialog: false,
menu: false,
loggedIn: sessionStorage.getItem("loggedin"),
fullname:
sessionStorage.getItem("firstname") +
" " +
sessionStorage.getItem("lastname")
}),
data() {
return {
sideNavDrawer: false,
loginDialog: false,
userInfoDialog: false,
loggedIn: false,
fullname: "",
loginError: ""
}
},
methods: {
forward() {
this.$router.push("/");
},
signIn(loginData) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if ((this.status == 200) & (this.readyState == 4)) {
sessionStorage.setItem(
"jwt",
this.getResponseHeader("Authorization")
);
sessionStorage.setItem("loggedin", true);
} else if (this.status != 200 && this.status != 0) {
document.getElementById("loginError").innerHTML =
"Login not successfull";
}
};
xhttp.open("POST", baseUri + "/login", false);
xhttp.send(
'{"username": "' +
loginData.username +
'", "password": "' +
loginData.password +
'"}'
);
if (sessionStorage.getItem("loggedin") == "true") {
sessionStorage.setItem("haveData", true);
var whoxhttp = new XMLHttpRequest();
whoxhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var userInformation = JSON.parse(whoxhttp.responseText);
sessionStorage.setItem("firstname", userInformation.firstname);
sessionStorage.setItem("lastname", userInformation.lastname);
sessionStorage.setItem("username", userInformation.username);
sessionStorage.setItem("userIDOwn", userInformation.id);
this.fullname =
sessionStorage.getItem("firstname") +
" " +
sessionStorage.getItem("lastname");
location.reload();
}
};
whoxhttp.open("GET", baseUri + "/whoami", false);
whoxhttp.setRequestHeader(
"Authorization",
sessionStorage.getItem("jwt")
);
whoxhttp.send(null);
location.reload();
handleLogoClick() {
if (this.$router.currentRoute.path !== "/") {
this.$router.replace("/")
}
},
signUp(signupData) {
var xhttp = new XMLHttpRequest();
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.onreadystatechange = function() {
if ((this.status == 201) & (this.readyState == 4)) {
location.reload();
} else if (this.status != 201 && this.status != 0) {
document.getElementById("loginError").innerHTML =
"The username already exist";
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"
}
};
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
);
async toAccounts() {
let usersResponse = await axios.get(BASE_URI + "/users/search/byUsername", {
params: {
username: sessionStorage.getItem("username")
}
};
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" ) {
})
sessionStorage.setItem("timeTrackAccountListUser", sessionStorage.getItem("username"))
sessionStorage.setItem("timeTrackAccountListUserId", usersResponse.data._links.self.href)
if (this.$router.currentRoute.path === "/timetrackaccounts") {
location.reload();
} else {
this.$router.push("/timetrackaccounts");
await 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")
}
}
};
</script>
<style scoped>
.link {
color: #f1f1f1f1;
text-decoration: none;
}
.v-application {
font-family: "Montserrat", sans-serif;
background-color: var(--v-background-base) !important;

1
frontend/src/globals.js Normal file
View File

@ -0,0 +1 @@
export const BASE_URI = 'http://plesk.icaotix.de:5000'

View File

@ -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;

View File

@ -15,7 +15,6 @@ export default new Vuetify({
accent: '#8c9eff',
error: '#b71c1c',
main: '#272727',
main_accent: '#202020',
footer: '#404040',

View File

@ -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: '*',

View File

@ -1 +0,0 @@
export const baseUri = 'http://plesk.icaotix.de:5000'

View File

@ -4,44 +4,60 @@
<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>
<p class="text-center logowhite--text font-size-larger">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">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
import axios from "axios"
export default {
name: "About"
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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-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: () => ({
data() {
return {
loggedIn: JSON.parse(sessionStorage.getItem("loggedin")),
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;
}
};
xhttp.open("GET", baseUri + "/records/search/today", false);
xhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
xhttp.send(null);
for (let index = 0; index < today.length; index++) {
var record = today[index];
var start = record.startdate;
start = start.split("T");
today[index].startdate = start[1];
},
async mounted() {
if (this.loggedIn) {
try {
var end = record.enddate;
end = end.split("T");
today[index].enddate = end[1];
} catch (e) {
today[index].enddate = "pending";
}
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`)
])
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;
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;
this.timeTrackAccounts = responses[1].data._embedded.accounts
this.location = responses[2].data
} catch (e) {
console.log(e)
}
}
}
}
};
</script>
<style scoped>
p {
font-size: 15pt;
}
.larger-text {
font-size: 30pt;
}
</style>

View File

@ -3,9 +3,7 @@
</template>
<script>
export default {
}
export default {}
</script>
<style scoped>

View File

@ -1,57 +1,49 @@
<template>
<v-container class="fill-height" id="signInListen">
<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-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
},
data() {
return {
errorMessage: "",
visiblePane: 1,
signInData: {username: "", password: ""},
signUpData: {firstname: "", lastname: "", username: "", password: ""}
}
},
methods: {
signin() {
if (this.username != "" && this.password != "") {
document.getElementById("missing").innerHTML= "";
const loginData = {
username: this.username,
password: this. password
}
this.$emit('signIn', loginData);
if (Object.values(this.signInData).every(item => item)) {
this.errorMessage = ""
this.$emit("signIn", this.signInData)
} else {
document.getElementById("missing").innerHTML= "Please fill out all fields";
this.errorMessage = "Please fill out all fields"
}
},
signup() {
if (this.usernameR != "" && this.passwordR != "") {
document.getElementById("missingR").innerHTML= "";
const signupData = {
firstname: this.firstname,
lastname: this.lastname,
username: this.usernameR,
password: this. passwordR
}
this.$emit('signUp', signupData);
if (Object.values(this.signUpData).every(item => item)) {
this.errorMessage = ""
this.$emit("signUp", this.signUpData)
} else {
document.getElementById("missingR").innerHTML= "Please fill out all fields";
}
this.errorMessage = "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();
switchPane(num) {
this.errorMessage = ""
this.visiblePane = num
},
handleEnter() {
if (this.visiblePane === 1) {
this.signin()
} else {
this.signup();
this.signup()
}
}
});
}
};
</script>
<style lang="css" scoped>
<style scoped>
</style>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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 &quot;{{username}}&quot;</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>

View 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>

View File

@ -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>
@ -57,17 +49,12 @@
export default {
name: "UsersItems",
props: ["user"],
data: () => ({
fab: undefined,
}),
methods: {
getUid(hrefTmp) {
var parts = hrefTmp.split("/");
return parts[4];
data() {
return {
editButton: false
}
}
}
};
</script>
<style scoped>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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};

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -5,133 +5,132 @@
<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-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
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-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",
@ -194,12 +193,7 @@ export default {
record = JSON.parse(recordxhttp.responseText);
}
};
recordxhttp.open(
"GET",
sessionStorage.getItem("timeRecordEditSelfLink"),
false
);
recordxhttp.open("GET", sessionStorage.getItem("timeRecordEditSelfLink"), false);
recordxhttp.setRequestHeader("Authorization", sessionStorage.getItem("jwt"));
recordxhttp.send(null);
@ -225,7 +219,6 @@ export default {
listen.addEventListener("keyup", e => {
if (e.keyCode === 13) {
e.preventDefault();
this.editRecord();
}
});

View File

@ -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">
<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>
@ -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>
@ -69,7 +62,12 @@
<script>
export default {
name: "TimeRecordItem",
props: ["timeRecord"]
props: ["timeRecord"],
data() {
return {
fab: false
}
}
};
</script>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>