Anti-waste: themed border, rich info badges, fix latte di montagna shelf-life, exclude opened from expiring_soon
This commit is contained in:
+3
-1
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
+18
-6
@@ -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-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 {
|
||||
|
||||
+30
-5
@@ -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 ? `<span class="aw-tc-arrow ${a.cls}">${a.sym}</span>` : '';
|
||||
|
||||
// Badges
|
||||
// Badges — richer info chips
|
||||
const diffPct = avgRate - myRate; // positive = you're better
|
||||
const badges = [];
|
||||
if (savedMoney > 0) badges.push(`<span class="aw-badge aw-badge-money">💰 ${bm.currency}${savedMoney}</span>`);
|
||||
if (savedMeals > 0) badges.push(`<span class="aw-badge aw-badge-meals">🥗 ${savedMeals} ${t('antiwaste.meals')}</span>`);
|
||||
if (savedCO2 > 0) badges.push(`<span class="aw-badge aw-badge-co2">🌍 −${savedCO2} kg CO₂</span>`);
|
||||
// Always show your waste rate vs avg
|
||||
badges.push(`<span class="aw-badge aw-badge-rate">
|
||||
<span class="aw-badge-icon">📊</span>
|
||||
<span class="aw-badge-body"><b>${myRate}%</b><small>${t('antiwaste.badge_rate')}</small></span>
|
||||
</span>`);
|
||||
if (savedMoney > 0) badges.push(`<span class="aw-badge aw-badge-money">
|
||||
<span class="aw-badge-icon">💰</span>
|
||||
<span class="aw-badge-body"><b>${bm.currency}${savedMoney}/m</b><small>${t('antiwaste.badge_saved_money')}</small></span>
|
||||
</span>`);
|
||||
if (savedMeals > 0) badges.push(`<span class="aw-badge aw-badge-meals">
|
||||
<span class="aw-badge-icon">🥗</span>
|
||||
<span class="aw-badge-body"><b>${savedMeals}</b><small>${t('antiwaste.badge_meals')}</small></span>
|
||||
</span>`);
|
||||
if (savedCO2 > 0) badges.push(`<span class="aw-badge aw-badge-co2">
|
||||
<span class="aw-badge-icon">🌍</span>
|
||||
<span class="aw-badge-body"><b>−${savedCO2} kg</b><small>CO₂</small></span>
|
||||
</span>`);
|
||||
if (wasted30 > 0) badges.push(`<span class="aw-badge aw-badge-wasted">
|
||||
<span class="aw-badge-icon">🗑️</span>
|
||||
<span class="aw-badge-body"><b>${wasted30}</b><small>${t('antiwaste.badge_wasted')}</small></span>
|
||||
</span>`);
|
||||
if (diffPct > 0) badges.push(`<span class="aw-badge aw-badge-better">
|
||||
<span class="aw-badge-icon">✅</span>
|
||||
<span class="aw-badge-body"><b>−${diffPct}%</b><small>${t('antiwaste.badge_better')}</small></span>
|
||||
</span>`);
|
||||
|
||||
// Random starting fact
|
||||
const facts = AW_FACTS[_currentLang] || AW_FACTS['it'];
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user