You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 

464 lines
15 KiB

const TimeTrack = {
data() {
return {
theme: 'quartz',
themes: null,
dashboardLogo: 'assets/img/logo.png',
ticketSystemUrl: '',
showPT: true,
linkTarget: '_blank',
inputs: {
importJson: ''
},
tickets: [],
archive: [],
worktimeTracker: {
tracking: false,
number: 'Worktime',
trackingStarted: null,
trackingStopped: null,
history: []
},
selectedTicket: null,
searchQuery: '',
portal: '',
portals: null,
importStringForPortals: '',
publicDB: false,
fun: false,
sounds: {
bad: [
'alert',
'wilhelm',
'wtf',
'jesus_wtf'
],
animals: [
'meow',
'moo',
'quack',
'pika',
'transition'
]
},
}
},
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 storedPublicDB = localStorage.getItem('publicDB');
this.publicDB = (storedPublicDB == null || 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 ? null : storedPortals;
let storedFun = localStorage.getItem('fun');
this.fun = storedFun == null ? false : storedFun;
},
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('publicDB', this.publicDB);
localStorage.setItem('fun', this.fun);
localStorage.setItem('theme', this.theme);
localStorage.setItem('portal', this.portal);
this.$forceUpdate();
},
resetToDefault() {
this.updateStorage();
},
addTrackedTicket() {
let newTicket = {
tracking: false,
number: '#',
trackingStarted: null,
trackingStopped: null,
history: []
};
this.tickets.push(newTicket);
this.updateStorage();
},
startTracking(ticket, individual = false) {
if (!individual) {
this.stopTrackingTicket();
}
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) {
ticket.trackingStopped = moment();
ticket.tracking = false;
let minutesSpent = moment.duration(
ticket.trackingStopped.diff(ticket.trackingStarted)
).as('minutes');
let historyEntry = {
trackingStarted: ticket.trackingStarted,
trackingStopped: ticket.trackingStopped,
manually: false,
minutes: Math.round(minutesSpent)
};
if (ticket.paused) {
historyEntry.pause = true;
}
ticket.history.push();
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) {
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);
},
getPortalLink (test = false) {
let finalPortalName = this.portal.replaceAll('_', '-');
finalPortalName.replaceAll('-test', '');
finalPortalName += test ? '-test' : '';
return 'https://' + finalPortalName + '.vemap.com';
},
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;
},
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'
});
playSound(oneOf(vue.sounds.animals));
vue.updateStorage();
})
},
importPortalsJson() {
let vue = this;
if (this.importStringForPortals !== '') {
this.portals = JSON.parse(this.importStringForPortals);
this.importStringForPortals = '';
console.log(vue.portals.length);
iziToast.show({
message: 'Portalnamen importiert',
color: 'green'
});
this.updateStorage();
}
}
},
watch: {
publicDB() {
this.updateStorage();
this.$forceUpdate();
},
showPT() {
this.updateStorage();
this.$forceUpdate();
},
theme() {
this.updateStorage();
this.$forceUpdate();
},
fun() {
this.updateStorage();
this.$forceUpdate();
}
},
computed: {
exportJson() {
return JSON.stringify({
tickets: this.tickets,
ticketSystemUrl: this.ticketSystemUrl,
showPT: this.showPT,
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();
}
Vue.createApp(TimeTrack).mount('#root');