PWA: Merkzettel-Box auf Produktliste, Checkboxen-Fix, qty_done=1

- PWA Panel 2: Merkzettel-Notizen über der Produktliste anzeigen
  (wie auf Website stundenzettel_commande.php), mit Abhaken + Hinzufügen
- stundenzettel_commande.php: Grüne Haken entfernt, immer Checkboxen
- Produktübernahme: qty_done immer 1 statt Auftragsmenge
- Cache-Busting v2.7 → v2.8

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-01 19:48:25 +01:00
parent 3c9add6864
commit 8ea1180041
4 changed files with 149 additions and 12 deletions

View file

@ -685,6 +685,80 @@ body {
outline: none;
}
/* === Merkzettel-Box (Panel 2, wie stundenzettel_commande.php) === */
.merkzettel-box {
background: var(--colorbackcard);
border-left: 3px solid var(--warning);
border-radius: 10px;
padding: 12px 14px;
margin-bottom: 12px;
}
.merkzettel-box-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 14px;
}
.merkzettel-box-icon {
font-size: 16px;
}
.merkzettel-box .opac {
color: var(--colortextmuted);
font-weight: normal;
font-size: 12px;
}
.merkzettel-box-list {
list-style: none;
margin: 0;
padding: 0;
}
.merkzettel-box-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 6px 0;
}
.merkzettel-box-item .note-checkbox,
.merkzettel-box-item .note-checkbox-ro {
width: 22px;
height: 22px;
flex-shrink: 0;
font-size: 18px;
color: var(--colortextmuted);
}
.merkzettel-box-item .note-checkbox {
cursor: pointer;
}
.merkzettel-box-item .note-checkbox.checked {
color: var(--success);
}
.merkzettel-box-item .note-text {
flex: 1;
font-size: 14px;
word-break: break-word;
}
.merkzettel-box-item .note-text.checked {
text-decoration: line-through;
color: var(--colortextmuted);
}
.merkzettel-box-add {
display: flex;
gap: 8px;
margin-top: 8px;
}
.merkzettel-box-add input {
flex: 1;
padding: 8px 12px;
background: var(--colorbackinput);
border: 1px solid var(--colorborder);
border-radius: 8px;
color: var(--colortext);
font-size: 14px;
min-height: 40px;
outline: none;
}
/* === Tracking-Cards === */
.tracking-card {
background: var(--colorbackcard);

View file

@ -1058,6 +1058,52 @@
var canWrite = self.state.canWrite;
var hasSelectable = false;
var activeFilter = self.state.productFilter || 'open';
var isDraft = stz && stz.status == 0;
// Merkzettel-Notizen oben anzeigen (wie stundenzettel_commande.php)
if (self.data.notes && self.data.notes.length) {
html += '<div class="merkzettel-box">';
html += '<div class="merkzettel-box-header">';
html += '<span class="merkzettel-box-icon">&#128203;</span>';
html += '<strong>Merkzettel</strong>';
if (stz) html += ' <span class="opac">(' + self.escHtml(stz.ref) + ')</span>';
html += '</div>';
html += '<ul class="merkzettel-box-list">';
self.data.notes.forEach(function(n) {
var checked = n.checked == 1;
html += '<li class="merkzettel-box-item">';
if (isDraft && canWrite) {
html += '<span class="note-checkbox' + (checked ? ' checked' : '') + '" data-id="' + n.id + '" data-checked="' + (checked ? 0 : 1) + '">';
html += checked ? '&#9745;' : '&#9744;';
html += '</span>';
} else {
html += '<span class="note-checkbox-ro">' + (checked ? '&#9745;' : '&#9744;') + '</span>';
}
html += '<span class="' + (checked ? 'note-text checked' : 'note-text') + '">' + self.escHtml(n.note) + '</span>';
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">&#128203;</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>';
}
// Filter-Buttons (wie Desktop: Offen/Erledigt/Alle)
html += '<div class="filter-bar">';
@ -1248,6 +1294,13 @@
$panel.find('.btn-create-new-stz').on('click', function() {
self.showCreateStzDialog();
});
// Merkzettel auf Panel 2: Abhaken + Hinzufuegen
$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) {
@ -1664,6 +1717,21 @@
});
},
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) {

View file

@ -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.7">
<link rel="stylesheet" href="css/pwa.css?v=2.8">
<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.7"></script>
<script src="js/pwa.js?v=2.8"></script>
<!-- Service Worker Registration -->
<script>

View file

@ -175,13 +175,13 @@ if ($action == 'transfer_products' && $user->hasRight('stundenzettel', 'write'))
$sql2 .= " AND fk_commandedet = ".((int)$line_id);
$resql2 = $db->query($sql2);
if ($resql2 && $db->num_rows($resql2) == 0) {
// Noch nicht vorhanden, hinzufügen
// Noch nicht vorhanden, hinzufügen (immer mit Menge 1)
$stundenzettel->addProduct(
$obj->fk_product,
$obj->rowid,
null,
$obj->qty,
0,
1,
'order',
$obj->description // Beschreibung für Freitext-Produkte
);
@ -206,7 +206,7 @@ if ($action == 'transfer_products' && $user->hasRight('stundenzettel', 'write'))
$resqlMa = $db->query($sqlMa);
if ($resqlMa && ($objMa = $db->fetch_object($resqlMa))) {
$qty = max(1, (float)$objMa->qty_done); // Mindestens 1
$qty = 1; // Immer Menge 1 uebernehmen
// Prüfe ob Produkt schon auf diesem Stundenzettel (in Produktliste) existiert
$sqlCheck = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product";
@ -1865,14 +1865,9 @@ if ($tab == 'products') {
print '<tr class="oddeven'.$sectionClass.'">';
// Checkbox immer anzeigen, ausser wenn bereits auf diesem Stundenzettel
$isAlreadyOnStz = isset($alreadyOnStundenzettel[$obj->rowid]);
// Checkbox immer anzeigen
print '<td class="center"'.$styleFirst.'>';
if (!$isAlreadyOnStz) {
print '<input type="checkbox" name="selected[]" value="'.$obj->rowid.'" class="product-checkbox">';
} else {
print '<span class="fas fa-check" style="color: #28a745;" title="'.$langs->trans("AlreadyOnStundenzettel").'"></span>';
}
print '</td>';
// Produkt