From 2e46090adc56c6a55f79c5963428b1c57632159b Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Mon, 4 May 2026 18:30:30 +0000 Subject: [PATCH] fix(webapp): confirmation dialog + undo button visibility - Add _showDestructiveConfirm() helper: shows modal with 5-second auto-confirm countdown bar; user can confirm early or cancel - throwAll(): now shows confirmation before discarding all stock - submitUseAll(): same confirmation before marking all as used - undoTransactionEntry(): replace native confirm() with modal - Rename core logic to _doSubmitUseAll() / _doUndoTransaction() - btn-log-undo: more visible (red tint + larger font) so user can easily undo accidental operations from the history log - Bump app.js version to v=20260505a --- assets/css/style.css | 14 ++--- assets/js/app.js | 143 ++++++++++++++++++++++++++++++++++++------- index.html | 2 +- 3 files changed, 130 insertions(+), 29 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index a17f239..d33f731 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -4248,19 +4248,19 @@ body.cooking-mode-active .app-header { .btn-log-undo { flex-shrink: 0; - background: none; - border: 1px solid var(--border); - color: var(--text-muted); + background: rgba(248, 113, 113, 0.12); + border: 1px solid rgba(248, 113, 113, 0.4); + color: #f87171; border-radius: 6px; - padding: 3px 8px; - font-size: 0.85rem; + padding: 5px 10px; + font-size: 1rem; cursor: pointer; line-height: 1.4; transition: background 0.15s, color 0.15s; } .btn-log-undo:hover { - background: rgba(255,255,255,0.08); - color: var(--text); + background: rgba(248, 113, 113, 0.25); + color: #fca5a5; } diff --git a/assets/js/app.js b/assets/js/app.js index 826536f..97d7e3f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -5743,27 +5743,105 @@ function selectThrowLocation(btn, loc) { document.getElementById('throw-location').value = loc; } +/** + * Show a destructive-action confirmation modal with a 5-second auto-confirm countdown. + * The user can tap "Annulla" to cancel or "Conferma" (or wait) to proceed. + * @param {string} title — Modal title + * @param {string} msg — Explanatory text + * @param {Function} onConfirm — Called when confirmed (by user or countdown) + * @param {string} [confirmLabel] — Override confirm button label + */ +function _showDestructiveConfirm(title, msg, onConfirm, confirmLabel) { + const DURATION = 5000; + const btnLabel = confirmLabel || t('confirm.proceed') || 'Conferma'; + const cancelLabel = t('confirm.cancel') || 'Annulla'; + let rafHandle = null; + let timerHandle = null; + let resolved = false; + + const overlayEl = document.getElementById('modal-overlay'); + const contentEl = document.getElementById('modal-content'); + const confirmBtnId = '_destConfirmBtn_' + Date.now(); + const barId = '_destConfirmBar_' + Date.now(); + + contentEl.innerHTML = ` + +

${escapeHtml(msg)}

+
+
+
+
+ + +
+ `; + overlayEl.style.display = 'flex'; + + function cleanup() { + if (rafHandle) cancelAnimationFrame(rafHandle); + if (timerHandle) clearTimeout(timerHandle); + rafHandle = timerHandle = null; + } + function doConfirm() { + if (resolved) return; + resolved = true; + cleanup(); + closeModal(); + onConfirm(); + } + function doCancel() { + if (resolved) return; + resolved = true; + cleanup(); + closeModal(); + } + + document.getElementById(confirmBtnId).addEventListener('click', doConfirm); + document.getElementById('_destCancelBtn').addEventListener('click', doCancel); + + // Countdown animation + const barEl = document.getElementById(barId); + const start = performance.now(); + function tick() { + const pct = Math.min(100, (performance.now() - start) / DURATION * 100); + if (barEl) barEl.style.width = (100 - pct) + '%'; + if (pct < 100) { rafHandle = requestAnimationFrame(tick); } + } + rafHandle = requestAnimationFrame(tick); + timerHandle = setTimeout(doConfirm, DURATION); +} + async function throwAll() { closeModal(); - showLoading(true); - try { - const result = await api('inventory_use', {}, 'POST', { - product_id: currentProduct.id, - use_all: true, - location: '__all__', - notes: 'Buttato' - }); - showLoading(false); - if (result.success) { - showToast(t('toast.thrown_away', { name: currentProduct.name }), 'success'); - showPage('dashboard'); - } else { - showToast(result.error || 'Errore', 'error'); - } - } catch(e) { - showLoading(false); - showToast(t('error.connection'), 'error'); - } + const name = currentProduct ? currentProduct.name : ''; + _showDestructiveConfirm( + t('use.throw_all_confirm_title') || '🗑️ Butta tutto', + (t('use.throw_all_confirm_msg') || 'Vuoi davvero buttare via tutto il prodotto?') + (name ? `\n"${name}"` : ''), + async () => { + showLoading(true); + try { + const result = await api('inventory_use', {}, 'POST', { + product_id: currentProduct.id, + use_all: true, + location: '__all__', + notes: 'Buttato' + }); + showLoading(false); + if (result.success) { + showToast(t('toast.thrown_away', { name: currentProduct.name }), 'success'); + showPage('dashboard'); + } else { + showToast(result.error || 'Errore', 'error'); + } + } catch(e) { + showLoading(false); + showToast(t('error.connection'), 'error'); + } + }, + t('use.throw_all_confirm_btn') || '🗑️ Sì, butta' + ); } async function throwPartial() { @@ -7124,9 +7202,24 @@ async function confirmMoveAfterUse(productId, fromLoc, toLoc, openedId) { } async function submitUseAll() { + // Gate: show a countdown-confirmation before the destructive use_all call + const name = currentProduct ? currentProduct.name : ''; + const items0 = _useCurrentItems ? _useCurrentItems.filter(i => parseFloat(i.quantity) > 0) : []; + const totalQty = items0.reduce((s, i) => s + parseFloat(i.quantity || 0), 0); + const unit = items0[0]?.unit || 'pz'; + const qtyStr = formatQuantity(totalQty, unit, items0[0]?.default_quantity, items0[0]?.package_unit); + _showDestructiveConfirm( + t('use.use_all_confirm_title') || '✅ Finisci tutto', + `${t('use.use_all_confirm_msg') || 'Conferma che hai finito tutto il prodotto:'} "${name}" (${qtyStr})`, + _doSubmitUseAll, + t('use.use_all_confirm_btn') || '✅ Sì, finito' + ); +} + +async function _doSubmitUseAll() { showLoading(true); try { - const currentLoc = document.getElementById('use-location').value; + const currentLoc = document.getElementById('use-location')?.value || '__all__'; const items = _useCurrentItems.filter(i => parseFloat(i.quantity) > 0); const openedAtCurrentLoc = items.find(i => i.location === currentLoc && _isOpenedInventoryItem(i)); @@ -9372,7 +9465,15 @@ async function loadLog(more = false) { async function undoTransactionEntry(id, type, name) { const action = type === 'in' ? t('log.undo_action_remove') : t('log.undo_action_restore'); - if (!confirm(t('log.undo_confirm').replace('{action}', action).replace('{name}', name))) return; + const msg = t('log.undo_confirm').replace('{action}', action).replace('{name}', name); + _showDestructiveConfirm( + t('log.undo_title') || '↩ Annulla operazione', + msg, + () => _doUndoTransaction(id, type, name) + ); +} + +async function _doUndoTransaction(id, type, name) { try { const res = await api('transaction_undo', {}, 'POST', { id }); if (res.success) { diff --git a/index.html b/index.html index 97d7d83..5ca9030 100644 --- a/index.html +++ b/index.html @@ -1318,6 +1318,6 @@ - +