Browse Source

WIP timeboxes

modals-to-spa
stingl 4 years ago
parent
commit
82b1ea6295
  1. 20
      css/app.css
  2. 143
      index.html
  3. 60
      js/app.js

20
css/app.css

@ -182,3 +182,23 @@ nav, .card {
margin-top: 1em; margin-top: 1em;
margin-left: 1em; margin-left: 1em;
} }
.dropdown-menu {
background: transparent;
border: none;
box-shadow: none;
}
.dropdown-menu-button {
width: 10em;
margin-bottom: 1em;
}
.bg-timebox {
background-color: lightcyan;
color: black;
}
.bg-timebox input {
color: black;
}

143
index.html

@ -40,7 +40,7 @@
<div class="row"> <div class="row">
<template v-for="(ticket, ticketIndex) in tickets"> <template v-for="(ticket, ticketIndex) in tickets">
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6">
<div class="card bg-gradient-secondary"> <div :class="'card ' + (ticket.isTimeBox ? 'bg-timebox' : 'bg-gradient-secondary')">
<div class="card-body"> <div class="card-body">
<div class="card-text"> <div class="card-text">
<input type="text" <input type="text"
@ -48,6 +48,7 @@
class="form-control trackingNameField" class="form-control trackingNameField"
@keydown="updateStorage()"/> @keydown="updateStorage()"/>
<template v-if="!ticket.isTimeBox">
<div class="ticket-time-info"> <div class="ticket-time-info">
<div v-if="ticket.tracking === true"> <div v-if="ticket.tracking === true">
<div class="text-danger font-weight-bolder float-end"> <div class="text-danger font-weight-bolder float-end">
@ -97,6 +98,82 @@
</button> </button>
</div> </div>
</div> </div>
</template>
<template v-else>
<div class="row">
<div class="ticket-time-info">
<div v-if="ticket.tracking === true">
<div class="text-danger font-weight-bolder float-end">
<div class="spinner-grow spinner-grow-sm" role="status">
<span class="sr-only">Tracking...</span>
</div>
Tracking
</div>
</div>
</div>
<div v-if="ticket.tracking === true" class="ticket-time-info">
<span class="float-end">{{ getTrackingStartTime(ticket) }}</span>
<span v-if="ticket.tracking === true">Gestartet: </span>
<br/>
<span class="float-end">{{ currentTrackingRunningFor(ticket) }}</span>
<span v-if="ticket.tracking === true">Läuft seit: </span>
</div>
<template v-if="!ticket.timeBoxMinutes">
<div class="col-12">
Minuten:
</div>
<div class="col-4">
<button type="button" class="btn btn-secondary ticket-action-button"
@click="startTimeBox(ticket, 15)">
<i class="far fa-play-circle"></i> 15
</button>
</div>
<div class="col-4">
<button type="button" class="btn btn-secondary ticket-action-button"
@click="startTimeBox(ticket, 30)">
<i class="far fa-play-circle"></i> 30
</button>
</div>
<div class="col-4">
<button type="button" class="btn btn-secondary ticket-action-button"
@click="startTimeBox(ticket, 60)">
<i class="far fa-play-circle"></i> 60
</button>
</div>
</template>
<div class="col-12" v-if="ticket.timeBoxMinutes && timeBoxTimeLeft(ticket) > 0">
<span class="float-end">{{ timeBoxTimeLeft(ticket) }} Minuten</span>
<span>Zeit übrig: </span>
</div>
<div class="col-12 text-center" v-if="timeBoxTimeLeft(ticket) < 1">
<h5 class="text-danger text-bold">Abgeschlossen</h5>
</div>
<div class="col-md-12" v-if="ticket.tracking">
<button type="button" class="btn btn-danger ticket-action-button"
@click="stopTracking(ticket)">
<i class="far fa-stop-circle"></i>
</button>
</div>
<div class="col-md-12" v-if="!ticket.tracking && ticket.timeBoxMinutes && timeBoxTimeLeft(ticket) > 0">
<button type="button" class="btn btn-info ticket-action-button"
@click="startTracking(ticket)">
<i class="far fa-play-circle"></i>
</button>
</div>
<div class="col-md-12">
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal"
@click="archiveTicket(ticketIndex)" title="Archivieren">
<i class="fas fa-archive"></i>
</button>
</div>
</div>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -252,7 +329,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-clock"></i> Tracker</h5> <h5 class="modal-title"><i class="fas fa-clock"></i> Tracker</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
@ -345,7 +422,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-archive"></i> Archivierte Tracker</h5> <h5 class="modal-title"><i class="fas fa-archive"></i> Archivierte Tracker</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
@ -413,7 +490,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-sliders-h"></i> Einstellungen</h5> <h5 class="modal-title"><i class="fas fa-sliders-h"></i> Einstellungen</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body row"> <div class="modal-body row">
<div class="col-md-6"> <div class="col-md-6">
@ -500,8 +577,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary" v-on:click="updateStorage()" data-dismiss="modal">Speichern</button> <button class="btn btn-primary" v-on:click="updateStorage()" data-bs-dismiss="modal">Speichern</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Schließen</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div> </div>
</div> </div>
</div> </div>
@ -514,7 +591,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-history"></i> History von {{ selectedTicket.number }}</h5> <h5 class="modal-title"><i class="fas fa-history"></i> History von {{ selectedTicket.number }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ul class="list-group ticket-history"> <ul class="list-group ticket-history">
@ -578,7 +655,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-user-edit"></i> Buchung für {{selectedTicket.number}}</h5> <h5 class="modal-title"><i class="fas fa-user-edit"></i> Buchung für {{selectedTicket.number}}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
@ -587,10 +664,10 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<a href="javascript:" class="btn btn-success btn-full-width" data-dismiss="modal" @click="makeCustomBooking()">+ Hinzubuchen</a> <a href="javascript:" class="btn btn-success btn-full-width" data-bs-dismiss="modal" @click="makeCustomBooking()">+ Hinzubuchen</a>
</div> </div>
<div class="col-6"> <div class="col-6">
<a href="javascript:" class="btn btn-danger btn-full-width" data-dismiss="modal" @click="makeCustomBooking(true)">- Abbuchen</a> <a href="javascript:" class="btn btn-danger btn-full-width" data-bs-dismiss="modal" @click="makeCustomBooking(true)">- Abbuchen</a>
</div> </div>
</div> </div>
</div> </div>
@ -605,7 +682,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-random"></i> Portal Switcher</h5> <h5 class="modal-title"><i class="fas fa-random"></i> Portal Switcher</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
@ -647,7 +724,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="fas fa-code"></i> Snippet Space</h5> <h5 class="modal-title"><i class="fas fa-code"></i> Snippet Space</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
@ -677,21 +754,21 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<a type="button" <div class="dropup">
class="btn btn-success text-light bottom-menu-item" <button class="btn btn-success text-light bottom-menu-item" type="button" id="create-dropdown-menu" data-bs-toggle="dropdown" aria-expanded="false" title="Neu">
@click="addTrackedTicket()"
data-bs-toggle="tooltip"
data-bs-placement="left"
title="Neuer Tracker">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</a> </button>
<ul class="dropdown-menu" aria-labelledby="create-dropdown-menu">
<li><button class="btn btn-light dropdown-menu-button" type="button" @click="addTrackedTicket()">Tracker</button></li>
<li><button class="btn btn-light dropdown-menu-button" type="button" @click="addTimeBox()">Timebox</button></li>
</ul>
</div>
</div> </div>
<div class="col" v-if="tickets.length > 0"> <div class="col" v-if="tickets.length > 0">
<a type="button" <a type="button"
class="btn btn-primary text-light bottom-menu-item" class="btn btn-primary text-light bottom-menu-item"
data-toggle="modal" data-bs-toggle="modal"
data-target="#showTicketsModal" data-bs-target="#showTicketsModal"
data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
title="Alle Tracker"> title="Alle Tracker">
<i class="fas fa-clock"></i> <i class="fas fa-clock"></i>
@ -700,9 +777,8 @@
<div class="col" v-if="archive.length > 0"> <div class="col" v-if="archive.length > 0">
<a type="button" <a type="button"
class="btn btn-secondary text-dark bottom-menu-item" class="btn btn-secondary text-dark bottom-menu-item"
data-toggle="modal" data-bs-toggle="modal"
data-target="#showArchivedTicketsModal" data-bs-target="#showArchivedTicketsModal"
data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
title="Archivierte Tracker"> title="Archivierte Tracker">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
@ -711,9 +787,8 @@
<div class="col" v-if="experimental.portalSwitcher"> <div class="col" v-if="experimental.portalSwitcher">
<a type="button" <a type="button"
class="btn btn-warning text-light bottom-menu-item" class="btn btn-warning text-light bottom-menu-item"
data-toggle="modal" data-bs-toggle="modal"
data-target="#switcherModal" data-bs-target="#switcherModal"
data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
title="Portal Switcher"> title="Portal Switcher">
<i class="fas fa-random"></i> <i class="fas fa-random"></i>
@ -722,9 +797,8 @@
<div class="col" v-if="experimental.snippetSpace"> <div class="col" v-if="experimental.snippetSpace">
<a type="button" <a type="button"
class="btn btn-info text-light bottom-menu-item" class="btn btn-info text-light bottom-menu-item"
data-toggle="modal" data-bs-toggle="modal"
data-target="#snippetSpaceModal" data-bs-target="#snippetSpaceModal"
data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
title="Snippet Space"> title="Snippet Space">
<i class="fas fa-code"></i> <i class="fas fa-code"></i>
@ -733,9 +807,8 @@
<div class="col"> <div class="col">
<a type="button" <a type="button"
class="btn btn-dark text-light bottom-menu-item" class="btn btn-dark text-light bottom-menu-item"
data-toggle="modal" data-bs-toggle="modal"
data-target="#settingsModal" data-bs-target="#settingsModal"
data-bs-toggle="tooltip"
data-bs-placement="left" data-bs-placement="left"
title="Einstellungen"> title="Einstellungen">
<i class="fas fa-sliders-h"></i> <i class="fas fa-sliders-h"></i>
@ -754,7 +827,7 @@
<script src="https://unpkg.com/vue@next"></script> <script src="https://unpkg.com/vue@next"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
</body> </body>

60
js/app.js

@ -80,6 +80,11 @@ const TimeTrack = {
vue.updateStorage(); vue.updateStorage();
}, 5000); }, 5000);
} }
Notification.requestPermission();
setInterval(() => {
vue.checkTimeBoxes();
}, 10000);
}, },
methods: { methods: {
loadTooltips() { loadTooltips() {
@ -140,22 +145,40 @@ const TimeTrack = {
this.updateStorage(); this.updateStorage();
}, },
addTrackedTicket() { addTrackedTicket() {
let newTicket = { this.tickets.push({
tracking: false, tracking: false,
number: '#', number: '#',
trackingStarted: null, trackingStarted: null,
trackingStopped: null, trackingStopped: null,
history: [] history: []
}; });
this.tickets.push(newTicket);
this.updateStorage(); this.updateStorage();
}, },
startTracking(ticket, individual = false) { addTimeBox() {
this.tickets.push({
tracking: false,
number: 'Timebox ',
trackingStarted: null,
trackingStopped: null,
isTimeBox: true,
history: []
});
this.updateStorage();
},
startTimeBox(ticket, minutes) {
this.startTracking(ticket, false, minutes);
},
startTracking(ticket, individual = false, timeBoxMinutes = null) {
if (!individual) { if (!individual) {
this.stopTrackingTicket(); this.stopTrackingTicket();
} }
if (timeBoxMinutes) {
ticket.timeBoxMinutes = timeBoxMinutes;
}
ticket.status = 'wip'; ticket.status = 'wip';
ticket.trackingStarted = moment(); ticket.trackingStarted = moment();
@ -233,7 +256,7 @@ const TimeTrack = {
currentTrackingRunningFor(ticket) { currentTrackingRunningFor(ticket) {
return this.timeWithPostFix(Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes'))); return this.timeWithPostFix(Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes')));
}, },
getTotalTime(ticket) { getTotalTime(ticket, raw = false) {
let totalTime = 0; let totalTime = 0;
if (ticket.history.length > 0) { if (ticket.history.length > 0) {
@ -246,7 +269,11 @@ const TimeTrack = {
totalTime += Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes')); totalTime += Math.round(moment.duration(moment().diff(ticket.trackingStarted)).as('minutes'));
} }
if (raw) {
return totalTime;
} else {
return this.timeWithPostFix(totalTime); return this.timeWithPostFix(totalTime);
}
}, },
getTotalTimeToday(ticket) { getTotalTimeToday(ticket) {
let totalTime = 0; let totalTime = 0;
@ -540,6 +567,29 @@ const TimeTrack = {
message: 'Code kopiert!', message: 'Code kopiert!',
color: 'green' color: 'green'
}); });
},
checkTimeBoxes() {
let vue = this;
this.tickets.forEach((ticket) => {
if (ticket.isTimeBox && vue.timeBoxTimeLeft(ticket) <= 0) {
vue.stopTracking(ticket);
if (Notification.permission) {
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));
} }
}, },
watch: { watch: {