PWA: Dezimal-Mengen, STZ-Freigabe, Notiz-Trennung, qty_done=1
- Mengenanzeige klickbar für Dezimaleingabe (Komma), +/- bleiben Ganzzahl - Freigeben/Wiedereröffnen-Button für einzelne Stundenzettel - Warnung bei Freigabe ohne Leistung mit Service-Auswahl-Dialog (Standard-Dienstleistung des Kunden vorausgewählt) - API: validate_stz und setdraft_stz Endpunkte - API: default_service_id/label im get_order_context - Produktübernahme: qty_done Standard auf 1 statt 0 - Merkzettel auf Produktliste: nur Anzeige + Abhaken, kein Hinzufügen - Scroll-Position nach Panel-Neurendern zurücksetzen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dabcdbde13
commit
4ca3ea5deb
4 changed files with 321 additions and 44 deletions
|
|
@ -30,6 +30,7 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
|||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
|
||||
dol_include_once('/stundenzettel/class/stundenzettel.class.php');
|
||||
dol_include_once('/stundenzettel/lib/stundenzettel.lib.php');
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
|
|
@ -237,6 +238,20 @@ switch ($action) {
|
|||
$customer = new Societe($db);
|
||||
$customer->fetch($order->socid);
|
||||
|
||||
// Standard-Dienstleistung des Kunden ermitteln
|
||||
$defaultServiceId = 0;
|
||||
$defaultServiceLabel = '';
|
||||
if (isset($customer->array_options['options_stundenzettel_default_service'])) {
|
||||
$defaultServiceId = (int)$customer->array_options['options_stundenzettel_default_service'];
|
||||
if ($defaultServiceId > 0) {
|
||||
$sqlDS = "SELECT ref, label FROM ".MAIN_DB_PREFIX."product WHERE rowid = ".((int)$defaultServiceId);
|
||||
$resDS = $db->query($sqlDS);
|
||||
if ($resDS && ($objDS = $db->fetch_object($resDS))) {
|
||||
$defaultServiceLabel = $objDS->ref.' - '.$objDS->label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auftragsdaten
|
||||
$response['order'] = array(
|
||||
'id' => (int)$order->id,
|
||||
|
|
@ -244,7 +259,9 @@ switch ($action) {
|
|||
'date' => dol_print_date($order->date_commande, 'day'),
|
||||
'customer_name' => $customer->name,
|
||||
'customer_id' => (int)$customer->id,
|
||||
'status' => (int)$order->statut
|
||||
'status' => (int)$order->statut,
|
||||
'default_service_id' => $defaultServiceId,
|
||||
'default_service_label' => $defaultServiceLabel
|
||||
);
|
||||
|
||||
// Stundenzettel finden (per ID oder letzten Draft)
|
||||
|
|
@ -1497,7 +1514,7 @@ switch ($action) {
|
|||
$objLine->rowid, // fk_commandedet
|
||||
null,
|
||||
$objLine->qty, // qty_original
|
||||
0, // qty_done
|
||||
1, // qty_done (Standard: 1)
|
||||
'order',
|
||||
$objLine->description
|
||||
);
|
||||
|
|
@ -1543,7 +1560,7 @@ switch ($action) {
|
|||
null, // fk_commandedet (kein Auftragsbezug)
|
||||
null,
|
||||
$qty, // qty_original = Zielmenge
|
||||
0, // qty_done
|
||||
1, // qty_done (Standard: 1)
|
||||
'added',
|
||||
$description ?: $productLabel
|
||||
);
|
||||
|
|
@ -1556,6 +1573,65 @@ switch ($action) {
|
|||
$response['added'] = $added;
|
||||
break;
|
||||
|
||||
// ---- Stundenzettel freigeben (validieren) ----
|
||||
case 'validate_stz':
|
||||
if (!$canWrite) {
|
||||
$response['error'] = 'Keine Schreibberechtigung';
|
||||
break;
|
||||
}
|
||||
|
||||
$stzId = GETPOST('stz_id', 'int');
|
||||
$stz = new Stundenzettel($db);
|
||||
if ($stz->fetch($stzId) <= 0) {
|
||||
$response['error'] = 'Stundenzettel nicht gefunden';
|
||||
break;
|
||||
}
|
||||
if ($stz->status != Stundenzettel::STATUS_DRAFT) {
|
||||
$response['error'] = 'Stundenzettel ist nicht im Entwurf';
|
||||
break;
|
||||
}
|
||||
if (!canEditStz($stz, $user, $canWriteAll)) {
|
||||
$response['error'] = 'Keine Berechtigung';
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $stz->validate($user);
|
||||
if ($result > 0) {
|
||||
// Netto-Wert aller Stundenzettel des Auftrags neu berechnen
|
||||
updateOrderNettoSTZ($db, $stz->fk_commande);
|
||||
$response['success'] = true;
|
||||
} else {
|
||||
$response['error'] = $stz->error ?: 'Fehler beim Freigeben';
|
||||
}
|
||||
break;
|
||||
|
||||
// ---- Stundenzettel zurueck auf Entwurf setzen ----
|
||||
case 'setdraft_stz':
|
||||
if (!$canWrite) {
|
||||
$response['error'] = 'Keine Schreibberechtigung';
|
||||
break;
|
||||
}
|
||||
|
||||
$stzId = GETPOST('stz_id', 'int');
|
||||
$stz = new Stundenzettel($db);
|
||||
if ($stz->fetch($stzId) <= 0) {
|
||||
$response['error'] = 'Stundenzettel nicht gefunden';
|
||||
break;
|
||||
}
|
||||
if ($stz->status == Stundenzettel::STATUS_DRAFT) {
|
||||
$response['error'] = 'Stundenzettel ist bereits im Entwurf';
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $stz->setDraft($user);
|
||||
if ($result > 0) {
|
||||
updateOrderNettoSTZ($db, $stz->fk_commande);
|
||||
$response['success'] = true;
|
||||
} else {
|
||||
$response['error'] = $stz->error ?: 'Fehler beim Zuruecksetzen';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$response['error'] = 'Unbekannte Aktion: '.$action;
|
||||
}
|
||||
|
|
|
|||
42
css/pwa.css
42
css/pwa.css
|
|
@ -530,6 +530,29 @@ body {
|
|||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
.qty-editable {
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
padding: 2px 6px;
|
||||
border: 1px dashed transparent;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.qty-editable:active {
|
||||
background: var(--colorbackline);
|
||||
border-color: var(--colorborder);
|
||||
}
|
||||
.qty-inline-input {
|
||||
width: 56px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: 6px;
|
||||
background: var(--colorbackinput);
|
||||
color: var(--colortext);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* === Accordion-Sections === */
|
||||
.accordion-section {
|
||||
|
|
@ -1269,6 +1292,25 @@ body {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
/* === STZ Aktions-Buttons === */
|
||||
.stz-actions {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.warning-box {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
border: 1px solid rgba(255, 193, 7, 0.4);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.warning-box p {
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
.warning-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Safe-Area fuer Geraete mit Notch */
|
||||
@supports (padding-bottom: env(safe-area-inset-bottom)) {
|
||||
.fab {
|
||||
|
|
|
|||
237
js/pwa.js
237
js/pwa.js
|
|
@ -464,6 +464,8 @@
|
|||
self.state.customerName = res.order.customer_name;
|
||||
self.state.canWrite = res.can_write;
|
||||
self.state.canEditStz = res.can_edit_stz;
|
||||
self.state.defaultServiceId = res.order.default_service_id || 0;
|
||||
self.state.defaultServiceLabel = res.order.default_service_label || '';
|
||||
|
||||
if (res.stz) {
|
||||
self.state.stzId = res.stz.id;
|
||||
|
|
@ -652,6 +654,9 @@
|
|||
this.renderPanelStundenzettel();
|
||||
this.renderPanelProducts();
|
||||
this.renderPanelTracking();
|
||||
|
||||
// Scroll-Position aller Panels nach oben zuruecksetzen
|
||||
$('.swipe-panel').scrollTop(0);
|
||||
},
|
||||
|
||||
// ---- Panel 0: Alle Stundenzettel ----
|
||||
|
|
@ -892,9 +897,30 @@
|
|||
html += '</div>'; // accordion-section
|
||||
}
|
||||
|
||||
// ---- AKTIONS-BUTTONS (Freigeben / Wiedereroeffnen) ----
|
||||
if (self.state.canWrite) {
|
||||
html += '<div class="stz-actions mt-12">';
|
||||
if (isDraft) {
|
||||
html += '<button class="btn btn-primary w-full" id="btn-validate-stz">✅ Stundenzettel freigeben</button>';
|
||||
} else {
|
||||
html += '<button class="btn btn-ghost w-full" id="btn-setdraft-stz">🔓 Zur\u00fcck auf Entwurf</button>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
$panel.html(html);
|
||||
|
||||
// ---- Event-Listener ----
|
||||
// Freigeben / Wiedereroeffnen
|
||||
$('#btn-validate-stz').on('click', function() {
|
||||
self.validateStz();
|
||||
});
|
||||
$('#btn-setdraft-stz').on('click', function() {
|
||||
self.showConfirm('Zur\u00fcck auf Entwurf?', 'Stundenzettel wird wieder bearbeitbar.', 'Zur\u00fcck auf Entwurf').then(function(ok) {
|
||||
if (ok) self.setDraftStz();
|
||||
});
|
||||
});
|
||||
|
||||
// Leistungen
|
||||
$panel.find('.btn-edit-leistung').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
|
|
@ -929,6 +955,52 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Menge direkt bearbeiten (Klick auf Zahl)
|
||||
$panel.find('.qty-editable').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var $display = $(this);
|
||||
if ($display.find('input').length) return; // Bereits im Edit-Modus
|
||||
var id = $display.data('id');
|
||||
var qty = parseFloat($display.data('qty'));
|
||||
var max = parseFloat($display.data('max'));
|
||||
var qtyStr = qty.toLocaleString('de-DE', {minimumFractionDigits: 0, maximumFractionDigits: 2});
|
||||
$display.html('<input type="text" inputmode="decimal" class="qty-inline-input" value="' + qtyStr + '">');
|
||||
var $input = $display.find('input');
|
||||
$input.focus().select();
|
||||
|
||||
var submitQty = function() {
|
||||
var raw = $input.val().replace(',', '.').trim();
|
||||
var newQty = parseFloat(raw);
|
||||
if (isNaN(newQty) || newQty < 0) {
|
||||
$display.html(self.formatQty(qty));
|
||||
return;
|
||||
}
|
||||
// Auf 2 Dezimalstellen runden
|
||||
newQty = Math.round(newQty * 100) / 100;
|
||||
if (newQty === qty) {
|
||||
$display.html(self.formatQty(qty));
|
||||
return;
|
||||
}
|
||||
if (max > 0 && newQty > max) {
|
||||
self.showConfirm('Auftragsmenge \u00fcberschritten', 'Auftragsmenge: ' + self.formatQty(max) + '\nNeue Menge: ' + self.formatQty(newQty), 'Trotzdem', 'btn-warning').then(function(ok) {
|
||||
if (ok) {
|
||||
self.updateQty(id, newQty);
|
||||
} else {
|
||||
$display.html(self.formatQty(qty));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.updateQty(id, newQty);
|
||||
}
|
||||
};
|
||||
|
||||
$input.on('blur', submitQty);
|
||||
$input.on('keydown', function(ev) {
|
||||
if (ev.key === 'Enter') { ev.preventDefault(); $input.blur(); }
|
||||
if (ev.key === 'Escape') { $input.off('blur'); $display.html(self.formatQty(qty)); }
|
||||
});
|
||||
});
|
||||
|
||||
// Produkt loeschen
|
||||
$panel.find('.btn-delete-product').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
|
|
@ -1060,7 +1132,7 @@
|
|||
var activeFilter = self.state.productFilter || 'open';
|
||||
var isDraft = stz && stz.status == 0;
|
||||
|
||||
// Merkzettel-Notizen oben anzeigen (wie stundenzettel_commande.php)
|
||||
// Merkzettel-Notizen oben anzeigen (Abhaken moeglich, neue Notizen nur auf Panel 1)
|
||||
if (self.data.notes && self.data.notes.length) {
|
||||
html += '<div class="merkzettel-box">';
|
||||
html += '<div class="merkzettel-box-header">';
|
||||
|
|
@ -1083,25 +1155,6 @@
|
|||
html += '</li>';
|
||||
});
|
||||
html += '</ul>';
|
||||
// Notiz-Input (nur im Entwurf)
|
||||
if (isDraft && canWrite) {
|
||||
html += '<div class="merkzettel-box-add">';
|
||||
html += '<input type="text" id="note-input-p2" placeholder="Neue Notiz...">';
|
||||
html += '<button class="btn btn-primary btn-icon" id="btn-add-note-p2">+</button>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
} else if (isDraft && canWrite) {
|
||||
// Leere Box mit nur Input zum Hinzufuegen
|
||||
html += '<div class="merkzettel-box">';
|
||||
html += '<div class="merkzettel-box-header">';
|
||||
html += '<span class="merkzettel-box-icon">📋</span>';
|
||||
html += '<strong>Merkzettel</strong>';
|
||||
html += '</div>';
|
||||
html += '<div class="merkzettel-box-add">';
|
||||
html += '<input type="text" id="note-input-p2" placeholder="Neue Notiz...">';
|
||||
html += '<button class="btn btn-primary btn-icon" id="btn-add-note-p2">+</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
|
|
@ -1295,12 +1348,10 @@
|
|||
self.showCreateStzDialog();
|
||||
});
|
||||
|
||||
// Merkzettel auf Panel 2: Abhaken + Hinzufuegen
|
||||
// Merkzettel auf Panel 2: Abhaken moeglich, neue Notizen nur auf Panel 1
|
||||
$panel.find('.merkzettel-box .note-checkbox').on('click', function() {
|
||||
self.toggleNote($(this).data('id'), $(this).data('checked'));
|
||||
});
|
||||
$('#btn-add-note-p2').on('click', function() { self.addNoteFromPanel2(); });
|
||||
$('#note-input-p2').on('keypress', function(e) { if (e.which === 13) self.addNoteFromPanel2(); });
|
||||
},
|
||||
|
||||
renderProductCard: function(p, isDraft, canWrite) {
|
||||
|
|
@ -1336,7 +1387,7 @@
|
|||
if (isDraft && canWrite) {
|
||||
html += '<div class="qty-controls">';
|
||||
html += '<button class="qty-btn btn-qty-minus" data-id="' + p.id + '" data-qty="' + p.qty_done + '">−</button>';
|
||||
html += '<span class="qty-display">' + self.formatQty(p.qty_done) + '</span>';
|
||||
html += '<span class="qty-display qty-editable" data-id="' + p.id + '" data-qty="' + p.qty_done + '" data-max="' + (p.qty_original || 9999) + '">' + self.formatQty(p.qty_done) + '</span>';
|
||||
html += '<button class="qty-btn btn-qty-plus" data-id="' + p.id + '" data-qty="' + p.qty_done + '" data-max="' + (p.qty_original || 9999) + '">+</button>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
|
|
@ -1698,6 +1749,129 @@
|
|||
});
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// AKTIONEN: Freigeben / Wiedereroeffnen
|
||||
// ============================================================
|
||||
|
||||
validateStz: function() {
|
||||
var self = this;
|
||||
|
||||
// Pruefen ob Leistungen vorhanden
|
||||
if (!self.data.leistungen || !self.data.leistungen.length) {
|
||||
// Keine Leistungen - Warnung mit Option eine hinzuzufuegen
|
||||
self.showValidateWarningDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// Leistungen vorhanden - direkt freigeben
|
||||
self._doValidateStz();
|
||||
},
|
||||
|
||||
showValidateWarningDialog: function() {
|
||||
var self = this;
|
||||
var html = '';
|
||||
|
||||
html += '<div class="warning-box mb-12">';
|
||||
html += '<p><strong>⚠ Keine Leistungen erfasst!</strong></p>';
|
||||
html += '<p>F\u00fcr die Rechnungsstellung wird mindestens eine Leistungsposition ben\u00f6tigt.</p>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="form-group"><label>Leistung ausw\u00e4hlen</label>';
|
||||
html += '<select id="dlg-validate-service" style="width:100%;padding:12px;background:var(--colorbackinput);border:1px solid var(--colorborder);border-radius:8px;color:var(--colortext);font-size:16px;min-height:48px;">';
|
||||
html += '<option value="">-- Ohne Leistung freigeben --</option>';
|
||||
|
||||
// Standard-Service des Kunden vorselektieren
|
||||
if (self.state.defaultServiceId > 0) {
|
||||
html += '<option value="' + self.state.defaultServiceId + '" selected>' + self.escHtml(self.state.defaultServiceLabel) + ' (Standard)</option>';
|
||||
}
|
||||
html += '</select></div>';
|
||||
|
||||
// Dienste laden
|
||||
self.api('get_services', {}).then(function(res) {
|
||||
if (res.success && res.services) {
|
||||
var $select = $('#dlg-validate-service');
|
||||
res.services.forEach(function(s) {
|
||||
// Standard-Service nicht doppelt anzeigen
|
||||
if (s.id == self.state.defaultServiceId) return;
|
||||
$select.append('<option value="' + s.id + '">' + self.escHtml(s.ref + ' - ' + s.label) + '</option>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var footer = '';
|
||||
footer += '<button class="btn btn-primary w-full mb-8" id="dlg-validate-with-leistung">Mit Leistung freigeben</button>';
|
||||
footer += '<button class="btn btn-ghost w-full" id="dlg-validate-without">Ohne Leistung freigeben</button>';
|
||||
|
||||
self.openBottomSheet('Stundenzettel freigeben', html, footer);
|
||||
|
||||
$('#dlg-validate-with-leistung').on('click', function() {
|
||||
var serviceId = $('#dlg-validate-service').val();
|
||||
if (!serviceId) {
|
||||
self.showToast('Bitte eine Leistung ausw\u00e4hlen', 'error');
|
||||
return;
|
||||
}
|
||||
self.closeBottomSheet();
|
||||
// Leistung mit heutigem Datum und Standard-Zeiten anlegen, dann freigeben
|
||||
var stz = self.data.stz;
|
||||
var data = {
|
||||
stz_id: self.state.stzId,
|
||||
date: stz.date_iso || new Date().toISOString().substr(0, 10),
|
||||
time_start: '08:00',
|
||||
time_end: '16:00',
|
||||
description: '',
|
||||
fk_product: serviceId
|
||||
};
|
||||
self.showLoading();
|
||||
self.api('add_leistung', data).then(function(res) {
|
||||
if (res.success) {
|
||||
self._doValidateStz();
|
||||
} else {
|
||||
self.hideLoading();
|
||||
self.showToast(res.error || 'Fehler', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#dlg-validate-without').on('click', function() {
|
||||
self.closeBottomSheet();
|
||||
self._doValidateStz();
|
||||
});
|
||||
},
|
||||
|
||||
_doValidateStz: function() {
|
||||
var self = this;
|
||||
self.showLoading();
|
||||
self.api('validate_stz', {stz_id: self.state.stzId}).then(function(res) {
|
||||
self.hideLoading();
|
||||
if (res.success) {
|
||||
self.showToast('Stundenzettel freigegeben', 'success');
|
||||
self.reloadData();
|
||||
} else {
|
||||
self.showToast(res.error || 'Fehler beim Freigeben', 'error');
|
||||
}
|
||||
}).catch(function() {
|
||||
self.hideLoading();
|
||||
self.showToast('Verbindungsfehler', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
setDraftStz: function() {
|
||||
var self = this;
|
||||
self.showLoading();
|
||||
self.api('setdraft_stz', {stz_id: self.state.stzId}).then(function(res) {
|
||||
self.hideLoading();
|
||||
if (res.success) {
|
||||
self.showToast('Stundenzettel zur\u00fcck auf Entwurf', 'success');
|
||||
self.reloadData();
|
||||
} else {
|
||||
self.showToast(res.error || 'Fehler', 'error');
|
||||
}
|
||||
}).catch(function() {
|
||||
self.hideLoading();
|
||||
self.showToast('Verbindungsfehler', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// AKTIONEN: Notizen
|
||||
// ============================================================
|
||||
|
|
@ -1717,21 +1891,6 @@
|
|||
});
|
||||
},
|
||||
|
||||
addNoteFromPanel2: function() {
|
||||
var self = this;
|
||||
var text = $('#note-input-p2').val().trim();
|
||||
if (!text) return;
|
||||
|
||||
self.api('add_note', {stz_id: self.state.stzId, note: text}).then(function(res) {
|
||||
if (res.success) {
|
||||
$('#note-input-p2').val('');
|
||||
self.reloadData();
|
||||
} else {
|
||||
self.showToast(res.error || 'Fehler', 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleNote: function(id, checked) {
|
||||
var self = this;
|
||||
self.api('toggle_note', {note_id: id, checked: checked, stz_id: self.state.stzId}).then(function(res) {
|
||||
|
|
|
|||
4
pwa.php
4
pwa.php
|
|
@ -38,7 +38,7 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#4390dc');
|
|||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="img/icon-192.png">
|
||||
<link rel="apple-touch-icon" href="img/icon-192.png">
|
||||
<link rel="stylesheet" href="css/pwa.css?v=2.8">
|
||||
<link rel="stylesheet" href="css/pwa.css?v=2.9">
|
||||
<style>:root { --primary: <?php echo htmlspecialchars($themeColor); ?>; }</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -160,7 +160,7 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#4390dc');
|
|||
authUrl: '<?php echo dol_buildpath('/stundenzettel/ajax/pwa_auth.php', 1); ?>'
|
||||
};
|
||||
</script>
|
||||
<script src="js/pwa.js?v=2.8"></script>
|
||||
<script src="js/pwa.js?v=2.9"></script>
|
||||
|
||||
<!-- Service Worker Registration -->
|
||||
<script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue