|
|
|
|
const TimeTrack = {
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
theme: 'quartz',
|
|
|
|
|
themes: null,
|
|
|
|
|
dashboardLogo: 'assets/img/logo.png',
|
|
|
|
|
ticketSystemUrl: '',
|
|
|
|
|
showPT: true,
|
|
|
|
|
linkTarget: '_blank',
|
|
|
|
|
inputs: {
|
|
|
|
|
importJson: ''
|
|
|
|
|
},
|
|
|
|
|
tickets: [],
|
|
|
|
|
archive: [],
|
|
|
|
|
selectedTicket: null,
|
|
|
|
|
searchQuery: ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
this.loadStorage();
|
|
|
|
|
this.fetchThemes();
|
|
|
|
|
moment.locale('de');
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
vue.$forceUpdate();
|
|
|
|
|
}, 1000 * 60);
|
|
|
|
|
|
|
|
|
|
vue.loadTooltips();
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
vue.loadTooltips();
|
|
|
|
|
}, 5000);
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
loadTooltips() {
|
|
|
|
|
let tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
|
|
|
|
let tooltipList = 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 || false) ? true : storedShowPT;
|
|
|
|
|
|
|
|
|
|
let storedTheme = localStorage.getItem('theme');
|
|
|
|
|
this.theme = storedTheme == null ? 'materia' : storedTheme;
|
|
|
|
|
},
|
|
|
|
|
updateStorage() {
|
|
|
|
|
localStorage.setItem('tickets', JSON.stringify(this.tickets));
|
|
|
|
|
localStorage.setItem('archive', JSON.stringify(this.archive));
|
|
|
|
|
localStorage.setItem('ticketSystemUrl', this.ticketSystemUrl);
|
|
|
|
|
localStorage.setItem('showPT', this.showPT);
|
|
|
|
|
localStorage.setItem('theme', this.theme);
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
resetToDefault() {
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
addTrackedTicket() {
|
|
|
|
|
let newTicket = {
|
|
|
|
|
tracking: false,
|
|
|
|
|
number: '#',
|
|
|
|
|
trackingStarted: null,
|
|
|
|
|
trackingStopped: null,
|
|
|
|
|
history: []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.tickets.push(newTicket);
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
startTracking(ticket) {
|
|
|
|
|
this.stopTrackingTicket();
|
|
|
|
|
ticket.trackingStarted = moment();
|
|
|
|
|
ticket.tracking = true;
|
|
|
|
|
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
stopTracking(ticket) {
|
|
|
|
|
ticket.trackingStopped = moment();
|
|
|
|
|
ticket.tracking = false;
|
|
|
|
|
|
|
|
|
|
let minutesSpent = moment.duration(
|
|
|
|
|
ticket.trackingStopped.diff(ticket.trackingStarted)
|
|
|
|
|
).as('minutes');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ticket.history.push({
|
|
|
|
|
trackingStarted: ticket.trackingStarted,
|
|
|
|
|
trackingStopped: ticket.trackingStopped,
|
|
|
|
|
manually: false,
|
|
|
|
|
minutes: Math.round(minutesSpent)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ticket.trackingStarted = null;
|
|
|
|
|
ticket.trackingStopped = null;
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
postFix = ' Stunde';
|
|
|
|
|
time = (time / 60).toFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let plural = '';
|
|
|
|
|
|
|
|
|
|
if ((time > 1 || time <= 0) && postFix !== ' PT') {
|
|
|
|
|
plural = 'n'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return time + postFix + plural;
|
|
|
|
|
},
|
|
|
|
|
stopTrackingTicket() {
|
|
|
|
|
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;
|
|
|
|
|
},
|
|
|
|
|
deleteTicket(index, archive = false) {
|
|
|
|
|
if (archive) {
|
|
|
|
|
this.archive.splice(index, 1);
|
|
|
|
|
} else {
|
|
|
|
|
this.tickets.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
},
|
|
|
|
|
archiveTicket(index) {
|
|
|
|
|
if (this.tickets[index].tracking) {
|
|
|
|
|
this.stopTrackingTicket();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.selectedTicket.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");
|
|
|
|
|
|
|
|
|
|
alert('Text kopiert!');
|
|
|
|
|
},
|
|
|
|
|
fetchThemes() {
|
|
|
|
|
let vue = this;
|
|
|
|
|
|
|
|
|
|
axios.get(
|
|
|
|
|
'https://bootswatch.com/api/5.json'
|
|
|
|
|
).then((response) => {
|
|
|
|
|
vue.themes = response.data.themes;
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
console.log(error);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
showHistoryForTicket(ticket) {
|
|
|
|
|
this.selectedTicket = ticket;
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
let historyModal = new bootstrap.Modal(document.getElementById('historyModal'));
|
|
|
|
|
historyModal.toggle();
|
|
|
|
|
}, 50)
|
|
|
|
|
},
|
|
|
|
|
collectDataForDay() {
|
|
|
|
|
let collection = [];
|
|
|
|
|
|
|
|
|
|
this.tickets.forEach((ticket) => {
|
|
|
|
|
ticket.history.forEach((historyEntry) => {
|
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === moment().format("MMM Do YY")) {
|
|
|
|
|
collection.push(historyEntry);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return collection;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
showPT() {
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
},
|
|
|
|
|
theme() {
|
|
|
|
|
this.updateStorage();
|
|
|
|
|
this.$forceUpdate();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
exportJson() {
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
tickets: this.tickets,
|
|
|
|
|
ticketSystemUrl: this.ticketSystemUrl,
|
|
|
|
|
showPT: this.showPT,
|
|
|
|
|
theme: this.theme
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vue.createApp(TimeTrack).mount('#root');
|