Compare commits

...
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

28 Commits

  1. 4
      .idea/timetrack.iml
  2. BIN
      assets/img/gitlab.png
  3. BIN
      assets/img/jira.png
  4. BIN
      assets/img/redmine.png
  5. 16
      css/app.css
  6. 144
      index.html
  7. 231
      js/app.js

4
.idea/timetrack.iml

@ -4,5 +4,9 @@
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="izitoast" level="application" />
<orderEntry type="library" name="quill" level="application" />
<orderEntry type="library" name="quill.snow" level="application" />
<orderEntry type="library" name="quill.bubble" level="application" />
</component> </component>
</module> </module>

BIN
assets/img/gitlab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
assets/img/jira.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/img/redmine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

16
css/app.css

@ -222,3 +222,19 @@ nav, .card {
bottom: 0; bottom: 0;
left: 0; left: 0;
} }
.finished-task {
text-decoration: line-through;
}
#trackerTasksModal a li {
color: black !important;
}
#trackerTasksModal input[type=range] {
width: 100%;
}
.ticket-icon {
max-width: 24px;
}

144
index.html

@ -78,30 +78,38 @@
<span class="">Heute: </span> <span class="">Heute: </span>
<div class="row"> <div class="row">
<div class="col-md-12" v-if="!ticket.tracking"> <div class="col-md-6" v-if="!ticket.tracking">
<button type="button" class="btn btn-info ticket-action-button" <button type="button" class="btn btn-primary ticket-action-button"
@click="startTracking(ticket)"> @click="startTracking(ticket)">
<i class="far fa-play-circle"></i> <i class="far fa-play-circle"></i> <small>Starten</small>
</button> </button>
</div> </div>
<div class="col-md-12" v-else> <div class="col-md-6" v-else>
<button type="button" class="btn btn-danger ticket-action-button" <button type="button" class="btn btn-danger ticket-action-button"
@click="stopTracking(ticket)"> @click="stopTracking(ticket)">
<i class="far fa-stop-circle"></i> <i class="far fa-stop-circle"></i> <small>Stoppen</small>
</button> </button>
</div> </div>
<div class="col-6"> <div class="col-6">
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-warning ticket-action-button" data-bs-dismiss="modal"
@click="archiveTracker(ticketIndex)" title="Archivieren"> @click="archiveTracker(ticketIndex)" title="Archivieren">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i> <small>Archivieren</small>
</button>
</div>
<div class="col-md-6">
<button type="button" class="btn btn-info ticket-action-button"
@click="openTasksForTracker(ticket)" title="Tasks">
<i class="fas fa-clipboard-check"></i> <small>Tasks {{ showOpenTasksForTracker(ticket.tasks) }}</small>
</button> </button>
</div> </div>
<div class="col-6"> <div class="col-6">
<button class="btn btn-danger ticket-action-button" <button class="btn btn-danger ticket-action-button"
@click="deleteTracker(ticketIndex)" title="Löschen"> @click="deleteTracker(ticketIndex)" title="Löschen">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i> <small>Löschen</small>
</button> </button>
</div> </div>
</div> </div>
@ -169,20 +177,20 @@
<div class="col-md-12" v-if="ticket.tracking"> <div class="col-md-12" v-if="ticket.tracking">
<button type="button" class="btn btn-danger ticket-action-button" <button type="button" class="btn btn-danger ticket-action-button"
@click="stopTracking(ticket)"> @click="stopTracking(ticket)">
<i class="far fa-stop-circle"></i> <i class="far fa-stop-circle"></i> <small>Stoppen</small>
</button> </button>
</div> </div>
<div class="col-md-12" v-if="!ticket.tracking && ticket.timeBoxMinutes && timeBoxTimeLeft(ticket) > 0"> <div class="col-md-12" v-if="!ticket.tracking && ticket.timeBoxMinutes && timeBoxTimeLeft(ticket) > 0">
<button type="button" class="btn btn-info ticket-action-button" <button type="button" class="btn btn-primary ticket-action-button"
@click="startTracking(ticket)"> @click="startTracking(ticket)">
<i class="far fa-play-circle"></i> <i class="far fa-play-circle"></i> <small>Starten</small>
</button> </button>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<button class="btn btn-danger ticket-action-button" <button class="btn btn-danger ticket-action-button"
@click="deleteTracker(ticketIndex)" title="Löschen"> @click="deleteTracker(ticketIndex)" title="Löschen">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i> <small>Löschen</small>
</button> </button>
</div> </div>
</div> </div>
@ -255,7 +263,7 @@
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-warning ticket-action-button" data-bs-dismiss="modal"
@click="archiveTracker(ticketIndex)" title="Archivieren"> @click="archiveTracker(ticketIndex)" title="Archivieren">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
</button> </button>
@ -320,7 +328,7 @@
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-warning ticket-action-button" data-bs-dismiss="modal"
@click="archiveTracker(ticketIndex)" title="Archivieren"> @click="archiveTracker(ticketIndex)" title="Archivieren">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
</button> </button>
@ -348,7 +356,10 @@
<div class="row"> <div class="row">
<template v-for="(ticket, ticketIndex) in tickets"> <template v-for="(ticket, ticketIndex) in tickets">
<div class="col-md-6"> <div class="col-md-6">
<h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6> <!-- <h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6>-->
<div class="form-group">
<input type="text" class="form-control" v-model="ticket.number" @keydown="updateStorage()" placeholder="Name">
</div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" v-model="ticket.description" @keydown="updateStorage()" placeholder="Beschreibung"> <input type="text" class="form-control" v-model="ticket.description" @keydown="updateStorage()" placeholder="Beschreibung">
@ -365,17 +376,29 @@
</button> </button>
</div> </div>
<div class="col"> <div class="col">
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-warning ticket-action-button" data-bs-dismiss="modal"
@click="archiveTracker(ticketIndex)" title="Archivieren"> @click="archiveTracker(ticketIndex)" title="Archivieren">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
</button> </button>
</div> </div>
<div class="col" v-if="ticket.tasks && ticket.tasks.length > 0">
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal"
@click="openTasksForTracker(ticket)" title="Tasks">
<i class="fas fa-clipboard-check"></i>
</button>
</div>
<div class="col" v-if="ticket.history.length > 0"> <div class="col" v-if="ticket.history.length > 0">
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-info ticket-action-button" data-bs-dismiss="modal"
@click="showHistoryForTracker(ticket)" title="History"> @click="showHistoryForTracker(ticket)" title="History">
<i class="fas fa-history"></i> <i class="fas fa-history"></i>
</button> </button>
</div> </div>
<div class="col" v-if="ticket.history.length > 0 && this.ticketSystemUrl">
<button class="btn btn-white ticket-action-button"
@click="sendLastBookingToTicketSystem(ticket)" title="Letzte Buchung in Ticketsystem eintragen">
<img :src="ticketSystemIcon" class="ticket-icon"/>
</button>
</div>
<div class="col"> <div class="col">
<button class="btn btn-danger ticket-action-button" <button class="btn btn-danger ticket-action-button"
@click="deleteTracker(ticketIndex)" title="Löschen"> @click="deleteTracker(ticketIndex)" title="Löschen">
@ -398,6 +421,71 @@
</div> </div>
</div> </div>
<!-- Tasks Modal -->
<div class="modal modal-fullscreen fade"
v-if="selectedTracker"
id="trackerTasksModal"
tabindex="-1"
role="dialog"
aria-labelledby="showTrackerTasksModal"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-clock"></i> Tasks für {{ selectedTracker.number }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-group">
<input type="text" id="newTaskInput" class="form-control"
v-model="newTaskInput" placeholder="Neuer Task" v-on:keyup.enter="addTask()"/>
</div>
<ul class="list-group" v-if="selectedTracker.tasks && selectedTracker.tasks.length > 0">
<template v-for="(task, taskIndex) in selectedTracker.tasks">
<li class="list-group-item" v-if="!task.done">
<span class="float-end">
<a href="javascript:">
<i class="fas fa-trash" @click="deleteTask(taskIndex)"></i>
</a>
</span>
<a href="javascript:" @click="toggleTask(task)">
<i class="far fa-square"></i>
</a> {{ task.name }}
<div class="form-group">
<div class="float-end">
{{ task.percentDone }}% erledigt
</div>
<input type="range"
class="range range-success range-tasks"
min="0"
max="100"
step="5"
v-model="task.percentDone"
@change="checkForCompletionOfTask(task)">
</div>
</li>
</template>
</ul>
<br/>
<ul class="list-group" v-if="selectedTracker.tasks && selectedTracker.tasks.length > 0">
<template v-for="(task, taskIndex) in selectedTracker.tasks">
<li class="list-group-item" v-if="task.done">
<span class="float-end">
<a href="javascript:">
<i class="fas fa-trash" @click="deleteTask(taskIndex)"></i>
</a>
</span>
<a href="javascript:" @click="toggleTask(task)">
<i class="far fa-check-square"></i>
</a> <span class="finished-task">{{ task.name }}</span>
</li>
</template>
</ul>
</div>
</div>
</div>
</div>
<!-- Archive Modal --> <!-- Archive Modal -->
<div class="modal modal-fullscreen fade" id="showArchivedTicketsModal" tabindex="-1" role="dialog" <div class="modal modal-fullscreen fade" id="showArchivedTicketsModal" tabindex="-1" role="dialog"
aria-labelledby="showArchivedTicketsModalLabel" aria-labelledby="showArchivedTicketsModalLabel"
@ -417,10 +505,14 @@
</div> </div>
<template v-for="(ticket, ticketIndex) in archive"> <template v-for="(ticket, ticketIndex) in archive">
<div class="col-md-6" v-if="searchQuery === '' || ticket.number.search(searchQuery) >= 0 || (ticket.description && ticket.description.search(searchQuery)) >= 0"> <div class="col-md-6" v-if="searchQuery === '' || ticket.number.search(searchQuery) >= 0 || (ticket.description && ticket.description.search(searchQuery)) >= 0">
<h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6> <div class="form-group">
<div v-if="ticket.description"> <input type="text" class="form-control" v-model="ticket.number" @keydown="updateStorage()" placeholder="Name">
<p class="fst-italic">{{ ticket.description }}</p>
</div> </div>
<div class="form-group">
<input type="text" class="form-control" v-model="ticket.description" @keydown="updateStorage()" placeholder="Beschreibung">
</div>
<div class="ticket-time-info"> <div class="ticket-time-info">
<span class="float-end">{{ getTotalTime(ticket) }}</span> <span class="float-end">{{ getTotalTime(ticket) }}</span>
<span class="current-ticket-info">Gesamt: </span> <span class="current-ticket-info">Gesamt: </span>
@ -437,12 +529,24 @@
<i class="fas fa-power-off"></i> <i class="fas fa-power-off"></i>
</button> </button>
</div> </div>
<div class="col" v-if="ticket.tasks && ticket.tasks.length > 0">
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal"
@click="openTasksForTracker(ticket)" title="Tasks">
<i class="fas fa-clipboard-check"></i>
</button>
</div>
<div class="col" v-if="ticket.history.length > 0"> <div class="col" v-if="ticket.history.length > 0">
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal" <button class="btn btn-info ticket-action-button" data-bs-dismiss="modal"
@click="showHistoryForTracker(ticket)" title="History"> @click="showHistoryForTracker(ticket)" title="History">
<i class="fas fa-history"></i> <i class="fas fa-history"></i>
</button> </button>
</div> </div>
<div class="col" v-if="ticket.history.length > 0 && this.ticketSystemUrl">
<button class="btn btn-white ticket-action-button"
@click="sendLastBookingToTicketSystem(ticket)" title="Letzte Buchung in Ticketsystem eintragen">
<img :src="ticketSystemIcon" class="ticket-icon"/>
</button>
</div>
<div class="col"> <div class="col">
<button class="btn btn-danger ticket-action-button" <button class="btn btn-danger ticket-action-button"
@click="deleteTracker(ticketIndex, true)" title="Löschen"> @click="deleteTracker(ticketIndex, true)" title="Löschen">
@ -790,7 +894,7 @@
</div> </div>
<div class="" v-if="archive.length > 0"> <div class="" v-if="archive.length > 0">
<a type="button" <a type="button"
class="btn btn-secondary text-dark bottom-menu-item" class="btn btn-warning bottom-menu-item"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#showArchivedTicketsModal" data-bs-target="#showArchivedTicketsModal"
data-bs-placement="top" data-bs-placement="top"

231
js/app.js

@ -1,3 +1,7 @@
Array.prototype.pushToBeginning = function (toPush) {
return this.unshift.apply(this, [toPush]);
}
const TimeTrack = { const TimeTrack = {
data() { data() {
return { return {
@ -20,6 +24,7 @@ const TimeTrack = {
}, },
tickets: [], tickets: [],
archive: [], archive: [],
trashed: {},
worktimeTracker: { worktimeTracker: {
tracking: false, tracking: false,
number: 'Worktime', number: 'Worktime',
@ -52,7 +57,8 @@ const TimeTrack = {
snippets: [], snippets: [],
codeMirrors: [], codeMirrors: [],
customBookingValue: '', customBookingValue: '',
customDateForPastDays: '' customDateForPastDays: '',
newTaskInput: ''
} }
}, },
mounted() { mounted() {
@ -60,7 +66,9 @@ const TimeTrack = {
this.loadStorage(); this.loadStorage();
this.fetchThemes(); this.fetchThemes();
moment.locale('de'); moment.locale('de');
this.customDateForPastDays = moment().format();
setInterval(() => { setInterval(() => {
vue.$forceUpdate(); vue.$forceUpdate();
@ -152,7 +160,7 @@ const TimeTrack = {
this.updateStorage(); this.updateStorage();
}, },
createTracker() { createTracker() {
this.tickets.push({ this.tickets.pushToBeginning({
tracking: false, tracking: false,
number: '#', number: '#',
trackingStarted: null, trackingStarted: null,
@ -163,7 +171,7 @@ const TimeTrack = {
this.updateStorage(); this.updateStorage();
}, },
createTimeBox() { createTimeBox() {
this.tickets.push({ this.tickets.pushToBeginning({
tracking: false, tracking: false,
number: 'Timebox ', number: 'Timebox ',
trackingStarted: null, trackingStarted: null,
@ -203,6 +211,7 @@ const TimeTrack = {
}, },
stopTracking(ticket) { stopTracking(ticket) {
// console.log(ticket); // console.log(ticket);
let component = this;
ticket.trackingStopped = moment(); ticket.trackingStopped = moment();
ticket.tracking = false; ticket.tracking = false;
@ -217,7 +226,6 @@ const TimeTrack = {
manually: false, manually: false,
minutes: Math.round(minutesSpent) minutes: Math.round(minutesSpent)
}; };
// console.log(historyEntry);
if (this.experimental.trackWorktime) { if (this.experimental.trackWorktime) {
if (ticket.paused) { if (ticket.paused) {
@ -225,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; ticket.trackingStarted = null;
@ -241,6 +276,28 @@ const TimeTrack = {
this.stopTracking(ticket); 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) { resumeTracking(ticket) {
ticket.trackingStarted = moment(); ticket.trackingStarted = moment();
ticket.tracking = true; ticket.tracking = true;
@ -335,12 +392,16 @@ const TimeTrack = {
return number.indexOf('#') >= 0; return number.indexOf('#') >= 0;
}, },
deleteTracker(index, archive = false) { deleteTracker(index, archive = false) {
let component = this;
let message = ''; let message = '';
if (archive) { if (archive) {
Object.assign(this.trashed, this.archive[index]);
let name = this.archive[index].number; let name = this.archive[index].number;
message = 'Tracker "' + name + '" wurde gelöscht'; message = 'Tracker "' + name + '" wurde gelöscht';
this.archive.splice(index, 1); this.archive.splice(index, 1);
} else { } else {
Object.assign(this.trashed, this.tickets[index]);
let name = this.tickets[index].number; let name = this.tickets[index].number;
message = 'Tracker "' + name + '" wurde gelöscht'; message = 'Tracker "' + name + '" wurde gelöscht';
this.tickets.splice(index, 1); this.tickets.splice(index, 1);
@ -348,22 +409,40 @@ const TimeTrack = {
iziToast.show({ iziToast.show({
message: message, message: message,
color: 'blue' 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(); this.updateStorage();
}, },
restoreTrashed() {
let restoredTracker = {};
Object.assign(restoredTracker, this.trashed);
this.trashed = {};
this.tickets.pushToBeginning(restoredTracker);
this.updateStorage();
},
archiveTracker(index) { archiveTracker(index) {
if (this.tickets[index].tracking) { if (this.tickets[index].tracking) {
this.stopActiveTracker(); this.stopActiveTracker();
} }
this.archive.push(this.tickets[index]); this.archive.pushToBeginning(this.tickets[index]);
this.tickets.splice(index, 1); this.tickets.splice(index, 1);
this.updateStorage(); this.updateStorage();
}, },
reactivateTicket(index) { reactivateTicket(index) {
this.tickets.push(this.archive[index]); this.tickets.pushToBeginning(this.archive[index]);
this.archive.splice(index, 1); this.archive.splice(index, 1);
this.updateStorage(); this.updateStorage();
}, },
@ -376,7 +455,7 @@ const TimeTrack = {
this.updateStorage(); this.updateStorage();
}, },
bookTimeManually(ticket, minutes) { bookTimeManually(ticket, minutes) {
ticket.history.push({ ticket.history.pushToBeginning({
trackingStarted: moment(), trackingStarted: moment(),
trackingStopped: moment(), trackingStopped: moment(),
manually: true, manually: true,
@ -414,9 +493,9 @@ const TimeTrack = {
ticketCollection.forEach((ticket) => { ticketCollection.forEach((ticket) => {
if (ticket.archived || (ticket.active && ticket.active === false)) { if (ticket.archived || (ticket.active && ticket.active === false)) {
archive.push(ticket); archive.pushToBeginning(ticket);
} else { } else {
tickets.push(ticket) tickets.pushToBeginning(ticket)
} }
}); });
@ -441,14 +520,22 @@ const TimeTrack = {
console.log(error); console.log(error);
}); });
}, },
showHistoryForTracker(ticket) { showHistoryForTracker(tracker) {
this.selectedTracker = ticket; this.selectedTracker = tracker;
this.$forceUpdate(); this.$forceUpdate();
setTimeout(() => { setTimeout(() => {
let historyModal = new bootstrap.Modal(document.getElementById('historyModal')); let historyModal = new bootstrap.Modal(document.getElementById('historyModal'));
historyModal.toggle(); historyModal.toggle();
}, 50); }, 50);
}, },
openTasksForTracker(tracker) {
this.selectedTracker = tracker;
this.$forceUpdate();
setTimeout(() => {
let tasksModal = new bootstrap.Modal(document.getElementById('trackerTasksModal'));
tasksModal.toggle();
}, 50);
},
showCustomBookingForTracker(ticket) { showCustomBookingForTracker(ticket) {
this.selectedTracker = ticket; this.selectedTracker = ticket;
this.$forceUpdate(); this.$forceUpdate();
@ -487,8 +574,17 @@ const TimeTrack = {
this.tickets.forEach((ticket) => { this.tickets.forEach((ticket) => {
ticket.history.forEach((historyEntry) => { ticket.history.forEach((historyEntry) => {
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) { if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) {
historyEntry.ticket = ticket.number; let newEntry = {};
collection.push(historyEntry); 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);
}
} }
}); });
}); });
@ -496,15 +592,34 @@ const TimeTrack = {
this.archive.forEach((ticket) => { this.archive.forEach((ticket) => {
ticket.history.forEach((historyEntry) => { ticket.history.forEach((historyEntry) => {
if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) { if (moment(historyEntry.trackingStarted).format("MMM Do YY") === day) {
historyEntry.ticket = ticket.number; let newEntry = {};
historyEntry.archive = true; Object.assign(newEntry, historyEntry);
collection.push(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; return collection;
}, },
getCollectionItemWithValue(collection, property, value) {
let found = false;
collection.forEach((item) => {
if (item[property] === value) {
found = item;
}
})
return found;
},
sendPortalChangeRequest() { sendPortalChangeRequest() {
let vue = this; let vue = this;
let publicDBParam = this.publicDB ? '&publicDB=1' : ''; let publicDBParam = this.publicDB ? '&publicDB=1' : '';
@ -547,7 +662,7 @@ const TimeTrack = {
id: getRandomID() id: getRandomID()
}; };
this.snippets.push(newSnippet); this.snippets.pushToBeginning(newSnippet);
this.loadSnippet(this.snippets[this.snippets.length-1]); this.loadSnippet(this.snippets[this.snippets.length-1]);
this.updateStorage(); this.updateStorage();
}, },
@ -624,10 +739,71 @@ const TimeTrack = {
tellJoke(category, language = 'en') { tellJoke(category, language = 'en') {
let jokeService = new JokeService(category ?? undefined, language); let jokeService = new JokeService(category ?? undefined, language);
jokeService.tell(); 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() { beforeDestroy() {
this.stopTrackingTicket(); this.stopActiveTracker();
}, },
watch: { watch: {
publicDB() { publicDB() {
@ -665,6 +841,21 @@ const TimeTrack = {
fun: this.fun, fun: this.fun,
theme: this.theme 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';
}
}
} }
} }
}; };