From 2edd5a6ebdcce7f79c522e672e236717c3087989 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sat, 18 Apr 2026 18:56:27 +0000 Subject: [PATCH 1/3] fix(kiosk): skip debug keystore config when file does not exist (CI fix) --- evershelf-kiosk/app/build.gradle.kts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/evershelf-kiosk/app/build.gradle.kts b/evershelf-kiosk/app/build.gradle.kts index 7cfa508..f2e3b16 100644 --- a/evershelf-kiosk/app/build.gradle.kts +++ b/evershelf-kiosk/app/build.gradle.kts @@ -16,13 +16,17 @@ android { } signingConfigs { - // Use the standard Android debug keystore so every machine produces - // APKs with the same debug signature โ€” required for over-the-air updates. + // Use the standard Android debug keystore when building locally so the + // debug APK signature stays consistent across machines (needed for OTA updates). + // In CI the keystore doesn't exist โ€” fall back to Gradle's auto-generated key. getByName("debug") { - storeFile = file("${System.getProperty("user.home")}/.android/debug.keystore") - storePassword = "android" - keyAlias = "androiddebugkey" - keyPassword = "android" + val ks = file("${System.getProperty("user.home")}/.android/debug.keystore") + if (ks.exists()) { + storeFile = ks + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" + } } } From 3901113e76267bf9b5e83ee0473eca4440373c31 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sat, 18 Apr 2026 19:12:07 +0000 Subject: [PATCH 2/3] fix: Storico nav icon, recipe title in transaction notes and log display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.html: replace broken byte with ๐Ÿ“‹ emoji in Storico nav button - submitRecipeUse: pass 'Ricetta: ' as notes to inventory_use API so every ingredient use is linked to the recipe in the DB - loadLog: render recipe note as small italic line ๐Ÿณ below log-detail row - style.css: add .log-recipe-note style (0.75rem, muted, italic) --- assets/css/style.css | 7 +++++++ assets/js/app.js | 9 +++++++-- index.html | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index 2108892..9efb0c8 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -3905,6 +3905,13 @@ body { margin-left: 4px; } +.log-recipe-note { + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 2px; + font-style: italic; +} + .btn-log-undo { flex-shrink: 0; background: none; diff --git a/assets/js/app.js b/assets/js/app.js index 6eb308d..e1d8b2e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -8112,7 +8112,9 @@ async function loadLog(more = false) { const locLabels = { 'frigo': '๐ŸงŠ Frigo', 'freezer': 'โ„๏ธ Freezer', 'dispensa': '๐Ÿ—„๏ธ Dispensa' }; const locStr = t.type === 'bring' ? '' : (locLabels[loc] || ('๐Ÿ“ ' + loc)); const isAnnotation = (t.notes || '').includes('[Annullato]'); - const notes = t.notes && !isAnnotation ? ` ยท ${t.notes}` : ''; + const isRecipeNote = !isAnnotation && (t.notes || '').startsWith('Ricetta:'); + const notes = t.notes && !isAnnotation && !isRecipeNote ? ` ยท ${t.notes}` : ''; + const recipeNote = isRecipeNote ? `<div class="log-recipe-note">๐Ÿณ ${escapeHtml(t.notes)}</div>` : ''; const undone = t.undone == 1 || isAnnotation; // Can undo if within 24h, not already undone, not a bring entry, not a counter-transaction @@ -8124,6 +8126,7 @@ async function loadLog(more = false) { html += `<div class="log-info">`; html += `<div class="log-product"><strong>${escapeHtml(t.name)}</strong>${brand}${undone ? ' <span class="log-undone-badge">Annullato</span>' : ''}</div>`; html += `<div class="log-detail">${typeLabel} ${t.type !== 'bring' ? (t.quantity + ' ' + (t.unit || '')) + ' ยท ' : ''}${locStr}${notes} ยท ${timeStr}</div>`; + html += recipeNote; html += `</div>`; if (canUndo) { html += `<button class="btn-log-undo" onclick="undoTransactionEntry(${t.id}, '${escapeHtml(t.type)}', '${escapeHtml(t.name || '')}')" title="Annulla questa operazione">โ†ฉ</button>`; @@ -8769,11 +8772,13 @@ async function submitRecipeUse(useAll) { btn.textContent = 'โณ...'; try { + const recipeTitle = _cachedRecipe?.recipe?.title || ''; const result = await api('inventory_use', {}, 'POST', { product_id: productId, quantity: qty, use_all: useAll, - location: location + location: location, + notes: recipeTitle ? `Ricetta: ${recipeTitle}` : '', }); if (result.success) { diff --git a/index.html b/index.html index aae6762..a7a7a17 100644 --- a/index.html +++ b/index.html @@ -1144,7 +1144,7 @@ <span class="nav-label" data-i18n="nav.shopping">Spesa</span> </button> <button class="nav-btn" onclick="showPage('log')" data-page="log"> - <span class="nav-icon">๏ฟฝ</span> + <span class="nav-icon">๐Ÿ“‹</span> <span class="nav-label" data-i18n="nav.log">Storico</span> </button> <button class="nav-btn" onclick="showPage('settings')" data-page="settings"> From 1021f0473533d7b76bc09e6eaa9f3b1ad4121fde Mon Sep 17 00:00:00 2001 From: dadaloop82 <evershelfproject@gmail.com> Date: Sun, 19 Apr 2026 06:06:18 +0000 Subject: [PATCH 3/3] fix: smarter proactive shopping list urgency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PHP: predictive urgency block now scales by imminence: round(days_left) <= 3 โ†’ high, <= 7 โ†’ medium, <= 14 โ†’ low (was flat 'low' for any days_left <= 14) - PHP: also upgrades existing 'low' urgency to 'high' when imminent depletion detected (round(days_left) <= 3, isFrequent) - JS: autoAddCriticalItems now also adds: - high urgency items with pct_left < 20% (nearly empty) - high urgency items with days_left <= 3 (imminent) - any item with days_left <= 2 and uses_per_month >= 5 Result: Latte di Montagna (27.8x/mo, 3 days left) now appears on shopping list before running out, as do Lenticchie/Riso Basmati at 1% stock and Sandwich at 1 day left. --- api/index.php | 28 ++++++++++++++++++++++++---- assets/js/app.js | 16 +++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/api/index.php b/api/index.php index 24721c1..c2e6222 100644 --- a/api/index.php +++ b/api/index.php @@ -3349,11 +3349,31 @@ function smartShopping(PDO $db): void { $score += 40; } - // Frequently used but stock getting low (predictive) โ€” stricter thresholds + // Frequently used but stock getting low (predictive) โ€” scale urgency by imminence if ($urgency === 'none' && $dailyRate > 0 && $daysLeft <= 14 && $isFrequent && $isRecent) { - $urgency = 'low'; - $reasons[] = 'Previsto esaurimento tra ~' . round($daysLeft) . 'gg'; - $score += 25; + $daysLeftDisplay = (int)round($daysLeft); + $reasons[] = 'Finisce tra ~' . $daysLeftDisplay . 'gg'; + if ($daysLeftDisplay <= 3) { + // Running out within 3 days for a frequent product โ†’ high urgency + $urgency = 'high'; + $score += 70; + } elseif ($daysLeftDisplay <= 7) { + // Running out within a week โ†’ medium + $urgency = 'medium'; + $score += 45; + } else { + $urgency = 'low'; + $score += 25; + } + } + // Also upgrade existing low urgency when imminent depletion is detected + if ($urgency === 'low' && $dailyRate > 0 && (int)round($daysLeft) <= 3 && $isFrequent) { + $urgency = 'high'; + $daysLeftLbl = 'Finisce tra ~' . (int)round($daysLeft) . 'gg'; + if (!in_array($daysLeftLbl, $reasons)) { + $reasons[] = $daysLeftLbl; + } + $score += 45; } // Opened item with fast consumption โ€” only if actually used regularly diff --git a/assets/js/app.js b/assets/js/app.js index e1d8b2e..35ae9da 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -6725,11 +6725,17 @@ async function autoAddCriticalItems() { const lastRun = parseInt(localStorage.getItem('_autoAddedCriticalTs') || '0'); if (Date.now() - lastRun < 10 * 60 * 1000) return; localStorage.setItem('_autoAddedCriticalTs', String(Date.now())); - // Auto-add: critical urgency (always) + high urgency that are completely out of stock (qty=0) - const toAdd = smartShoppingItems.filter(i => - !i.on_bring && !_isBringPurchased(i.name, i.urgency) && - (i.urgency === 'critical' || (i.urgency === 'high' && i.current_qty === 0)) - ); + // Auto-add rules: + // - critical: always + // - high: when qty=0 OR pct_left<20 (almost gone) OR days_left<=3 (imminent) + // - any urgency with days_left<=2 and uses_per_month>=5 (running out tomorrow for heavy user) + const toAdd = smartShoppingItems.filter(i => { + if (i.on_bring || _isBringPurchased(i.name, i.urgency)) return false; + if (i.urgency === 'critical') return true; + if (i.urgency === 'high' && (i.current_qty === 0 || i.pct_left < 20 || i.days_left <= 3)) return true; + if (i.days_left <= 2 && (i.uses_per_month || 0) >= 5) return true; + return false; + }); if (toAdd.length === 0) return; const itemsToAdd = toAdd.map(i => ({ name: i.name, specification: _urgencyToSpec(i.urgency, i.brand) })); try {