|
|
|
|
const TimeTrack = {
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
experimental: {
|
|
|
|
|
trackWorktime: false,
|
|
|
|
|
boardView: false,
|
|
|
|
|
snippetSpace: false,
|
|
|
|
|
portalSwitcher: true
|
|
|
|
|
},
|
|
|
|
|
view: 'board',
|
|
|
|
|
theme: 'materia',
|
|
|
|
|
themes: null,
|
|
|
|
|
dashboardLogo: 'assets/img/logo.png',
|
|
|
|
|
ticketSystemUrl: '',
|
|
|
|
|
showPT: true,
|
|
|
|
|
dontShowMinutes: false,
|
|
|
|
|
linkTarget: '_blank',
|
|
|
|
|
inputs: {
|
|
|
|
|
importJson: ''
|
|
|
|
|
},
|
|
|
|
|
tickets: [],
|
|
|
|
|
archive: [],
|
|
|
|
|
trashed: {},
|
|
|
|
|
worktimeTracker: {
|
|
|
|
|
tracking: false,
|
|
|
|
|
number: 'Worktime',
|
|
|
|
|
trackingStarted: null,
|
|
|
|
|
trackingStopped: null,
|
|
|
|
|
history: []
|
|
|
|
|
},
|
|
|
|
|
selectedTracker: null,
|
|
|
|
|
searchQuery: '',
|
|
|
|
|
portal: '',
|
|
|
|
|
portals: null,
|
|
|
|
|
importStringForPortals: '',
|
|
|
|
|
publicDB: false,
|
|
|
|
|
fun: false,
|
|
|
|
|
sounds: {
|
|
|
|
|
bad: [
|
|
|
|
|
'alert',
|
|
|
|
|
'wilhelm',
|
|
|
|
|
// 'wtf',
|
|
|
|
|
// 'jesus_wtf'
|
|
|
|
|
],
|
|
|
|
|
animals: [
|
|
|
|
|
// 'meow',
|
|
|
|
|
// 'moo',
|
|
|
|
|
// 'quack',
|
|
|
|
|
// 'pika',
|
|
|
|
|
'transition'
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
snippets: [],
|
|
|
|
|
codeMirrors: [],
|
|
|
|
|
customBookingValue: '',
|
|
|
|
|
customDateForPastDays: '',
|
|
|
|
|
newTaskInput: ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
this.loadStorage();
|
|
|
|
|
this.fetchThemes();
|
|
|
|
|
|
|
|
|
|
moment.locale('de');
|
|
|
|
|
this.customDateForPastDays = moment().format();
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
vue.$forceUpdate();
|
|
|
|
|
}, 1000 * 60);
|
|
|
|
|
|
|
|
|
|
vue.loadTooltips();
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
vue.loadTooltips();
|
|
|
|
|
}, 5000);
|
|
|
|
|
|
|
|
|
|
if (this.experimental.snippetSpace) {
|
|
|
|
|
this.snippets.forEach((snippet) => {
|
|
|
|
|
vue.loadSnippet(snippet);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
vue.updateStorage();
|
|
|
|
|
}, 5000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
vue.checkTimeBoxes();
|
|
|
|
|
}, 10000);
|
|
|
|
|
|
|
|
|
|
if (this.fun && localStorage.getItem('noJokes') === null) {
|
|
|
|
|
let jokeService = new JokeService();
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
jokeService.tell();
|
|
|
|
|
}, 1_800_000)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
loadTooltips() {
|
|
|
|
|
let tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
|
|
|
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
|
|
|
return new bootstrap.Tooltip(tooltipTriggerEl)
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
loadStorage() {
|
|
|
|
|
let storedTickets = JSON.parse(localStorage.getItem('tickets'));
|
|
|
|
|
this.tickets = storedTickets == null ? [] : storedTickets;
|
|
|
|
|
|
|
|
|
|
let storedArchive = JSON.parse(localStorage.getItem('archive'));
|
|
|
|
|
this.archive = storedArchive == null ? [] : storedArchive;
|
|
|
|
|
|
|
|
|
|
let storedTicketSystemUrl = localStorage.getItem('ticketSystemUrl');
|
|
|
|
|
this.ticketSystemUrl = storedTicketSystemUrl == null ? '' : storedTicketSystemUrl;
|
|
|
|
|
|
|
|
|
|
let storedShowPT = localStorage.getItem('showPT');
|
|
|
|
|
this.showPT = (storedShowPT == null || storedShowPT === 'false') ? true : storedShowPT;
|
|
|
|
|
|
|
|
|
|
let storedDontShowMinutes = localStorage.getItem('dontShowMinutes');
|
|
|
|
|
this.dontShowMinutes = (storedDontShowMinutes == null || storedDontShowMinutes === 'false') ? false : Boolean(storedDontShowMinutes);
|
|
|
|
|
|
|
|
|
|
let storedPublicDB = localStorage.getItem('publicDB');
|
|
|
|
|
storedPublicDB = storedPublicDB === 'true';
|
|
|
|
|
this.publicDB = (storedPublicDB == null || storedPublicDB === 'false') ? false : storedPublicDB;
|
|
|
|
|
|
|
|
|
|
let storedTheme = localStorage.getItem('theme');
|
|
|
|
|
this.theme = storedTheme == null ? 'materia' : storedTheme;
|
|
|
|
|
|
|
|
|
|
let storedPortal = localStorage.getItem('portal');
|
|
|
|
|
this.portal = storedPortal == null ? '' : storedPortal;
|
|
|
|
|
|
|
|
|
|
let storedPortals = JSON.parse(localStorage.getItem('portals'));
|
|
|
|
|
this.portals = storedPortals == null ? [] : storedPortals;
|
|
|
|
|
|
|
|
|
|
let storedFun = localStorage.getItem('fun');
|
|
|
|
|
this.fun = storedFun == null || storedFun === 'false' ? false : storedFun;
|
|
|
|
|
|
|
|
|
|
// let storedSnippets = JSON.parse(localStorage.getItem('snippets'));
|
|
|
|
|
// this.snippets = storedSnippets == null ? [] : storedSnippets;
|
|
|
|
|
},
|
|
|
|
|
updateStorage() {
|
|
|
|
|
localStorage.setItem('tickets', JSON.stringify(this.tickets));
|
|
|
|
|
localStorage.setItem('archive', JSON.stringify(this.archive));
|
|
|
|
|
localStorage.setItem('portals', JSON.stringify(this.portals));
|
|
|
|
|
localStorage.setItem('ticketSystemUrl', this.ticketSystemUrl);
|
|
|
|
|
localStorage.setItem('showPT', this.showPT);
|
|
|
|
|
localStorage.setItem('dontShowMinutes', Boolean(this.dontShowMinutes));
|
|
|
|
|
localStorage.setItem('publicDB', this.publicDB);
|
|
|
|
|
localStorage.setItem('fun', this.fun);
|
|
|
|
|
localStorage.setItem('theme', this.theme);
|
|
|
|
|
localStorage.setItem('portal', this.portal);
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
resetToDefault() {
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
createTracker() {
|
|
|
|
|
this.tickets.push({
|
|
|
|
|
tracking: false,
|
|
|
|
|
number: '#',
|
|
|
|
|
trackingStarted: null,
|
|
|
|
|
trackingStopped: null,
|
|
|
|
|
history: []
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
createTimeBox() {
|
|
|
|
|
this.tickets.push({
|
|
|
|
|
tracking: false,
|
|
|
|
|
number: 'Timebox ',
|
|
|
|
|
trackingStarted: null,
|
|
|
|
|
trackingStopped: null,
|
|
|
|
|
isTimeBox: true,
|
|
|
|
|
history: []
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
startTimeBox(ticket, minutes) {
|
|
|
|
|
Notification.requestPermission();
|
|
|
|
|
this.startTracking(ticket, false, minutes);
|
|
|
|
|
},
|
|
|
|
|
startTracking(ticket, individual = false, timeBoxMinutes = null) {
|
|
|
|
|
if (!individual) {
|
|
|
|
|
this.stopActiveTracker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (timeBoxMinutes) {
|
|
|
|
|
ticket.timeBoxMinutes = timeBoxMinutes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ticket.status = 'wip';
|
|
|
|
|
|
|
|
|
|
ticket.trackingStarted = moment();
|
|
|
|
|
ticket.tracking = true;
|
|
|
|
|
|
|
|
|
|
let noNoTickets = ['#1920', '#3110', '#2492', '#2419', '#1256'];
|
|
|
|
|
|
|
|
|
|
if (this.fun && noNoTickets.includes(ticket.number)) {
|
|
|
|
|
playSound(oneOf(this.sounds.bad));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
stopTracking(ticket) {
|
|
|
|
|
// console.log(ticket);
|
|
|
|
|
ticket.trackingStopped = moment();
|
|
|
|
|
ticket.tracking = false;
|
|
|
|
|
|
|
|
|
|
let minutesSpent = moment.duration(
|
|
|
|
|
ticket.trackingStopped.diff(ticket.trackingStarted)
|
|
|
|
|
).as('minutes');
|
|
|
|
|
|
|
|
|
|
if (minutesSpent > 0) {
|
|
|
|
|
let historyEntry = {
|
|
|
|
|
trackingStarted: ticket.trackingStarted,
|
|
|
|
|
trackingStopped: ticket.trackingStopped,
|
|
|
|
|
manually: false,
|
|
|
|
|
minutes: Math.round(minutesSpent)
|
|
|
|
|
};
|
|
|
|
|
// console.log(historyEntry);
|
|
|
|
|
|
|
|
|
|
if (this.experimental.trackWorktime) {
|
|
|
|
|
if (ticket.paused) {
|
|
|
|
|
historyEntry.pause = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ticket.history.push(historyEntry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ticket.trackingStarted = null;
|
|
|
|
|
ticket.trackingStopped = null;
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
pauseTracking(ticket) {
|
|
|
|
|
ticket.trackingStopped = moment();
|
|
|
|
|
ticket.tracking = false;
|
|
|
|
|
ticket.paused = true;
|
|
|
|
|
|
|
|
|
|
this.stopTracking(ticket);
|
|
|
|
|
},
|
|
|
|
|
resumeTracking(ticket) {
|
|
|
|
|
ticket.trackingStarted = moment();
|
|
|
|
|
ticket.tracking = true;
|
|
|
|
|
ticket.paused = false;
|
|
|
|
|
|
|
|
|
|
let noNoTickets = ['#1920', '#3110', '#2492', '#2419', '#1256'];
|
|
|
|
|
|
|
|
|
|
if (this.fun && noNoTickets.includes(ticket.number)) {
|
|
|
|
|
playSound(oneOf(this.sounds.bad))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
formattedDate(date) {
|
|
|
|
|
return moment(date).format('llll');
|
|
|
|
|
},
|
|
|
|
|
exactTimestamp(date) {
|
|
|
|
|
return moment(date).format('LTS');
|
|
|
|
|
},
|
|
|
|
|
currentTrackingRunningFor(ticket) {
|
|
|
|
|
return this.timeWithPostFix(Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes')));
|
|
|
|
|
},
|
|
|
|
|
getTotalTime(ticket, raw = false) {
|
|
|
|
|
let totalTime = 0;
|
|
|
|
|
|
|
|
|
|
if (ticket.history.length > 0) {
|
|
|
|
|
ticket.history.forEach(function (historyEntry) {
|
|
|
|
|
totalTime += Math.round(historyEntry.minutes);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ticket.tracking) {
|
|
|
|
|
totalTime += Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (raw) {
|
|
|
|
|
return totalTime;
|
|
|
|
|
} else {
|
|
|
|
|
return this.timeWithPostFix(totalTime);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
getTotalTimeToday(ticket) {
|
|
|
|
|
let totalTime = 0;
|
|
|
|
|
|
|
|
|
|
if (ticket.history.length > 0) {
|
|
|
|
|
ticket.history.forEach(function (historyEntry) {
|
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === moment().format("MMM Do YY")) {
|
|
|
|
|
totalTime += Math.round(historyEntry.minutes);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ticket.tracking) {
|
|
|
|
|
totalTime += Math.round(moment.duration(moment().diff(ticket.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.dontShowMinutes) {
|
|
|
|
|
postFix = ' Stunde';
|
|
|
|
|
time = (time / 60).toFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let plural = '';
|
|
|
|
|
|
|
|
|
|
if (((time > 1 || time <= 0) || this.dontShowMinutes) && postFix !== ' PT') {
|
|
|
|
|
plural = 'n'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return time + postFix + plural;
|
|
|
|
|
},
|
|
|
|
|
stopActiveTracker() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
vue.tickets.forEach(function (ticket) {
|
|
|
|
|
if (ticket.tracking === true) {
|
|
|
|
|
vue.stopTracking(ticket);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
getTrackingStartTime(ticket) {
|
|
|
|
|
return moment(ticket.trackingStarted).format('LT');
|
|
|
|
|
},
|
|
|
|
|
isTicketNumber(number) {
|
|
|
|
|
return number.indexOf('#') >= 0;
|
|
|
|
|
},
|
|
|
|
|
deleteTracker(index, archive = false) {
|
|
|
|
|
let component = this;
|
|
|
|
|
let message = '';
|
|
|
|
|
|
|
|
|
|
if (archive) {
|
|
|
|
|
Object.assign(this.trashed, this.archive[index]);
|
|
|
|
|
let name = this.archive[index].number;
|
|
|
|
|
message = 'Tracker "' + name + '" wurde gelöscht';
|
|
|
|
|
this.archive.splice(index, 1);
|
|
|
|
|
} else {
|
|
|
|
|
Object.assign(this.trashed, this.tickets[index]);
|
|
|
|
|
let name = this.tickets[index].number;
|
|
|
|
|
message = 'Tracker "' + name + '" wurde gelöscht';
|
|
|
|
|
this.tickets.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: message,
|
|
|
|
|
color: 'blue',
|
|
|
|
|
buttons: [
|
|
|
|
|
['<button><i class="fas fa-undo"></i></button>', function (instance, toast) {
|
|
|
|
|
instance.hide({
|
|
|
|
|
transitionOut: 'fadeOutUp',
|
|
|
|
|
onClosing: function(instance, toast, closedBy){
|
|
|
|
|
component.restoreTrashed();
|
|
|
|
|
}
|
|
|
|
|
}, toast, 'buttonName');
|
|
|
|
|
}, true]
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
restoreTrashed() {
|
|
|
|
|
let restoredTracker = {};
|
|
|
|
|
Object.assign(restoredTracker, this.trashed);
|
|
|
|
|
this.trashed = {};
|
|
|
|
|
|
|
|
|
|
this.tickets.push(restoredTracker);
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
archiveTracker(index) {
|
|
|
|
|
if (this.tickets[index].tracking) {
|
|
|
|
|
this.stopActiveTracker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.archive.push(this.tickets[index]);
|
|
|
|
|
this.tickets.splice(index, 1);
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
reactivateTicket(index) {
|
|
|
|
|
this.tickets.push(this.archive[index]);
|
|
|
|
|
this.archive.splice(index, 1);
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
deleteHistoryEntry(ticketIndex, historyIndex) {
|
|
|
|
|
if (ticketIndex) {
|
|
|
|
|
this.tickets[ticketIndex].history.splice(historyIndex, 1);
|
|
|
|
|
} else {
|
|
|
|
|
this.selectedTracker.history.splice(historyIndex, 1);
|
|
|
|
|
}
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
bookTimeManually(ticket, minutes) {
|
|
|
|
|
ticket.history.push({
|
|
|
|
|
trackingStarted: moment(),
|
|
|
|
|
trackingStopped: moment(),
|
|
|
|
|
manually: true,
|
|
|
|
|
minutes: Math.round(minutes)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
importData() {
|
|
|
|
|
let json = JSON.parse(this.inputs.importJson);
|
|
|
|
|
|
|
|
|
|
if (json.trackedTickets) {
|
|
|
|
|
this.tickets = this.extractTicketsFromLegacyJson(json.trackedTickets);
|
|
|
|
|
this.archive = this.extractArchivedTicketsFromLegacyJson(json.trackedTickets);
|
|
|
|
|
} else {
|
|
|
|
|
this.tickets = json.tickets;
|
|
|
|
|
this.archive = json.archive ?? [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.ticketSystemUrl = json.redmineUrl ?? json.ticketSystemUrl;
|
|
|
|
|
this.showPT = json.showPT;
|
|
|
|
|
this.theme = json.theme;
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
location.reload();
|
|
|
|
|
},
|
|
|
|
|
extractTicketsFromLegacyJson(tickets) {
|
|
|
|
|
return this.extractTickets(tickets);
|
|
|
|
|
},
|
|
|
|
|
extractArchivedTicketsFromLegacyJson(tickets) {
|
|
|
|
|
return this.extractTickets(tickets, true);
|
|
|
|
|
},
|
|
|
|
|
extractTickets(ticketCollection, forArchive = false) {
|
|
|
|
|
let tickets = [];
|
|
|
|
|
let archive = [];
|
|
|
|
|
|
|
|
|
|
ticketCollection.forEach((ticket) => {
|
|
|
|
|
if (ticket.archived || (ticket.active && ticket.active === false)) {
|
|
|
|
|
archive.push(ticket);
|
|
|
|
|
} else {
|
|
|
|
|
tickets.push(ticket)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return forArchive ? archive : tickets;
|
|
|
|
|
},
|
|
|
|
|
copy2Clipboard() {
|
|
|
|
|
let copyText = document.getElementById("exportJsonInput");
|
|
|
|
|
|
|
|
|
|
copyText.select();
|
|
|
|
|
copyText.setSelectionRange(0, 99999);
|
|
|
|
|
|
|
|
|
|
document.execCommand("copy");
|
|
|
|
|
},
|
|
|
|
|
fetchThemes() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
axios.get(
|
|
|
|
|
'https://bootswatch.com/api/5.json'
|
|
|
|
|
).then((response) => {
|
|
|
|
|
vue.themes = response.data.themes;
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
console.log(error);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
showHistoryForTracker(tracker) {
|
|
|
|
|
this.selectedTracker = tracker;
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
let historyModal = new bootstrap.Modal(document.getElementById('historyModal'));
|
|
|
|
|
historyModal.toggle();
|
|
|
|
|
}, 50);
|
|
|
|
|
},
|
|
|
|
|
openTasksForTracker(tracker) {
|
|
|
|
|
this.selectedTracker = tracker;
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
let tasksModal = new bootstrap.Modal(document.getElementById('trackerTasksModal'));
|
|
|
|
|
tasksModal.toggle();
|
|
|
|
|
}, 50);
|
|
|
|
|
},
|
|
|
|
|
showCustomBookingForTracker(ticket) {
|
|
|
|
|
this.selectedTracker = ticket;
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
let customBookingModal = new bootstrap.Modal(document.getElementById('customBookingModal'));
|
|
|
|
|
customBookingModal.toggle();
|
|
|
|
|
}, 50);
|
|
|
|
|
},
|
|
|
|
|
makeCustomBooking(subtract = false) {
|
|
|
|
|
if (subtract) {
|
|
|
|
|
this.customBookingValue -= (this.customBookingValue * 2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Buchung erfolgreich',
|
|
|
|
|
color: 'green'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.bookTimeManually(this.selectedTracker, this.customBookingValue);
|
|
|
|
|
},
|
|
|
|
|
getPortalLink (test = false) {
|
|
|
|
|
let finalPortalName = this.portal.replaceAll('_', '-');
|
|
|
|
|
finalPortalName.replaceAll('-test', '');
|
|
|
|
|
finalPortalName += test ? '-test' : '';
|
|
|
|
|
|
|
|
|
|
return 'https://' + finalPortalName + '.vemap.com';
|
|
|
|
|
},
|
|
|
|
|
collectDataForDay(subtractDays = 0, customDate = false) {
|
|
|
|
|
let day = moment().subtract(subtractDays, "days").format("MMM Do YY");
|
|
|
|
|
let collection = [];
|
|
|
|
|
|
|
|
|
|
if (customDate) {
|
|
|
|
|
day = moment(customDate).format("MMM Do YY");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.tickets.forEach((ticket) => {
|
|
|
|
|
ticket.history.forEach((historyEntry) => {
|
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) {
|
|
|
|
|
let newEntry = {};
|
|
|
|
|
Object.assign(newEntry, historyEntry);
|
|
|
|
|
newEntry.ticket = ticket.number;
|
|
|
|
|
|
|
|
|
|
let existingEntry = this.getCollectionItemWithValue(collection, 'ticket', ticket.number);
|
|
|
|
|
|
|
|
|
|
if (existingEntry) {
|
|
|
|
|
existingEntry.minutes = Number(existingEntry.minutes) + Number(newEntry.minutes);
|
|
|
|
|
} else {
|
|
|
|
|
collection.push(newEntry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.archive.forEach((ticket) => {
|
|
|
|
|
ticket.history.forEach((historyEntry) => {
|
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) {
|
|
|
|
|
let newEntry = {};
|
|
|
|
|
Object.assign(newEntry, historyEntry);
|
|
|
|
|
newEntry.ticket = ticket.number;
|
|
|
|
|
|
|
|
|
|
let existingEntry = this.getCollectionItemWithValue(collection, 'ticket', ticket.number);
|
|
|
|
|
|
|
|
|
|
if (existingEntry) {
|
|
|
|
|
existingEntry.minutes = Number(existingEntry.minutes) + Number(newEntry.minutes);
|
|
|
|
|
} else {
|
|
|
|
|
collection.push(newEntry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return collection;
|
|
|
|
|
},
|
|
|
|
|
getCollectionItemWithValue(collection, property, value) {
|
|
|
|
|
let found = false;
|
|
|
|
|
|
|
|
|
|
collection.forEach((item) => {
|
|
|
|
|
if (item[property] === value) {
|
|
|
|
|
found = item;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return found;
|
|
|
|
|
},
|
|
|
|
|
sendPortalChangeRequest() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
let publicDBParam = this.publicDB ? '&publicDB=1' : '';
|
|
|
|
|
axios.get(
|
|
|
|
|
'https://settings.vemap.docker/?portal2change=' + this.portal + publicDBParam
|
|
|
|
|
).then((response) => {
|
|
|
|
|
// console.log(response)
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
// An error is expected here due to apache restarting
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Portal-Wechsel erfolgreich',
|
|
|
|
|
color: 'green'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (vue.fun) {
|
|
|
|
|
playSound(oneOf(vue.sounds.animals));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vue.updateStorage();
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
importPortalsJson() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
if (this.importStringForPortals !== '') {
|
|
|
|
|
this.portals = JSON.parse(this.importStringForPortals);
|
|
|
|
|
|
|
|
|
|
this.importStringForPortals = '';
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Portalnamen importiert',
|
|
|
|
|
color: 'green'
|
|
|
|
|
});
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
createSnippet() {
|
|
|
|
|
let newSnippet = {
|
|
|
|
|
content: '',
|
|
|
|
|
mode: 'htmlmixed',
|
|
|
|
|
id: getRandomID()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.snippets.push(newSnippet);
|
|
|
|
|
this.loadSnippet(this.snippets[this.snippets.length-1]);
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
loadSnippet(snippet) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
let element = document.getElementById(snippet.id);
|
|
|
|
|
if (!element) {
|
|
|
|
|
console.log('Textarea with id '+snippet.id+' not found');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snippet.mirrorInstance = CodeMirror.fromTextArea(element, {
|
|
|
|
|
theme: 'darcula',
|
|
|
|
|
lineNumbers: true,
|
|
|
|
|
mode: snippet.mode,
|
|
|
|
|
value: snippet.content
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
snippet.content = snippet.mirrorInstance.doc.getValue();
|
|
|
|
|
}, 1000);
|
|
|
|
|
}, 100);
|
|
|
|
|
},
|
|
|
|
|
deleteSnippet(index) {
|
|
|
|
|
this.snippets.splice(index, 1);
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Snippet wurde gelöscht',
|
|
|
|
|
color: 'blue'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
copySnippet(snippet) {
|
|
|
|
|
let code = document.getElementById(snippet.id);
|
|
|
|
|
|
|
|
|
|
code.select();
|
|
|
|
|
code.setSelectionRange(0, 99999);
|
|
|
|
|
document.execCommand("copy");
|
|
|
|
|
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Code kopiert!',
|
|
|
|
|
color: 'green'
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
checkTimeBoxes() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
this.tickets.forEach((ticket) => {
|
|
|
|
|
if (ticket.isTimeBox && !ticket.notificated && vue.timeBoxTimeLeft(ticket) <= 0) {
|
|
|
|
|
ticket.notificated = true;
|
|
|
|
|
vue.stopTracking(ticket);
|
|
|
|
|
// alert('Zeit für "'+ticket.number+'" ist abgelaufen!');
|
|
|
|
|
if (Notification.permission === 'granted') {
|
|
|
|
|
new Notification('Timebox zu Ende', {
|
|
|
|
|
body: 'Zeit für "'+ticket.number+'" ist abgelaufen!',
|
|
|
|
|
icon: '/timetrack/assets/img/favicon.ico'
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
iziToast.show({
|
|
|
|
|
message: 'Zeit für "'+ticket.number+'" ist abgelaufen!',
|
|
|
|
|
color: 'red'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
timeBoxTimeLeft(ticket) {
|
|
|
|
|
return Number(ticket.timeBoxMinutes - this.getTotalTime(ticket, true));
|
|
|
|
|
},
|
|
|
|
|
validateBooleans(value) {
|
|
|
|
|
return value === 'true' || value === true;
|
|
|
|
|
},
|
|
|
|
|
tellJoke(category, language = 'en') {
|
|
|
|
|
let jokeService = new JokeService(category ?? undefined, language);
|
|
|
|
|
jokeService.tell();
|
|
|
|
|
},
|
|
|
|
|
addTask() {
|
|
|
|
|
if (this.newTaskInput.length <= 0 || this.newTaskInput.trim() === '') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.selectedTracker.tasks) {
|
|
|
|
|
this.selectedTracker.tasks = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.selectedTracker.tasks.push({
|
|
|
|
|
name: this.newTaskInput,
|
|
|
|
|
done: false,
|
|
|
|
|
created: moment(),
|
|
|
|
|
percentDone: 0,
|
|
|
|
|
finished: null
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.newTaskInput = '';
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
deleteTask(taskIndex) {
|
|
|
|
|
this.selectedTracker.tasks.splice(taskIndex, 1)
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
toggleTask(task) {
|
|
|
|
|
task.done = !task.done;
|
|
|
|
|
|
|
|
|
|
if (task.done) {
|
|
|
|
|
task.finished = moment();
|
|
|
|
|
task.percentDone = 100;
|
|
|
|
|
} else {
|
|
|
|
|
task.finished = null;
|
|
|
|
|
task.percentDone = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
checkForCompletionOfTask(task) {
|
|
|
|
|
task.done = task.percentDone == 100;
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
getOpenTasksForTracker(tasks) {
|
|
|
|
|
if (!tasks) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let counter = 0;
|
|
|
|
|
|
|
|
|
|
tasks.forEach((task) => {
|
|
|
|
|
if (!task.done) {
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return counter;
|
|
|
|
|
},
|
|
|
|
|
showOpenTasksForTracker(tasks) {
|
|
|
|
|
let count = this.getOpenTasksForTracker(tasks);
|
|
|
|
|
return count > 0 ? ' ('+count+')' : '';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
this.stopActiveTracker();
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
publicDB() {
|
|
|
|
|
this.publicDB = this.validateBooleans(this.publicDB)
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
showPT() {
|
|
|
|
|
this.showPT = this.validateBooleans(this.showPT)
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
dontShowMinutes() {
|
|
|
|
|
this.dontShowMinutes = this.validateBooleans(this.dontShowMinutes)
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
theme() {
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
fun() {
|
|
|
|
|
this.fun = this.validateBooleans(this.fun)
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
exportJson() {
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
tickets: this.tickets,
|
|
|
|
|
archive: this.archive,
|
|
|
|
|
ticketSystemUrl: this.ticketSystemUrl,
|
|
|
|
|
showPT: this.showPT,
|
|
|
|
|
fun: this.fun,
|
|
|
|
|
theme: this.theme
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function oneOf(collection) {
|
|
|
|
|
return collection[Math.floor(Math.random()*collection.length)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function playSound(sound, extension = null) {
|
|
|
|
|
let audio = new Audio('/timetrack/assets/audio/' + sound + (extension ?? '.mp3'));
|
|
|
|
|
audio.play();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getRandomID() {
|
|
|
|
|
let text = '';
|
|
|
|
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
|
|
|
}
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TimeTrackApp = Vue.createApp(TimeTrack).mount('#root');
|
|
|
|
|
|
|
|
|
|
function iHateToHaveFun() {
|
|
|
|
|
localStorage.setItem('noJokes', 1);
|
|
|
|
|
}
|