v5.2: Edit-Button für Bestellzeilen, Dialog vereinfacht
- Edit-Button (Stift-Icon) an jeder Bestellzeile statt Touch-Events - Abbrechen-Button aus Line-Edit-Dialog entfernt (nur Löschen/Speichern) - Status-Typ-Vergleich für Entwurfs-Erkennung korrigiert - Cache-Busting für zuverlässige PWA-Updates (v=60) - Service Worker v6.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
707d7f50a0
commit
08221a660d
7 changed files with 57 additions and 33 deletions
12
ChangeLog.md
12
ChangeLog.md
|
|
@ -27,6 +27,18 @@
|
||||||
- **Kompakterer Scan-Button**: Mehr Platz für Tool-Buttons
|
- **Kompakterer Scan-Button**: Mehr Platz für Tool-Buttons
|
||||||
- **Service Worker v5.3**: Aktualisiertes Caching mit JsBarcode-Library
|
- **Service Worker v5.3**: Aktualisiertes Caching mit JsBarcode-Library
|
||||||
|
|
||||||
|
## 5.2
|
||||||
|
|
||||||
|
### Bestellzeilen-Bearbeitung
|
||||||
|
- **Edit-Button**: Stift-Icon an jeder Bestellzeile für zuverlässiges Öffnen des Bearbeitungsdialogs
|
||||||
|
- **Touch-Kompatibilität**: Button funktioniert zuverlässig auf allen Touch-Geräten
|
||||||
|
- **Dialog vereinfacht**: Abbrechen-Button entfernt, nur noch Löschen und Speichern
|
||||||
|
- **Typ-Fix**: Status-Vergleich für Entwurfs-Erkennung korrigiert
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
- **Cache-Busting**: CSS/JS URLs mit Versionsparameter für zuverlässige PWA-Updates
|
||||||
|
- **Service Worker v6.0**: Optimiertes Caching-Verhalten
|
||||||
|
|
||||||
## 5.1
|
## 5.1
|
||||||
|
|
||||||
### Bestellungen verwalten
|
### Bestellungen verwalten
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ echo json_encode([
|
||||||
'id' => $order->id,
|
'id' => $order->id,
|
||||||
'ref' => $order->ref,
|
'ref' => $order->ref,
|
||||||
'ref_supplier' => $order->ref_supplier,
|
'ref_supplier' => $order->ref_supplier,
|
||||||
'status' => $order->statut,
|
'status' => (int) $order->statut,
|
||||||
'supplier_name' => $order->thirdparty->name ?? '',
|
'supplier_name' => $order->thirdparty->name ?? '',
|
||||||
'total_ht' => (float) $order->total_ht
|
'total_ht' => (float) $order->total_ht
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class modHandyBarcodeScanner extends DolibarrModules
|
||||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@handybarcodescanner'
|
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@handybarcodescanner'
|
||||||
|
|
||||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '5.4';
|
$this->version = '6.0';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1075,16 +1075,10 @@
|
||||||
border: 1px solid var(--colorborder, #2b2c2e);
|
border: 1px solid var(--colorborder, #2b2c2e);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
color: var(--colortext, #dcdcdc);
|
color: var(--colortext, #dcdcdc);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-line:hover {
|
|
||||||
background: var(--colorbacktitle, #3b3c3e);
|
|
||||||
border-color: var(--butactionbg, #ad8c4f);
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-line-info {
|
.order-line-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
@ -1112,6 +1106,29 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-line-edit-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--colortextmuted, #888);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-line-edit-btn:hover,
|
||||||
|
.order-line-edit-btn:active {
|
||||||
|
background: var(--colorbacktitle, #3b3c3e);
|
||||||
|
color: var(--butactionbg, #0077b3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-line-edit-btn svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* Line edit dialog */
|
/* Line edit dialog */
|
||||||
.line-edit-dialog {
|
.line-edit-dialog {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
|
|
@ -1184,30 +1184,32 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canEdit = order.status === 0;
|
const canEdit = order.status == 0;
|
||||||
|
|
||||||
content.innerHTML = lines.map(line => `
|
content.innerHTML = lines.map(line => `
|
||||||
<div class="order-line ${canEdit ? 'editable' : ''}" data-line-id="${line.id}" data-order-id="${orderId}" data-is-freetext="${line.is_freetext ? '1' : '0'}">
|
<div class="order-line" data-line-id="${line.id}" data-order-id="${orderId}" data-is-freetext="${line.is_freetext ? '1' : '0'}">
|
||||||
<div class="order-line-info">
|
<div class="order-line-info">
|
||||||
<div class="order-line-label">${escapeHtml(line.product_label)}</div>
|
<div class="order-line-label">${escapeHtml(line.product_label)}</div>
|
||||||
<div class="order-line-ref">${line.product_ref ? 'Ref: ' + escapeHtml(line.product_ref) : ''} ${line.is_freetext ? '(Freitext)' : (line.stock > 0 ? '| Lager: ' + line.stock : '')}</div>
|
<div class="order-line-ref">${line.product_ref ? 'Ref: ' + escapeHtml(line.product_ref) : ''} ${line.is_freetext ? '(Freitext)' : (line.stock > 0 ? '| Lager: ' + line.stock : '')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-line-qty">${line.qty}x</div>
|
<div class="order-line-qty">${line.qty}x</div>
|
||||||
|
${canEdit ? `<button type="button" class="order-line-edit-btn" data-line-id="${line.id}" title="Bearbeiten">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||||
|
</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
if (canEdit) {
|
// Edit-Button Events
|
||||||
content.querySelectorAll('.order-line').forEach(lineEl => {
|
content.querySelectorAll('.order-line-edit-btn').forEach(btn => {
|
||||||
lineEl.addEventListener('click', function() {
|
btn.addEventListener('click', function(e) {
|
||||||
const lineId = parseInt(this.dataset.lineId);
|
e.stopPropagation();
|
||||||
const oId = parseInt(this.dataset.orderId);
|
const lineId = parseInt(this.dataset.lineId);
|
||||||
const line = currentModalLines.find(l => l.id === lineId);
|
const line = currentModalLines.find(l => l.id === lineId);
|
||||||
if (line) {
|
if (line) {
|
||||||
showLineEditDialogInModal(line, oId);
|
showLineEditDialogInModal(line, orderId);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLineEditDialogInModal(line, orderId) {
|
function showLineEditDialogInModal(line, orderId) {
|
||||||
|
|
@ -1237,7 +1239,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="line-edit-buttons">
|
<div class="line-edit-buttons">
|
||||||
<button type="button" class="action-btn btn-danger" id="line-delete">Löschen</button>
|
<button type="button" class="action-btn btn-danger" id="line-delete">Löschen</button>
|
||||||
<button type="button" class="action-btn btn-secondary" id="line-cancel">Abbrechen</button>
|
|
||||||
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
|
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1249,8 +1250,6 @@
|
||||||
resumeScanner();
|
resumeScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('line-cancel').addEventListener('click', closeDialog);
|
|
||||||
|
|
||||||
document.getElementById('line-save').addEventListener('click', () => {
|
document.getElementById('line-save').addEventListener('click', () => {
|
||||||
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
|
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
|
||||||
const descInput = document.getElementById('line-desc-input');
|
const descInput = document.getElementById('line-desc-input');
|
||||||
|
|
@ -1483,7 +1482,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canEdit = order.status === 0; // Only draft orders can be edited
|
const canEdit = order.status == 0; // Only draft orders can be edited
|
||||||
|
|
||||||
content.innerHTML = lines.map(line => `
|
content.innerHTML = lines.map(line => `
|
||||||
<div class="order-line ${canEdit ? 'editable' : ''}" data-line-id="${line.id}" data-order-id="${order.id}">
|
<div class="order-line ${canEdit ? 'editable' : ''}" data-line-id="${line.id}" data-order-id="${order.id}">
|
||||||
|
|
@ -1537,7 +1536,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="line-edit-buttons">
|
<div class="line-edit-buttons">
|
||||||
<button type="button" class="action-btn btn-danger" id="line-delete">Löschen</button>
|
<button type="button" class="action-btn btn-danger" id="line-delete">Löschen</button>
|
||||||
<button type="button" class="action-btn btn-secondary" id="line-cancel">Abbrechen</button>
|
|
||||||
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
|
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1549,8 +1547,6 @@
|
||||||
resumeScanner();
|
resumeScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('line-cancel').addEventListener('click', closeDialog);
|
|
||||||
|
|
||||||
document.getElementById('line-save').addEventListener('click', () => {
|
document.getElementById('line-save').addEventListener('click', () => {
|
||||||
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
|
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
|
||||||
const descInput = document.getElementById('line-desc-input');
|
const descInput = document.getElementById('line-desc-input');
|
||||||
|
|
|
||||||
4
pwa.php
4
pwa.php
|
|
@ -53,7 +53,7 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
<title>Barcode Scanner</title>
|
<title>Barcode Scanner</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="apple-touch-icon" href="img/icon-192.png">
|
<link rel="apple-touch-icon" href="img/icon-192.png">
|
||||||
<link rel="stylesheet" href="css/scanner.css">
|
<link rel="stylesheet" href="css/scanner.css?v=60">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -1280,6 +1280,6 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
|
||||||
<script src="<?php echo dol_buildpath('/handybarcodescanner/js/scanner.js', 1); ?>"></script>
|
<script src="<?php echo dol_buildpath('/handybarcodescanner/js/scanner.js', 1); ?>?v=60"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
5
sw.js
5
sw.js
|
|
@ -1,5 +1,5 @@
|
||||||
// Service Worker for HandyBarcodeScanner PWA
|
// Service Worker for HandyBarcodeScanner PWA
|
||||||
const CACHE_NAME = 'scanner-v5.5';
|
const CACHE_NAME = 'scanner-v6.0';
|
||||||
const ASSETS = [
|
const ASSETS = [
|
||||||
'pwa.php',
|
'pwa.php',
|
||||||
'css/scanner.css',
|
'css/scanner.css',
|
||||||
|
|
@ -36,7 +36,7 @@ self.addEventListener('activate', event => {
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
// Always fetch AJAX requests from network (include PWA auth endpoints)
|
// Always fetch AJAX requests from network
|
||||||
if (url.pathname.includes('/ajax/')) {
|
if (url.pathname.includes('/ajax/')) {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(event.request).catch(err => {
|
fetch(event.request).catch(err => {
|
||||||
|
|
@ -64,7 +64,6 @@ self.addEventListener('fetch', event => {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request).then(cached => {
|
caches.match(event.request).then(cached => {
|
||||||
return cached || fetch(event.request).then(response => {
|
return cached || fetch(event.request).then(response => {
|
||||||
// Cache successful responses
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const clone = response.clone();
|
const clone = response.clone();
|
||||||
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
|
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue