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.
513 lines
28 KiB
513 lines
28 KiB
<!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>Timetrack</title> |
|
<link rel="manifest" href="js/manifest.json"> |
|
<link rel="shortcut icon" type="image/x-icon" href="assets/img/favicon.ico"> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/css/iziToast.css"> |
|
</head> |
|
|
|
<body> |
|
<div id="root"> |
|
<div class="container"> |
|
<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"> |
|
<template v-for="(ticket, ticketIndex) in tickets"> |
|
<div class="col-lg-4 col-md-6"> |
|
<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"> |
|
<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 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> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</template> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- Tickets Modal --> |
|
<div class="modal modal-fullscreen fade" id="showTicketsModal" tabindex="-1" role="dialog" |
|
aria-labelledby="showTicketsModalLabel" |
|
aria-hidden="true"> |
|
<div class="modal-dialog showTicketsModalDialog" role="document"> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<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> |
|
</div> |
|
<div class="modal-body"> |
|
<div class="row"> |
|
<template v-for="(ticket, ticketIndex) in tickets"> |
|
<div class="col-md-6"> |
|
<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 v-if="getTotalTime(ticket) > 0">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(ticketIndex)" 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.replace('#', '')" |
|
target="_blank" class="btn btn-dark ticket-action-button" title="Ticket"> |
|
<i class="fas fa-external-link-square-alt"></i> |
|
</a> |
|
</div> |
|
</div> |
|
<br/> |
|
</div> |
|
</template> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- Archive Modal --> |
|
<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"> |
|
<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"> |
|
<h6><span v-if="isTicketNumber(ticket.number)"></span>{{ ticket.number }}</h6> |
|
<div v-if="ticket.description"> |
|
<p class="blockquote">{{ ticket.description }}</p> |
|
</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> |
|
|
|
<br> |
|
<div class="col-md-12 row"> |
|
<div class="col"> |
|
<button class="btn btn-success ticket-action-button" data-bs-dismiss="modal" |
|
@click="reactivateTicket(ticketIndex)" 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, true)" 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.replace('#', '')" |
|
target="_blank" class="btn btn-dark ticket-action-button" title="Ticket"> |
|
<i class="fas fa-external-link-square-alt"></i> |
|
</a> |
|
</div> |
|
</div> |
|
<br/> |
|
</div> |
|
</template> |
|
</div> |
|
</div> |
|
<div class="modal-footer row"> |
|
<div class="col-sm-6"></div> |
|
<div class="col-sm-6"> |
|
<i class="fas fa-search"></i> |
|
<input @keydown="$forceUpdate()" type="text" class="form-control search-field float-right" v-model="searchQuery" placeholder="Suche.."> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- Settings Modal --> |
|
<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 row"> |
|
<div class="col-md-6"> |
|
<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> |
|
<input type="checkbox" class="form-control-checkbox" v-model="publicDB"> |
|
Bei Portalumstellungen auf Standard-DB wechseln? |
|
</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/> |
|
<div class="form-group"> |
|
<label>Portalnamen <a class="text-muted" href="https://settings.vemap.docker/?fetchPortals" target="_blank">(Import-String hier)</a></label> |
|
<input v-model="importStringForPortals" class="form-control"/> |
|
<a href="javascript:" @click="importPortalsJson" class="btn" v-if="this.importStringForPortals !== ''">Import</a> |
|
</div> |
|
<br/> |
|
</div> |
|
<div class="col-md-6"> |
|
<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> |
|
<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> |
|
|
|
<!-- History Modal --> |
|
<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"> |
|
<template v-for="(tracker, historyIndex) in selectedTicket.history"> |
|
<li class="list-group-item" 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> |
|
</template> |
|
<template v-for="(tracker, historyIndex) in selectedTicket.history"> |
|
<li class="list-group-item bg-light" |
|
v-if="tracker.manually"> |
|
<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> |
|
</template> |
|
</ul> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- Switcher Modal --> |
|
<div class="modal fade" id="switcherModal" tabindex="-1" role="dialog" aria-labelledby="switcherModalLabel" |
|
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-random"></i> Portal Switcher</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-12"> |
|
<div class="form-group"> |
|
<label class="text-muted">Portalname:</label> |
|
<input v-if="!portals || portals.length === 0" class="form-control" @change="updateStorage()" @keydown="updateStorage()" v-model="portal"/> |
|
<select v-else v-model="portal" class="form-control"> |
|
<option v-for="selectablePortal in portals" :value="selectablePortal">{{ selectablePortal }}</option> |
|
</select> |
|
</div> |
|
</div> |
|
<template v-if="portal && portal !== ''"> |
|
<div class="col-12 switch-portal-button"> |
|
<a class="btn btn-danger btn-switcher" href="javascript:" @click="sendPortalChangeRequest"> |
|
<i class="fas fa-random"></i> Wechsle Portal |
|
</a> |
|
</div> |
|
<div class="col"> |
|
<a class="btn btn-info btn-switcher" href="https://my.vemap.docker" target="_blank">Docker</a> |
|
</div> |
|
<div class="col"> |
|
<a class="btn btn-warning btn-switcher" :href="getPortalLink(true)" target="_blank">Test</a> |
|
</div> |
|
<div class="col"> |
|
<a class="btn btn-success btn-switcher" :href="getPortalLink()" target="_blank">Live</a> |
|
</div> |
|
</template> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div id="bottom-menu"> |
|
<div class="container"> |
|
<div class="row"> |
|
<div class="col"> |
|
<a type="button" |
|
class="btn btn-success text-light bottom-menu-item" |
|
@click="addTrackedTicket()" |
|
data-bs-toggle="tooltip" |
|
data-bs-placement="left" |
|
title="Neuer Tracker"> |
|
<i class="fas fa-plus"></i> |
|
</a> |
|
</div> |
|
<div class="col" v-if="tickets.length > 0"> |
|
<a type="button" |
|
class="btn btn-primary text-light bottom-menu-item" |
|
data-toggle="modal" |
|
data-target="#showTicketsModal" |
|
data-bs-toggle="tooltip" |
|
data-bs-placement="top" |
|
title="Alle Tracker"> |
|
<i class="fas fa-clock"></i> |
|
</a> |
|
</div> |
|
<div class="col" v-if="archive.length > 0"> |
|
<a type="button" |
|
class="btn btn-secondary text-dark bottom-menu-item" |
|
data-toggle="modal" |
|
data-target="#showArchivedTicketsModal" |
|
data-bs-toggle="tooltip" |
|
data-bs-placement="top" |
|
title="Archivierte Tracker"> |
|
<i class="fas fa-archive"></i> |
|
</a> |
|
</div> |
|
<div class="col"> |
|
<a type="button" |
|
class="btn btn-warning text-light bottom-menu-item" |
|
data-toggle="modal" |
|
data-target="#switcherModal" |
|
data-bs-toggle="tooltip" |
|
data-bs-placement="top" |
|
title="Portal Switcher"> |
|
<i class="fas fa-random"></i> |
|
</a> |
|
</div> |
|
<div class="col"> |
|
<a type="button" |
|
class="btn btn-dark text-light bottom-menu-item" |
|
data-toggle="modal" |
|
data-target="#settingsModal" |
|
data-bs-toggle="tooltip" |
|
data-bs-placement="left" |
|
title="Einstellungen"> |
|
<i class="fas fa-sliders-h"></i> |
|
</a> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js"></script> |
|
<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="https://unpkg.com/vue@next"></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> |