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.
858 lines
29 KiB
858 lines
29 KiB
Array.prototype.pushToBeginning = function (toPush) { |
|
return this.unshift.apply(this, [toPush]); |
|
} |
|
|
|
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.pushToBeginning({ |
|
tracking: false, |
|
number: '#', |
|
trackingStarted: null, |
|
trackingStopped: null, |
|
history: [] |
|
}); |
|
|
|
this.updateStorage(); |
|
}, |
|
createTimeBox() { |
|
this.tickets.pushToBeginning({ |
|
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); |
|
let component = this; |
|
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; |
|
} |
|
} |
|
|
|
if (this.ticketSystemUrl && this.isTicketNumber(ticket.number)) { |
|
iziToast.show({ |
|
message: 'Buchung gespeichert', |
|
color: 'blue', |
|
buttons: [ |
|
['<button><img src="'+component.ticketSystemIcon+'" class="ticket-icon"/></button>', function (instance, toast) { |
|
instance.hide({ |
|
transitionOut: 'fadeOutUp', |
|
onClosing: function () { |
|
window.open( |
|
component.ticketSystemUrl + ticket.number.replace('#', '').trim()+'/time_entries/new?time_entry[hours]='+(Math.round(minutesSpent)/60), |
|
'_blank' |
|
); |
|
} |
|
}, toast, 'buttonName'); |
|
}, true] |
|
] |
|
}); |
|
} |
|
|
|
ticket.history.pushToBeginning(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.pushToBeginning(restoredTracker); |
|
this.updateStorage(); |
|
}, |
|
archiveTracker(index) { |
|
if (this.tickets[index].tracking) { |
|
this.stopActiveTracker(); |
|
} |
|
|
|
this.archive.pushToBeginning(this.tickets[index]); |
|
this.tickets.splice(index, 1); |
|
this.updateStorage(); |
|
}, |
|
reactivateTicket(index) { |
|
this.tickets.pushToBeginning(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.pushToBeginning({ |
|
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.pushToBeginning(ticket); |
|
} else { |
|
tickets.pushToBeginning(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.pushToBeginning(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.pushToBeginning(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.pushToBeginning(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.pushToBeginning({ |
|
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+' offen)' : ''; |
|
} |
|
}, |
|
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 |
|
}); |
|
}, |
|
ticketSystemIcon() { |
|
if (this.ticketSystemUrl) { |
|
if (this.ticketSystemUrl.search('redmine')) { |
|
return 'assets/img/redmine.png'; |
|
} |
|
|
|
if (this.ticketSystemUrl.search('jira')) { |
|
return 'assets/img/jira.png'; |
|
} |
|
|
|
if (this.ticketSystemUrl.search('gitlab')) { |
|
return 'assets/img/gitlab.png'; |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
|
|
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); |
|
} |