From 7c4dd992891db51a8445e37c9595e5c0a6b2d398 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Wed, 29 Apr 2026 06:19:35 +0000 Subject: [PATCH] Anti-waste: themed border, rich info badges, fix latte di montagna shelf-life, exclude opened from expiring_soon --- api/database.php | 4 +++- api/index.php | 1 + assets/css/style.css | 30 +++++++++++++++++++++--------- assets/js/app.js | 35 ++++++++++++++++++++++++++++++----- translations/de.json | 7 ++++++- translations/en.json | 7 ++++++- translations/it.json | 7 ++++++- 7 files changed, 73 insertions(+), 18 deletions(-) diff --git a/api/database.php b/api/database.php index fe3f6f7..19efb2a 100644 --- a/api/database.php +++ b/api/database.php @@ -269,7 +269,9 @@ function estimateOpenedExpiryDaysPHP(string $name, string $category, string $loc // ── F: Fridge — short-life perishables ────────────────────────────── if (preg_match('/latte\s+(fresco|intero|parzial|scremato)/', $n)) return 3; - if (preg_match('/latte\s+(uht|a\s+lunga)/', $n)) return 5; + if (preg_match('/latte\s+(uht|a\s+lunga)/', $n)) return 7; + // Long-life mountain/brand milks stored in pantry before use (UHT) + if (preg_match('/latte.*(montagna|alta\s+qual|parmalat|granarolo|esselunga|conservaz|microfiltrat)/i', $n)) return 7; if (preg_match('/\blatte\b/', $n)) return 4; if (preg_match('/\byogurt\b/', $n)) return 5; if (preg_match('/mozzarella|burrata|stracciatella/', $n)) return 3; diff --git a/api/index.php b/api/index.php index 2104a27..240b8c0 100644 --- a/api/index.php +++ b/api/index.php @@ -1529,6 +1529,7 @@ function getStats(PDO $db): void { COALESCE(i.vacuum_sealed, 0) as vacuum_sealed FROM inventory i JOIN products p ON i.product_id = p.id WHERE i.expiry_date IS NOT NULL AND i.expiry_date >= date('now') AND i.quantity > 0 + AND (i.opened_at IS NULL OR i.opened_at = '') ORDER BY i.expiry_date ASC LIMIT 4 ")->fetchAll(); diff --git a/assets/css/style.css b/assets/css/style.css index 4485a0e..2a99489 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -378,8 +378,9 @@ body { /* ── Anti-Waste Report Card ─────────────────────────────── */ #waste-chart-section { - background: var(--bg-card); - border: 1px solid var(--border); + background: linear-gradient(160deg, #f0fdf4 0%, var(--bg-card) 70%); + border: 2px solid #86efac; + border-left: 4px solid var(--success); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 10px; @@ -498,16 +499,27 @@ body { .aw-badge { display: inline-flex; align-items: center; - gap: 4px; - padding: 2px 8px; - border-radius: 999px; - font-size: 0.7rem; + gap: 5px; + padding: 4px 9px; + border-radius: 10px; + font-size: 0.72rem; font-weight: 600; border: 1px solid transparent; } -.aw-badge-money { background: #fef9c3; color: #854d0e; border-color: #fde047; } -.aw-badge-meals { background: #f0fdf4; color: #166534; border-color: #86efac; } -.aw-badge-co2 { background: #eff6ff; color: #1e40af; border-color: #93c5fd; } +.aw-badge-icon { font-size: 0.85rem; flex-shrink: 0; } +.aw-badge-body { + display: flex; + flex-direction: column; + line-height: 1.2; +} +.aw-badge-body b { font-size: 0.78rem; } +.aw-badge-body small { font-size: 0.6rem; font-weight: 500; opacity: .75; } +.aw-badge-rate { background: #f5f3ff; color: #5b21b6; border-color: #c4b5fd; } +.aw-badge-money { background: #fef9c3; color: #854d0e; border-color: #fde047; } +.aw-badge-meals { background: #f0fdf4; color: #166534; border-color: #86efac; } +.aw-badge-co2 { background: #eff6ff; color: #1e40af; border-color: #93c5fd; } +.aw-badge-wasted { background: #fef2f2; color: #991b1b; border-color: #fca5a5; } +.aw-badge-better { background: #f0fdf4; color: #15803d; border-color: #4ade80; } /* ── Trend mini-cards ───────────────────────────────────── */ .aw-trend-cards { diff --git a/assets/js/app.js b/assets/js/app.js index a68d1a9..62270a9 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1337,7 +1337,9 @@ function estimateOpenedExpiryDays(product, location) { } if (/latte\s+(fresco|intero|parzial|scremato)/.test(name)) return 3; - if (/latte\s+(uht|a\s+lunga)/.test(name)) return 5; + if (/latte\s+(uht|a\s+lunga)/.test(name)) return 7; + // Long-life mountain/brand milks stored in pantry before use (UHT) + if (/latte.*(montagna|alta\s+qual|parmalat|granarolo|esselunga|conservaz|microfiltrat)/i.test(name)) return 7; if (/\blatte\b/.test(name)) return 4; if (/\byogurt\b/.test(name)) return 5; if (/mozzarella|burrata|stracciatella/.test(name)) return 3; @@ -2234,11 +2236,34 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60, const arr2 = _awTrendArrow(rates[1], rates[2]); const arrowHtml = a => a ? `${a.sym}` : ''; - // Badges + // Badges — richer info chips + const diffPct = avgRate - myRate; // positive = you're better const badges = []; - if (savedMoney > 0) badges.push(`💰 ${bm.currency}${savedMoney}`); - if (savedMeals > 0) badges.push(`🥗 ${savedMeals} ${t('antiwaste.meals')}`); - if (savedCO2 > 0) badges.push(`🌍 −${savedCO2} kg CO₂`); + // Always show your waste rate vs avg + badges.push(` + 📊 + ${myRate}%${t('antiwaste.badge_rate')} + `); + if (savedMoney > 0) badges.push(` + 💰 + ${bm.currency}${savedMoney}/m${t('antiwaste.badge_saved_money')} + `); + if (savedMeals > 0) badges.push(` + 🥗 + ${savedMeals}${t('antiwaste.badge_meals')} + `); + if (savedCO2 > 0) badges.push(` + 🌍 + −${savedCO2} kgCO₂ + `); + if (wasted30 > 0) badges.push(` + 🗑️ + ${wasted30}${t('antiwaste.badge_wasted')} + `); + if (diffPct > 0) badges.push(` + + −${diffPct}%${t('antiwaste.badge_better')} + `); // Random starting fact const facts = AW_FACTS[_currentLang] || AW_FACTS['it']; diff --git a/translations/de.json b/translations/de.json index fda69d2..00eb1a5 100644 --- a/translations/de.json +++ b/translations/de.json @@ -696,7 +696,12 @@ "source": "Quellen: REDUCE, Eurostat, USDA 2021", "live_on": "Live-Daten", "live_off": "Offline", - "meals": "Mahlzeiten" + "meals": "Mahlzeiten", + "badge_rate": "Abfallquote", + "badge_saved_money": "gespart vs Ø", + "badge_meals": "Mahlzeiten gerettet", + "badge_wasted": "verschwendet", + "badge_better": "weniger als Ø" }, "error": { "generic": "Fehler", diff --git a/translations/en.json b/translations/en.json index 526f861..ccfe85d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -696,7 +696,12 @@ "source": "Sources: REDUCE, Eurostat, USDA 2021", "live_on": "Live data", "live_off": "Offline", - "meals": "meals" + "meals": "meals", + "badge_rate": "waste rate", + "badge_saved_money": "saved vs avg", + "badge_meals": "meals saved", + "badge_wasted": "items wasted", + "badge_better": "less than avg" }, "error": { "generic": "Error", diff --git a/translations/it.json b/translations/it.json index 0cae976..2c6b36f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -696,7 +696,12 @@ "source": "Fonti: REDUCE, Eurostat, USDA 2021", "live_on": "Dati in tempo reale", "live_off": "Offline", - "meals": "pasti" + "meals": "pasti", + "badge_rate": "tasso spreco", + "badge_saved_money": "risparmio vs media", + "badge_meals": "pasti salvati", + "badge_wasted": "prod. sprecati", + "badge_better": "in meno vs media" }, "error": { "generic": "Errore",