commit
138f82f4bb
10 changed files with 12751 additions and 0 deletions
@ -0,0 +1,130 @@ |
|||||||
|
body { |
||||||
|
background-color: #e7e7e7; |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.container { |
||||||
|
margin: 20px auto 20px auto; |
||||||
|
} |
||||||
|
|
||||||
|
.add-button { |
||||||
|
padding: 1px 5px 1px 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.ticket-action-button { |
||||||
|
margin-top: 10px; |
||||||
|
padding: 1px 5px 1px 5px; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.delete-ticket-button { |
||||||
|
margin-top: 10px; |
||||||
|
margin-bottom: 15px; |
||||||
|
padding: 1px 5px 1px 5px; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.side-button-right { |
||||||
|
position: absolute; |
||||||
|
right: 30px; |
||||||
|
|
||||||
|
height: 60px; |
||||||
|
width: 60px; |
||||||
|
|
||||||
|
font-size: 1.7em; |
||||||
|
} |
||||||
|
|
||||||
|
.add-tracker-button { |
||||||
|
position: absolute; |
||||||
|
left: 30px; |
||||||
|
bottom: 30px; |
||||||
|
|
||||||
|
height: 60px; |
||||||
|
width: 60px; |
||||||
|
|
||||||
|
font-size: 1.7em; |
||||||
|
} |
||||||
|
|
||||||
|
.first-button { |
||||||
|
bottom: 30px; |
||||||
|
} |
||||||
|
|
||||||
|
.second-button { |
||||||
|
bottom: 110px; |
||||||
|
padding-left: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.third-button { |
||||||
|
bottom: 190px; |
||||||
|
padding-left: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.fourth-button { |
||||||
|
bottom: 270px; |
||||||
|
padding-left: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.navbar-brand { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.logo-nav { |
||||||
|
max-height: 35px; |
||||||
|
margin: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
nav { |
||||||
|
border-radius: 45px; |
||||||
|
} |
||||||
|
|
||||||
|
nav, .card { |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.modal-content { |
||||||
|
max-width: 1000px; |
||||||
|
} |
||||||
|
|
||||||
|
.btn-full-width { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.timeTracking { |
||||||
|
height: 635px; |
||||||
|
overflow-y: scroll; |
||||||
|
margin-bottom: 0; |
||||||
|
padding-bottom: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.ticket-history { |
||||||
|
list-style: none; |
||||||
|
padding-left: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.trackingNameField { |
||||||
|
max-height: 1em; |
||||||
|
margin-bottom: 1px; |
||||||
|
} |
||||||
|
|
||||||
|
#showTrackedTicketsModal .showTrackedTicketsModalDialog, |
||||||
|
#showArchivedTicketsModal .showArchivedTicketsModalDialog{ |
||||||
|
max-width: 900px; |
||||||
|
} |
||||||
|
|
||||||
|
.btn { |
||||||
|
text-transform: initial !important; |
||||||
|
} |
||||||
|
|
||||||
|
.ticket-time-info { |
||||||
|
margin-top: 1em; |
||||||
|
clear: both; |
||||||
|
} |
||||||
|
|
||||||
|
.brand-title { |
||||||
|
margin-left: 2em; |
||||||
|
margin-top: 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
#settingsModal .form-group { |
||||||
|
margin-bottom: 0.5em; |
||||||
|
} |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 3.4 KiB |
@ -0,0 +1,372 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" |
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"> |
||||||
|
<title>vDash</title> |
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="img/favicon.ico"> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body oncontextmenu="return false;"> |
||||||
|
<div class="container" id="root"> |
||||||
|
<link rel="stylesheet" :href="'https://bootswatch.com/5/' + theme + '/bootstrap.min.css'"> |
||||||
|
<link rel="stylesheet" href="css/app.css"> |
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light"> |
||||||
|
<a class="navbar-brand"> |
||||||
|
<img :src="dashboardLogo" alt="logo" class="logo-nav float-end" v-if="dashboardLogo"/> |
||||||
|
<h5 class="brand-title">Timetrack</h5> |
||||||
|
</a> |
||||||
|
</nav> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-12"> |
||||||
|
<div class="row"> |
||||||
|
<div class=" col-lg-4 col-md-6" |
||||||
|
v-for="(ticket, ticketIndex) in trackedTickets" |
||||||
|
v-if="!ticket.archived"> |
||||||
|
<div class="card bg-gradient-secondary"> |
||||||
|
<div class="card-body"> |
||||||
|
<div class="card-text"> |
||||||
|
<input type="text" |
||||||
|
v-model="ticket.number" |
||||||
|
class="form-control trackingNameField" |
||||||
|
@keydown="updateStorage()"/> |
||||||
|
|
||||||
|
<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> |
||||||
|
|
||||||
|
<div class="ticket-time-info"> |
||||||
|
<span class="float-end">{{ getTotalTime(ticket) }}</span> |
||||||
|
<span class="current-ticket-info">Gesamt: </span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<span class="float-end">{{ getTotalTimeToday(ticket) }}</span> |
||||||
|
<span class="">Heute: </span> |
||||||
|
|
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-12" v-if="ticket.tracking == false"> |
||||||
|
<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" v-else> |
||||||
|
<button type="button" class="btn btn-danger ticket-action-button" |
||||||
|
@click="stopTracking(ticket)"> |
||||||
|
<i class="far fa-stop-circle"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<a type="button" class="btn btn-success add-tracker-button text-light" @click="addTrackedTicket()"> |
||||||
|
<i class="fas fa-plus"></i> |
||||||
|
</a> |
||||||
|
|
||||||
|
<a type="button" :class="'btn btn-primary side-button-right text-light ' + (archivedTrackers > 0 ? 'third-button' : 'second-button')" data-toggle="modal" |
||||||
|
data-target="#showTrackedTicketsModal" v-if="activeTrackers > 0"> |
||||||
|
<i class="fas fa-user-clock"></i> |
||||||
|
</a> |
||||||
|
|
||||||
|
<a type="button" :class="'btn btn-secondary side-button-right text-dark second-button'" data-toggle="modal" |
||||||
|
data-target="#showArchivedTicketsModal" v-if="archivedTrackers > 0"> |
||||||
|
<i class="fas fa-archive"></i> |
||||||
|
</a> |
||||||
|
|
||||||
|
<a type="button" class="btn btn-dark text-light side-button-right first-button" data-toggle="modal" |
||||||
|
data-target="#settingsModal"> |
||||||
|
<i class="fas fa-sliders-h"></i> |
||||||
|
</a> |
||||||
|
|
||||||
|
<div class="modal modal-fullscreen fade" id="showTrackedTicketsModal" tabindex="-1" role="dialog" |
||||||
|
aria-labelledby="showTrackedTicketsModalLabel" |
||||||
|
aria-hidden="true"> |
||||||
|
<div class="modal-dialog showTrackedTicketsModalDialog" role="document"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<h5 class="modal-title"><i class="fas fa-user-clock"></i> Tracker</h5> |
||||||
|
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-6" v-for="(ticket, ticketIndex) in trackedTickets" v-if="!ticket.archived"> |
||||||
|
<h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6> |
||||||
|
|
||||||
|
<div class="form-group"> |
||||||
|
<input type="text" class="form-control" v-model="ticket.description" @keydown="updateStorage()" placeholder="Beschreibung"> |
||||||
|
</div> |
||||||
|
|
||||||
|
<span>Gesamtzeit: {{ getTotalTime(ticket) }}</span> |
||||||
|
<br> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-3"> |
||||||
|
<button class="btn btn-success ticket-action-button" |
||||||
|
@click="bookTimeManually(ticket, 30)"> |
||||||
|
+ ½h |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col-md-3"> |
||||||
|
<button class="btn btn-success ticket-action-button" |
||||||
|
@click="bookTimeManually(ticket, 60)"> |
||||||
|
+ 1h |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col-md-3"> |
||||||
|
<button class="btn btn-warning ticket-action-button" |
||||||
|
@click="bookTimeManually(ticket, -30)"> |
||||||
|
- ½h |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col-md-3"> |
||||||
|
<button class="btn btn-warning ticket-action-button" |
||||||
|
@click="bookTimeManually(ticket, -60)"> |
||||||
|
- 1h |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col"> |
||||||
|
<button class="btn btn-secondary ticket-action-button" data-bs-dismiss="modal" |
||||||
|
@click="archiveTicket(ticket)" title="Archivieren"> |
||||||
|
<i class="fas fa-archive"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col" v-if="ticket.history.length > 0"> |
||||||
|
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal" |
||||||
|
@click="showHistoryForTicket(ticket)" title="History"> |
||||||
|
<i class="fas fa-history"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col"> |
||||||
|
<button class="btn btn-danger ticket-action-button" |
||||||
|
@click="deleteTicket(ticket)" title="Löschen"> |
||||||
|
<i class="fas fa-trash"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col" v-if="ticketSystemUrl"> |
||||||
|
<a v-if="isTicketNumber(ticket.number)" :href="ticketSystemUrl + ticket.number" |
||||||
|
target="_blank" class="btn btn-dark ticket-action-button" title="Ticket"> |
||||||
|
<i class="fas fa-external-link-square-alt"></i> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="modal modal-fullscreen fade" id="showArchivedTicketsModal" tabindex="-1" role="dialog" |
||||||
|
aria-labelledby="showArchivedTicketsModalLabel" |
||||||
|
aria-hidden="true"> |
||||||
|
<div class="modal-dialog showArchivedTicketsModalDialog" role="document"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<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> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-6" v-for="(ticket, ticketIndex) in trackedTickets" v-if="ticket.archived"> |
||||||
|
<h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6> |
||||||
|
<div v-if="ticket.description"> |
||||||
|
<p class="blockquote">{{ ticket.description }}</p> |
||||||
|
</div> |
||||||
|
<span>Gesamtzeit: {{ getTotalTime(ticket) }}</span> |
||||||
|
<br> |
||||||
|
<div class="col-md-12 row"> |
||||||
|
<div class="col"> |
||||||
|
<button class="btn btn-success ticket-action-button" data-bs-dismiss="modal" |
||||||
|
@click="reactivateTicket(ticket)" title="Reaktivieren"> |
||||||
|
<i class="fas fa-power-off"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col" v-if="ticket.history.length > 0"> |
||||||
|
<button class="btn btn-info ticket-action-button" data-bs-dismiss="modal" |
||||||
|
@click="showHistoryForTicket(ticket)" title="History"> |
||||||
|
<i class="fas fa-history"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col"> |
||||||
|
<button class="btn btn-danger ticket-action-button" |
||||||
|
@click="deleteTicket(ticket)" title="Löschen"> |
||||||
|
<i class="fas fa-trash"></i> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col" v-if="ticketSystemUrl"> |
||||||
|
<a v-if="isTicketNumber(ticket.number)" :href="ticketSystemUrl + ticket.number" |
||||||
|
target="_blank" class="btn btn-dark ticket-action-button" title="Ticket"> |
||||||
|
<i class="fas fa-external-link-square-alt"></i> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="modal fade" id="settingsModal" tabindex="-1" role="dialog" aria-labelledby="settingsModalLabel" |
||||||
|
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-sliders-h"></i> Einstellungen</h5> |
||||||
|
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<h5>Allgemeine Einstellungen</h5> |
||||||
|
<div class="form-group"> |
||||||
|
<label>Logo-Pfad</label> |
||||||
|
<input type="text" v-model="dashboardLogo" class="form-control"> |
||||||
|
</div> |
||||||
|
<div class="form-group"> |
||||||
|
<label>Ticket-Link <small>Link zu einem Ticket ohne Ticketnummer</small></label> |
||||||
|
<input type="text" v-model="ticketSystemUrl" class="form-control"> |
||||||
|
</div> |
||||||
|
<br/> |
||||||
|
<div class="form-group"> |
||||||
|
<label> |
||||||
|
<input type="checkbox" class="form-control-checkbox" v-model="showPT"> |
||||||
|
Ab 8 Stunden nurmehr PT anzeigen |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
<br/> |
||||||
|
<div class="form-group"> |
||||||
|
<label>Design</label> |
||||||
|
<select v-model="theme" class="form-control"> |
||||||
|
<option v-for="availableTheme in themes" :value="availableTheme.name.toLowerCase()">{{ availableTheme.name }}</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<br/> |
||||||
|
<h5>Zurücksetzen & Löschen</h5> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-6"> |
||||||
|
<button class="btn btn-outline-warning btn-full-width" @click="resetToDefault()"> |
||||||
|
Zurücksetzen |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<div class="col-md-6"> |
||||||
|
<button class="btn btn-outline-danger btn-full-width" @click="deleteAllData()">Alle Daten |
||||||
|
löschen |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<br/> |
||||||
|
<h5>Import & Export</h5> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-6"> |
||||||
|
<h6>Export-Json</h6> |
||||||
|
<textarea class="form-control" rows="3" id="exportJsonInput">{{ exportJson }}</textarea> |
||||||
|
<button class="btn btn-success btn-full-width" @click="copy2Clipboard">Export-String kopieren</button> |
||||||
|
</div> |
||||||
|
<div class="col-md-6"> |
||||||
|
<h6>Import</h6> |
||||||
|
<textarea class="form-control" rows="3" v-model="inputs.importJson"></textarea> |
||||||
|
<button class="btn btn-success btn-full-width" @click="importData">Import</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="modal-footer"> |
||||||
|
<button class="btn btn-primary" v-on:click="updateStorage()" data-dismiss="modal">Speichern</button> |
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Schließen</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="modal fade" id="historyModal" tabindex="-1" role="dialog" aria-labelledby="historyModalLabel" |
||||||
|
aria-hidden="true" v-if="selectedTicket"> |
||||||
|
<div class="modal-dialog" role="document"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<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> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<ul class="list-group ticket-history"> |
||||||
|
<li class="list-group-item" v-for="(tracker, historyIndex) in selectedTicket.history" v-if="!tracker.manually"> |
||||||
|
<div> |
||||||
|
<div class="float-end" :title="exactTimestamp(tracker.trackingStarted)"> |
||||||
|
{{ formattedDate(tracker.trackingStarted) }} |
||||||
|
</div> |
||||||
|
Start: |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<div class="float-end" :title="exactTimestamp(tracker.trackingStopped)"> |
||||||
|
{{ formattedDate(tracker.trackingStopped) }} |
||||||
|
</div> |
||||||
|
Ende: |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<div class="float-end"> |
||||||
|
{{ timeWithPostFix(tracker.minutes) }} |
||||||
|
</div> |
||||||
|
Zeit: |
||||||
|
</div> |
||||||
|
<a href="javascript:" @click="deleteHistoryEntry(null, historyIndex)" class="float-end"> |
||||||
|
<i class="fas fa-trash"></i> |
||||||
|
</a> |
||||||
|
<br/> |
||||||
|
</li> |
||||||
|
<li class="list-group-item bg-light" v-for="(tracker, historyIndex) in selectedTicket.history" |
||||||
|
v-if="tracker.manually && tracker.manually == true"> |
||||||
|
<div> |
||||||
|
<div class="float-end" :title="exactTimestamp(tracker.trackingStarted)"> |
||||||
|
{{ formattedDate(tracker.trackingStarted) }} |
||||||
|
</div> |
||||||
|
Manuell erfasst am: |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<div class="float-end"> |
||||||
|
{{ timeWithPostFix(tracker.minutes) }} |
||||||
|
</div> |
||||||
|
Zeit: |
||||||
|
</div> |
||||||
|
<a href="javascript:" @click="deleteHistoryEntry(ticketIndex, historyIndex)" class="float-end"> |
||||||
|
<i class="fas fa-trash"></i> |
||||||
|
</a> |
||||||
|
<br/> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
|
||||||
|
<script src="https://kit.fontawesome.com/b54a4cceff.js" crossorigin="anonymous"></script> |
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script> |
||||||
|
<script src="js/vue.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://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.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> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,284 @@ |
|||||||
|
let tab = new Vue({ |
||||||
|
el: '#root', |
||||||
|
data: { |
||||||
|
theme: 'quartz', |
||||||
|
themes: null, |
||||||
|
dashboardLogo: 'img/logo.png', |
||||||
|
ticketSystemUrl: '', |
||||||
|
showPT: true, |
||||||
|
linkTarget: '_blank', |
||||||
|
inputs: { |
||||||
|
importJson: '' |
||||||
|
}, |
||||||
|
trackedTickets: [], |
||||||
|
selectedTicket: null |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
let vue = this; |
||||||
|
|
||||||
|
this.loadStorage(); |
||||||
|
this.fetchThemes(); |
||||||
|
moment.locale('de'); |
||||||
|
|
||||||
|
setInterval(() => { |
||||||
|
vue.$forceUpdate(); |
||||||
|
}, 1000 * 60) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
loadStorage() { |
||||||
|
let storedTrackedTickets = JSON.parse(localStorage.getItem('trackedTickets')); |
||||||
|
this.trackedTickets = storedTrackedTickets == null ? [] : storedTrackedTickets; |
||||||
|
|
||||||
|
let storedticketSystemUrl = localStorage.getItem('ticketSystemUrl'); |
||||||
|
this.ticketSystemUrl = storedticketSystemUrl == null ? '' : storedticketSystemUrl; |
||||||
|
|
||||||
|
let storedShowPT = localStorage.getItem('showPT'); |
||||||
|
this.showPT = storedShowPT == null ? true : storedShowPT; |
||||||
|
|
||||||
|
let storedTheme = localStorage.getItem('theme'); |
||||||
|
this.theme = storedTheme == null ? 'materia' : storedTheme; |
||||||
|
}, |
||||||
|
updateStorage() { |
||||||
|
localStorage.setItem('trackedTickets', JSON.stringify(this.trackedTickets)); |
||||||
|
localStorage.setItem('ticketSystemUrl', this.ticketSystemUrl); |
||||||
|
localStorage.setItem('showPT', this.showPT); |
||||||
|
localStorage.setItem('theme', this.theme); |
||||||
|
}, |
||||||
|
resetToDefault() { |
||||||
|
this.updateStorage(); |
||||||
|
}, |
||||||
|
addTrackedTicket() { |
||||||
|
let newTicket = { |
||||||
|
archived: false, |
||||||
|
tracking: false, |
||||||
|
number: '#', |
||||||
|
trackingStarted: null, |
||||||
|
trackingStopped: null, |
||||||
|
history: [] |
||||||
|
}; |
||||||
|
|
||||||
|
this.trackedTickets.push(newTicket); |
||||||
|
this.updateStorage(); |
||||||
|
}, |
||||||
|
startTracking(ticket) { |
||||||
|
this.stopTrackingTicket(); |
||||||
|
ticket.trackingStarted = moment(); |
||||||
|
ticket.tracking = true; |
||||||
|
|
||||||
|
this.$forceUpdate(); |
||||||
|
this.updateStorage(); |
||||||
|
}, |
||||||
|
stopTracking(ticket) { |
||||||
|
ticket.trackingStopped = moment(); |
||||||
|
ticket.tracking = false; |
||||||
|
|
||||||
|
let minutesSpent = moment.duration( |
||||||
|
ticket.trackingStopped.diff(ticket.trackingStarted) |
||||||
|
).as('minutes'); |
||||||
|
|
||||||
|
|
||||||
|
ticket.history.push({ |
||||||
|
trackingStarted: ticket.trackingStarted, |
||||||
|
trackingStopped: ticket.trackingStopped, |
||||||
|
manually: false, |
||||||
|
minutes: Math.round(minutesSpent) |
||||||
|
}); |
||||||
|
|
||||||
|
ticket.trackingStarted = null; |
||||||
|
ticket.trackingStopped = null; |
||||||
|
|
||||||
|
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.trackedTickets.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) { |
||||||
|
this.trackedTickets.splice(index, 1); |
||||||
|
this.updateStorage(); |
||||||
|
}, |
||||||
|
archiveTicket(ticket) { |
||||||
|
ticket.archived = true; |
||||||
|
if (ticket.tracking) { |
||||||
|
this.stopTrackingTicket(); |
||||||
|
} |
||||||
|
this.updateStorage(); |
||||||
|
this.$forceUpdate(); |
||||||
|
}, |
||||||
|
reactivateTicket(ticket) { |
||||||
|
ticket.archived = false; |
||||||
|
this.updateStorage(); |
||||||
|
}, |
||||||
|
deleteHistoryEntry(ticketIndex, historyIndex) { |
||||||
|
if (ticketIndex) { |
||||||
|
this.trackedTickets[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); |
||||||
|
|
||||||
|
this.trackedTickets = json.trackedTickets; |
||||||
|
this.ticketSystemUrl = json.ticketSystemUrl; |
||||||
|
this.showPT = json.showPT; |
||||||
|
this.theme = json.theme; |
||||||
|
this.updateStorage(); |
||||||
|
location.reload(); |
||||||
|
}, |
||||||
|
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) |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
showPT() { |
||||||
|
this.updateStorage(); |
||||||
|
this.$forceUpdate(); |
||||||
|
}, |
||||||
|
theme() { |
||||||
|
this.updateStorage(); |
||||||
|
this.$forceUpdate(); |
||||||
|
} |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
exportJson() { |
||||||
|
return JSON.stringify({ |
||||||
|
trackedTickets: this.trackedTickets, |
||||||
|
ticketSystemUrl: this.ticketSystemUrl, |
||||||
|
showPT: this.showPT, |
||||||
|
theme: this.theme |
||||||
|
}); |
||||||
|
}, |
||||||
|
archivedTrackers() { |
||||||
|
let vue = this; |
||||||
|
let count = 0; |
||||||
|
|
||||||
|
this.trackedTickets.forEach((ticket) => { |
||||||
|
count += (ticket.archived ? 1 : 0); |
||||||
|
}) |
||||||
|
|
||||||
|
return count; |
||||||
|
}, |
||||||
|
activeTrackers() { |
||||||
|
let vue = this; |
||||||
|
let count = 0; |
||||||
|
|
||||||
|
this.trackedTickets.forEach((ticket) => { |
||||||
|
count += (ticket.archived ? 0 : 1); |
||||||
|
}) |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
} |
||||||
|
}); |
Reference in new issue