|
|
|
|
<template>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-md-12">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<template v-for="(tracker, trackerIndex) in $store.state.trackers" v-bind:key="trackerIndex">
|
|
|
|
|
<div class="col-lg-4 col-md-6">
|
|
|
|
|
<div :class="'card ' + (tracker.isTimeBox ? 'bg-timebox' : 'bg-gradient-secondary')"
|
|
|
|
|
:style="tracker.isTimeBox ? 'background: linear-gradient(90deg, grey ' + ((timeBoxTimeLeft(tracker) * 100) / tracker.timeBoxMinutes)+'% , black 100%':''">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="card-text">
|
|
|
|
|
<input type="text"
|
|
|
|
|
v-model="tracker.number"
|
|
|
|
|
class="form-control trackingNameField"
|
|
|
|
|
@keydown="this.$store.commit('saveTrackers')"/>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div v-if="tracker.tracking === true">
|
|
|
|
|
<div class="text-danger font-weight-bolder float-end tracker-time-info">
|
|
|
|
|
<div class="spinner-grow spinner-grow-sm" role="status">
|
|
|
|
|
<span class="sr-only">Tracking...</span>
|
|
|
|
|
</div>
|
|
|
|
|
Tracking
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<template v-if="!tracker.isTimeBox">
|
|
|
|
|
<div v-if="tracker.tracking === true">
|
|
|
|
|
<span class="float-end">{{ getTrackingStartTime(tracker) }}</span>
|
|
|
|
|
<span v-if="tracker.tracking === true">Gestartet: </span>
|
|
|
|
|
<br/>
|
|
|
|
|
<span class="float-end">{{ currentTrackingRunningFor(tracker) }}</span>
|
|
|
|
|
<span v-if="tracker.tracking === true">Läuft seit: </span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<span class="float-end">{{ getTotalTime(tracker) }}</span>
|
|
|
|
|
<span class="current-tracker-info">Gesamt: </span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<span class="float-end">{{ getTotalTimeToday(tracker) }}</span>
|
|
|
|
|
<span class="">Heute: </span>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-md-6" v-if="!tracker.tracking">
|
|
|
|
|
<button type="button" class="btn btn-primary tracker-action-button"
|
|
|
|
|
@click="startTracking(tracker)">
|
|
|
|
|
<i class="far fa-play-circle"></i> <small>Starten</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-md-6" v-else>
|
|
|
|
|
<button type="button" class="btn btn-danger tracker-action-button"
|
|
|
|
|
@click="stopTracking(tracker)">
|
|
|
|
|
<i class="far fa-stop-circle"></i> <small>Stoppen</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-6">
|
|
|
|
|
<button class="btn btn-warning tracker-action-button"
|
|
|
|
|
data-bs-dismiss="modal"
|
|
|
|
|
@click="archiveTracker(trackerIndex)" title="Archivieren">
|
|
|
|
|
<i class="fas fa-archive"></i> <small>Archivieren</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-md-6">
|
|
|
|
|
<button type="button" class="btn btn-info tracker-action-button"
|
|
|
|
|
@click="openTasksForTracker(tracker)" title="Tasks">
|
|
|
|
|
<i class="fas fa-clipboard -check"></i> <small>Tasks
|
|
|
|
|
{{ showOpenTasksForTracker(tracker.tasks) }}</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-6">
|
|
|
|
|
<button class="btn btn-danger tracker-action-button"
|
|
|
|
|
@click="deleteTracker(trackerIndex)" title="Löschen">
|
|
|
|
|
<i class="fas fa-trash"></i> <small>Löschen</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div v-if="tracker.tracking === true">
|
|
|
|
|
<span class="float-end">{{ getTrackingStartTime(tracker) }}</span>
|
|
|
|
|
<span v-if="tracker.tracking === true">Gestartet: </span>
|
|
|
|
|
<br/>
|
|
|
|
|
<span class="float-end">{{ currentTrackingRunningFor(tracker) }}</span>
|
|
|
|
|
<span v-if="tracker.tracking === true">Läuft seit: </span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<template v-if="!tracker.timeBoxMinutes">
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
Minuten:
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col">
|
|
|
|
|
<button type="button" class="btn btn-secondary tracker-action-button"
|
|
|
|
|
@click="startTimeBox(tracker, 15)">
|
|
|
|
|
<i class="far fa-play-circle"></i> 15
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col">
|
|
|
|
|
<button type="button" class="btn btn-secondary tracker-action-button"
|
|
|
|
|
@click="startTimeBox(tracker, 30)">
|
|
|
|
|
<i class="far fa-play-circle"></i> 30
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col">
|
|
|
|
|
<button type="button" class="btn btn-secondary tracker-action-button"
|
|
|
|
|
@click="startTimeBox(tracker, 60)">
|
|
|
|
|
<i class="far fa-play-circle"></i> 60
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div class="col-12"
|
|
|
|
|
v-if="tracker.timeBoxMinutes && timeBoxTimeLeft(tracker) > 0">
|
|
|
|
|
<span class="float-end">{{ timeBoxTimeLeft(tracker) }} Minuten</span>
|
|
|
|
|
<span>Zeit übrig: </span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-12 text-center" v-if="timeBoxTimeLeft(tracker) <= 0">
|
|
|
|
|
<h5 class="text-danger text-bold">Abgeschlossen</h5>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-md-12" v-if="tracker.tracking">
|
|
|
|
|
<button type="button" class="btn btn-danger tracker-action-button"
|
|
|
|
|
@click="stopTracking(tracker)">
|
|
|
|
|
<i class="far fa-stop-circle"></i> <small>Stoppen</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-12"
|
|
|
|
|
v-if="!tracker.tracking && tracker.timeBoxMinutes && timeBoxTimeLeft(tracker) > 0">
|
|
|
|
|
<button type="button" class="btn btn-primary tracker-action-button"
|
|
|
|
|
@click="startTracking(tracker)">
|
|
|
|
|
<i class="far fa-play-circle"></i> <small>Starten</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="col-md-12">
|
|
|
|
|
<button class="btn btn-danger tracker-action-button"
|
|
|
|
|
@click="deleteTracker(trackerIndex)" title="Löschen">
|
|
|
|
|
<i class="fas fa-trash"></i> <small>Löschen</small>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import moment from "moment";
|
|
|
|
|
import iziToast from "izitoast";
|
|
|
|
|
import {Offcanvas} from "bootstrap";
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "Trackers",
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
trackers() {
|
|
|
|
|
return this.$store.state.trackers;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
startTracking(tracker, individual = false, timeBoxMinutes = null) {
|
|
|
|
|
if (!individual) {
|
|
|
|
|
this.stopActiveTracker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (timeBoxMinutes) {
|
|
|
|
|
tracker.timeBoxMinutes = timeBoxMinutes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracker.status = 'wip';
|
|
|
|
|
|
|
|
|
|
tracker.trackingStarted = moment();
|
|
|
|
|
tracker.tracking = true;
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.$store.commit('saveTrackers');
|
|
|
|
|
},
|
|
|
|
|
stopTracking(tracker) {
|
|
|
|
|
tracker.trackingStopped = moment();
|
|
|
|
|
tracker.tracking = false;
|
|
|
|
|
|
|
|
|
|
let minutesSpent = moment.duration(
|
|
|
|
|
tracker.trackingStopped.diff(tracker.trackingStarted)
|
|
|
|
|
).as('minutes');
|
|
|
|
|
|
|
|
|
|
if (minutesSpent > 0) {
|
|
|
|
|
let historyEntry = {
|
|
|
|
|
trackingStarted: tracker.trackingStarted,
|
|
|
|
|
trackingStopped: tracker.trackingStopped,
|
|
|
|
|
manually: false,
|
|
|
|
|
minutes: Math.round(minutesSpent)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tracker.history.pushToBeginning(historyEntry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracker.trackingStarted = null;
|
|
|
|
|
tracker.trackingStopped = null;
|
|
|
|
|
|
|
|
|
|
this.$store.commit('saveTrackers');
|
|
|
|
|
},
|
|
|
|
|
archiveTracker(index) {
|
|
|
|
|
this.stopActiveTracker();
|
|
|
|
|
this.$store.commit('archiveTracker', index);
|
|
|
|
|
this.$store.commit('saveTrackers');
|
|
|
|
|
},
|
|
|
|
|
currentTrackingRunningFor(tracker) {
|
|
|
|
|
return this.timeWithPostFix(Math.round(moment.duration(moment().diff(tracker.trackingStarted)).as('minutes')));
|
|
|
|
|
},
|
|
|
|
|
getTotalTime(tracker, raw = false) {
|
|
|
|
|
let totalTime = 0;
|
|
|
|
|
|
|
|
|
|
if (tracker.history.length > 0) {
|
|
|
|
|
tracker.history.forEach(function (historyEntry) {
|
|
|
|
|
totalTime += Math.round(historyEntry.minutes);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tracker.tracking) {
|
|
|
|
|
totalTime += Math.round(moment.duration(moment().diff(tracker.trackingStarted)).as('minutes'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (raw) {
|
|
|
|
|
return totalTime;
|
|
|
|
|
} else {
|
|
|
|
|
return this.timeWithPostFix(totalTime);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
getTotalTimeToday(tracker) {
|
|
|
|
|
let totalTime = 0;
|
|
|
|
|
|
|
|
|
|
if (tracker.history.length > 0) {
|
|
|
|
|
tracker.history.forEach(function (historyEntry) {
|
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === moment().format("MMM Do YY")) {
|
|
|
|
|
totalTime += Math.round(historyEntry.minutes);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tracker.tracking) {
|
|
|
|
|
totalTime += Math.round(moment.duration(moment().diff(tracker.trackingStarted)).as('minutes'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.timeWithPostFix(totalTime);
|
|
|
|
|
},
|
|
|
|
|
timeWithPostFix(time) {
|
|
|
|
|
let postFix = ' Minute';
|
|
|
|
|
|
|
|
|
|
if (time >= 480 && this.showPT) {
|
|
|
|
|
postFix = ' PT';
|
|
|
|
|
time = (time / 480).toFixed(1);
|
|
|
|
|
} else if (time >= 60 || this.$store.state.settings.dontShowMinutes) {
|
|
|
|
|
postFix = ' Stunde';
|
|
|
|
|
time = (time / 60).toFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let plural = '';
|
|
|
|
|
|
|
|
|
|
if (((time > 1 || time <= 0) || this.$store.state.settings.dontShowMinutes) && postFix !== ' PT') {
|
|
|
|
|
plural = 'n'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return time + postFix + plural;
|
|
|
|
|
},
|
|
|
|
|
stopActiveTracker() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
vue.trackers.forEach(function (tracker) {
|
|
|
|
|
if (tracker.tracking === true) {
|
|
|
|
|
vue.stopTracking(tracker);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
getTrackingStartTime(tracker) {
|
|
|
|
|
return moment(tracker.trackingStarted).format('LT');
|
|
|
|
|
},
|
|
|
|
|
isTrackerNumber(number) {
|
|
|
|
|
return number.indexOf('#') >= 0;
|
|
|
|
|
},
|
|
|
|
|
deleteTracker(index, archive = false) {
|
|
|
|
|
let component = this;
|
|
|
|
|
this.$store.commit("deleteTracker", index, archive);
|
|
|
|
|
let message = 'Tracker "' + this.$store.state.trashed.number + '" wurde gelöscht';
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: message,
|
|
|
|
|
color: 'blue',
|
|
|
|
|
buttons: [
|
|
|
|
|
['<button><i class="fas fa-undo"></i></button>', function (instance, toast) {
|
|
|
|
|
instance.hide({
|
|
|
|
|
transitionOut: 'fadeOutUp',
|
|
|
|
|
onClosing: function(){
|
|
|
|
|
component.$store.commit('restoreTrashed');
|
|
|
|
|
component.$store.commit('saveTrackers');
|
|
|
|
|
}
|
|
|
|
|
}, toast, 'buttonName');
|
|
|
|
|
}, true]
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
this.$store.commit('saveTrackers');
|
|
|
|
|
},
|
|
|
|
|
showOpenTasksForTracker(tasks) {
|
|
|
|
|
let count = this.getOpenTasksForTracker(tasks);
|
|
|
|
|
return count > 0 ? ' ('+count+' offen)' : '';
|
|
|
|
|
},
|
|
|
|
|
getOpenTasksForTracker(tasks) {
|
|
|
|
|
if (!tasks) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let counter = 0;
|
|
|
|
|
|
|
|
|
|
tasks.forEach((task) => {
|
|
|
|
|
if (!task.done) {
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return counter;
|
|
|
|
|
},
|
|
|
|
|
timeBoxTimeLeft(tracker) {
|
|
|
|
|
return Number(tracker.timeBoxMinutes - this.getTotalTime(tracker, true));
|
|
|
|
|
},
|
|
|
|
|
startTimeBox(tracker, minutes) {
|
|
|
|
|
Notification.requestPermission();
|
|
|
|
|
this.startTracking(tracker, false, minutes);
|
|
|
|
|
},
|
|
|
|
|
openTasksForTracker(tracker) {
|
|
|
|
|
this.$store.commit('selectTracker', tracker);
|
|
|
|
|
|
|
|
|
|
let canvas = document.getElementById('trackerTasksModal');
|
|
|
|
|
let tasksCanvas = new Offcanvas(canvas);
|
|
|
|
|
tasksCanvas.show();
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.tracker-time-info {
|
|
|
|
|
margin-top: 1em;
|
|
|
|
|
clear: both;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.trackingNameField {
|
|
|
|
|
max-height: 1em;
|
|
|
|
|
margin-bottom: 1px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-timebox input {
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-timebox input:focus {
|
|
|
|
|
color: lightgray;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</style>
|