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:
Eduard Wisch 2026-02-25 14:20:51 +01:00
parent 707d7f50a0
commit 08221a660d
7 changed files with 57 additions and 33 deletions

View file

@ -27,6 +27,18 @@
- **Kompakterer Scan-Button**: Mehr Platz für Tool-Buttons
- **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
### Bestellungen verwalten

View file

@ -98,7 +98,7 @@ echo json_encode([
'id' => $order->id,
'ref' => $order->ref,
'ref_supplier' => $order->ref_supplier,
'status' => $order->statut,
'status' => (int) $order->statut,
'supplier_name' => $order->thirdparty->name ?? '',
'total_ht' => (float) $order->total_ht
],

View file

@ -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'
// 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
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';

View file

@ -1075,16 +1075,10 @@
border: 1px solid var(--colorborder, #2b2c2e);
border-radius: 6px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
color: var(--colortext, #dcdcdc);
}
.order-line:hover {
background: var(--colorbacktitle, #3b3c3e);
border-color: var(--butactionbg, #ad8c4f);
}
.order-line-info {
flex: 1;
min-width: 0;
@ -1112,6 +1106,29 @@
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 {
position: fixed;

View file

@ -1184,30 +1184,32 @@
return;
}
const canEdit = order.status === 0;
const canEdit = order.status == 0;
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-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>
<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>
`).join('');
if (canEdit) {
content.querySelectorAll('.order-line').forEach(lineEl => {
lineEl.addEventListener('click', function() {
const lineId = parseInt(this.dataset.lineId);
const oId = parseInt(this.dataset.orderId);
const line = currentModalLines.find(l => l.id === lineId);
if (line) {
showLineEditDialogInModal(line, oId);
}
});
// Edit-Button Events
content.querySelectorAll('.order-line-edit-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const lineId = parseInt(this.dataset.lineId);
const line = currentModalLines.find(l => l.id === lineId);
if (line) {
showLineEditDialogInModal(line, orderId);
}
});
}
});
}
function showLineEditDialogInModal(line, orderId) {
@ -1237,7 +1239,6 @@
</div>
<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-secondary" id="line-cancel">Abbrechen</button>
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
</div>
</div>
@ -1249,8 +1250,6 @@
resumeScanner();
}
document.getElementById('line-cancel').addEventListener('click', closeDialog);
document.getElementById('line-save').addEventListener('click', () => {
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
const descInput = document.getElementById('line-desc-input');
@ -1483,7 +1482,7 @@
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 => `
<div class="order-line ${canEdit ? 'editable' : ''}" data-line-id="${line.id}" data-order-id="${order.id}">
@ -1537,7 +1536,6 @@
</div>
<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-secondary" id="line-cancel">Abbrechen</button>
<button type="button" class="action-btn btn-primary" id="line-save">Speichern</button>
</div>
</div>
@ -1549,8 +1547,6 @@
resumeScanner();
}
document.getElementById('line-cancel').addEventListener('click', closeDialog);
document.getElementById('line-save').addEventListener('click', () => {
const newQty = parseFloat(document.getElementById('line-qty-input').value) || 1;
const descInput = document.getElementById('line-desc-input');

View file

@ -53,7 +53,7 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
<title>Barcode Scanner</title>
<link rel="manifest" href="manifest.json">
<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>
* {
box-sizing: border-box;
@ -1280,6 +1280,6 @@ $colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
</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="<?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>
</html>

5
sw.js
View file

@ -1,5 +1,5 @@
// Service Worker for HandyBarcodeScanner PWA
const CACHE_NAME = 'scanner-v5.5';
const CACHE_NAME = 'scanner-v6.0';
const ASSETS = [
'pwa.php',
'css/scanner.css',
@ -36,7 +36,7 @@ self.addEventListener('activate', event => {
self.addEventListener('fetch', event => {
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/')) {
event.respondWith(
fetch(event.request).catch(err => {
@ -64,7 +64,6 @@ self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request).then(response => {
// Cache successful responses
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));