|
|
|
@ -1,3 +1,7 @@
@@ -1,3 +1,7 @@
|
|
|
|
|
Array.prototype.pushToBeginning = function (toPush) { |
|
|
|
|
return this.unshift.apply(this, [toPush]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const TimeTrack = { |
|
|
|
|
data() { |
|
|
|
|
return { |
|
|
|
@ -7,8 +11,7 @@ const TimeTrack = {
@@ -7,8 +11,7 @@ const TimeTrack = {
|
|
|
|
|
snippetSpace: false, |
|
|
|
|
portalSwitcher: true |
|
|
|
|
}, |
|
|
|
|
view: 'trackers', |
|
|
|
|
showHistory: false, |
|
|
|
|
view: 'board', |
|
|
|
|
theme: 'materia', |
|
|
|
|
themes: null, |
|
|
|
|
dashboardLogo: 'assets/img/logo.png', |
|
|
|
@ -21,6 +24,7 @@ const TimeTrack = {
@@ -21,6 +24,7 @@ const TimeTrack = {
|
|
|
|
|
}, |
|
|
|
|
tickets: [], |
|
|
|
|
archive: [], |
|
|
|
|
trashed: {}, |
|
|
|
|
worktimeTracker: { |
|
|
|
|
tracking: false, |
|
|
|
|
number: 'Worktime', |
|
|
|
@ -53,7 +57,8 @@ const TimeTrack = {
@@ -53,7 +57,8 @@ const TimeTrack = {
|
|
|
|
|
snippets: [], |
|
|
|
|
codeMirrors: [], |
|
|
|
|
customBookingValue: '', |
|
|
|
|
customDateForPastDays: '' |
|
|
|
|
customDateForPastDays: '', |
|
|
|
|
newTaskInput: '' |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
mounted() { |
|
|
|
@ -61,7 +66,9 @@ const TimeTrack = {
@@ -61,7 +66,9 @@ const TimeTrack = {
|
|
|
|
|
|
|
|
|
|
this.loadStorage(); |
|
|
|
|
this.fetchThemes(); |
|
|
|
|
|
|
|
|
|
moment.locale('de'); |
|
|
|
|
this.customDateForPastDays = moment().format(); |
|
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
|
|
vue.$forceUpdate(); |
|
|
|
@ -153,7 +160,7 @@ const TimeTrack = {
@@ -153,7 +160,7 @@ const TimeTrack = {
|
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
|
createTracker() { |
|
|
|
|
this.tickets.push({ |
|
|
|
|
this.tickets.pushToBeginning({ |
|
|
|
|
tracking: false, |
|
|
|
|
number: '#', |
|
|
|
|
trackingStarted: null, |
|
|
|
@ -164,7 +171,7 @@ const TimeTrack = {
@@ -164,7 +171,7 @@ const TimeTrack = {
|
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
|
createTimeBox() { |
|
|
|
|
this.tickets.push({ |
|
|
|
|
this.tickets.pushToBeginning({ |
|
|
|
|
tracking: false, |
|
|
|
|
number: 'Timebox ', |
|
|
|
|
trackingStarted: null, |
|
|
|
@ -204,6 +211,7 @@ const TimeTrack = {
@@ -204,6 +211,7 @@ const TimeTrack = {
|
|
|
|
|
}, |
|
|
|
|
stopTracking(ticket) { |
|
|
|
|
// console.log(ticket);
|
|
|
|
|
let component = this; |
|
|
|
|
ticket.trackingStopped = moment(); |
|
|
|
|
ticket.tracking = false; |
|
|
|
|
|
|
|
|
@ -218,7 +226,6 @@ const TimeTrack = {
@@ -218,7 +226,6 @@ const TimeTrack = {
|
|
|
|
|
manually: false, |
|
|
|
|
minutes: Math.round(minutesSpent) |
|
|
|
|
}; |
|
|
|
|
// console.log(historyEntry);
|
|
|
|
|
|
|
|
|
|
if (this.experimental.trackWorktime) { |
|
|
|
|
if (ticket.paused) { |
|
|
|
@ -226,7 +233,34 @@ const TimeTrack = {
@@ -226,7 +233,34 @@ const TimeTrack = {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ticket.history.push(historyEntry); |
|
|
|
|
if (this.ticketSystemUrl && this.isTicketNumber(ticket.number)) { |
|
|
|
|
let finishedTasks = ticket.tasks.filter((task) => { |
|
|
|
|
return task.finished > ticket.trackingStarted && task.finished < ticket.trackingStopped; |
|
|
|
|
}).map((task) => { |
|
|
|
|
return task.name; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
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)+ |
|
|
|
|
'&time_entry[activity_id]=9&time_entry[comments]='+finishedTasks.join(', '), |
|
|
|
|
'_blank' |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
}, toast, 'buttonName'); |
|
|
|
|
}, true] |
|
|
|
|
] |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ticket.history.pushToBeginning(historyEntry); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ticket.trackingStarted = null; |
|
|
|
@ -242,6 +276,28 @@ const TimeTrack = {
@@ -242,6 +276,28 @@ const TimeTrack = {
|
|
|
|
|
|
|
|
|
|
this.stopTracking(ticket); |
|
|
|
|
}, |
|
|
|
|
sendLastBookingToTicketSystem(ticket) { |
|
|
|
|
let component = this; |
|
|
|
|
let latestHistoryItem = ticket.history[0]; |
|
|
|
|
|
|
|
|
|
if (this.ticketSystemUrl && this.isTicketNumber(ticket.number)) { |
|
|
|
|
let finishedTasks = []; |
|
|
|
|
|
|
|
|
|
if (ticket.tasks && ticket.tasks.length > 0) { |
|
|
|
|
finishedTasks = ticket.tasks.filter((task) => { |
|
|
|
|
return task.finished > latestHistoryItem.trackingStarted && task.finished < latestHistoryItem.trackingStopped; |
|
|
|
|
}).map((task) => { |
|
|
|
|
return task.name; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
window.open( |
|
|
|
|
component.ticketSystemUrl + ticket.number.replace('#', '').trim()+'/time_entries/new?time_entry[hours]='+(Math.round(latestHistoryItem.minutes)/60)+ |
|
|
|
|
'&time_entry[activity_id]=9&time_entry[comments]='+finishedTasks.join(', '), |
|
|
|
|
'_blank' |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
resumeTracking(ticket) { |
|
|
|
|
ticket.trackingStarted = moment(); |
|
|
|
|
ticket.tracking = true; |
|
|
|
@ -336,12 +392,16 @@ const TimeTrack = {
@@ -336,12 +392,16 @@ const TimeTrack = {
|
|
|
|
|
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); |
|
|
|
@ -350,29 +410,39 @@ const TimeTrack = {
@@ -350,29 +410,39 @@ const TimeTrack = {
|
|
|
|
|
iziToast.show({ |
|
|
|
|
message: message, |
|
|
|
|
color: 'blue', |
|
|
|
|
position: 'topCenter' |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
iziToast.show({ |
|
|
|
|
title: 'Tracker archiviert', |
|
|
|
|
color: 'green', |
|
|
|
|
position: 'topCenter' |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.archive.push(this.tickets[index]); |
|
|
|
|
this.archive.pushToBeginning(this.tickets[index]); |
|
|
|
|
this.tickets.splice(index, 1); |
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
|
reactivateTicket(index) { |
|
|
|
|
this.view = 'trackers'; |
|
|
|
|
this.tickets.push(this.archive[index]); |
|
|
|
|
this.tickets.pushToBeginning(this.archive[index]); |
|
|
|
|
this.archive.splice(index, 1); |
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
@ -385,7 +455,7 @@ const TimeTrack = {
@@ -385,7 +455,7 @@ const TimeTrack = {
|
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
|
bookTimeManually(ticket, minutes) { |
|
|
|
|
ticket.history.push({ |
|
|
|
|
ticket.history.pushToBeginning({ |
|
|
|
|
trackingStarted: moment(), |
|
|
|
|
trackingStopped: moment(), |
|
|
|
|
manually: true, |
|
|
|
@ -423,9 +493,9 @@ const TimeTrack = {
@@ -423,9 +493,9 @@ const TimeTrack = {
|
|
|
|
|
|
|
|
|
|
ticketCollection.forEach((ticket) => { |
|
|
|
|
if (ticket.archived || (ticket.active && ticket.active === false)) { |
|
|
|
|
archive.push(ticket); |
|
|
|
|
archive.pushToBeginning(ticket); |
|
|
|
|
} else { |
|
|
|
|
tickets.push(ticket) |
|
|
|
|
tickets.pushToBeginning(ticket) |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -450,15 +520,22 @@ const TimeTrack = {
@@ -450,15 +520,22 @@ const TimeTrack = {
|
|
|
|
|
console.log(error); |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
showHistoryForTracker(ticket) { |
|
|
|
|
this.selectedTracker = ticket; |
|
|
|
|
this.showHistory = true; |
|
|
|
|
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(); |
|
|
|
@ -497,8 +574,17 @@ const TimeTrack = {
@@ -497,8 +574,17 @@ const TimeTrack = {
|
|
|
|
|
this.tickets.forEach((ticket) => { |
|
|
|
|
ticket.history.forEach((historyEntry) => { |
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) { |
|
|
|
|
historyEntry.ticket = ticket.number; |
|
|
|
|
collection.push(historyEntry); |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
@ -506,15 +592,34 @@ const TimeTrack = {
@@ -506,15 +592,34 @@ const TimeTrack = {
|
|
|
|
|
this.archive.forEach((ticket) => { |
|
|
|
|
ticket.history.forEach((historyEntry) => { |
|
|
|
|
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) { |
|
|
|
|
historyEntry.ticket = ticket.number; |
|
|
|
|
historyEntry.archive = true; |
|
|
|
|
collection.push(historyEntry); |
|
|
|
|
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' : ''; |
|
|
|
@ -557,7 +662,7 @@ const TimeTrack = {
@@ -557,7 +662,7 @@ const TimeTrack = {
|
|
|
|
|
id: getRandomID() |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.snippets.push(newSnippet); |
|
|
|
|
this.snippets.pushToBeginning(newSnippet); |
|
|
|
|
this.loadSnippet(this.snippets[this.snippets.length-1]); |
|
|
|
|
this.updateStorage(); |
|
|
|
|
}, |
|
|
|
@ -634,10 +739,71 @@ const TimeTrack = {
@@ -634,10 +739,71 @@ const TimeTrack = {
|
|
|
|
|
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.stopTrackingTicket(); |
|
|
|
|
this.stopActiveTracker(); |
|
|
|
|
}, |
|
|
|
|
watch: { |
|
|
|
|
publicDB() { |
|
|
|
@ -675,6 +841,21 @@ const TimeTrack = {
@@ -675,6 +841,21 @@ const TimeTrack = {
|
|
|
|
|
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'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|