diff --git a/api/index.php b/api/index.php index 3ea6833..315a05f 100644 --- a/api/index.php +++ b/api/index.php @@ -1672,6 +1672,19 @@ function getStats(PDO $db): void { $originalExpiry = !empty($item['expiry_date']) ? strtotime($item['expiry_date']) : null; if (!empty($item['opened_at'])) { + // For conf unit: if all whole packages (no fraction), the opened_at tracks when the + // last package was first used, but the remaining whole confs are still sealed. + // Use the original package expiry, not the opened shelf-life. + if ($item['unit'] === 'conf' && $originalExpiry !== null) { + $qty = (float)$item['quantity']; + $frac = round($qty - (float)(int)floor($qty + 0.001), 4); + if ($frac < 0.001) { + // All whole: treat as sealed โ€” use original expiry + $item['opened_expiry'] = $item['expiry_date'] ?? null; + $item['days_to_expiry'] = (int)round(($originalExpiry - $today) / 86400); + goto after_expiry; + } + } // Compute opened shelf-life using AI (with rule-based fallback + persistent cache). // The vacuum-sealed multiplier is already handled inside getOpenedShelfLifeDays. $openedDays = getOpenedShelfLifeDays($item['name'], $item['category'], $item['location'], (bool)$vacuum); @@ -1683,6 +1696,7 @@ function getStats(PDO $db): void { $item['opened_expiry'] = date('Y-m-d', $finalExpiry); $item['days_to_expiry'] = (int)round(($finalExpiry - $today) / 86400); } else { + after_expiry: // Legacy: no opened_at, use stored expiry_date as-is $item['opened_expiry'] = $item['expiry_date'] ?? null; $item['days_to_expiry'] = $originalExpiry !== null diff --git a/assets/js/app.js b/assets/js/app.js index 8af800d..2f1a433 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2293,9 +2293,7 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60, const avgAnnualKg = Math.round(bm.avgKgMonth * 12); const annualInfo = t('antiwaste.annual_info') .replace('{you}', myAnnualKg) - .replace('{avg}', avgAnnualKg) - .replace('{min}', bm.rangeMin) - .replace('{max}', bm.rangeMax); + .replace('{avg}', avgAnnualKg); // Build all badge objects (shown 4 at a time, rotated every 5 min) const diffPct = avgRate - myRate; diff --git a/translations/de.json b/translations/de.json index ba5a540..81221ca 100644 --- a/translations/de.json +++ b/translations/de.json @@ -700,7 +700,7 @@ "live_on": "Live-Daten", "live_off": "Offline", "meals": "Mahlzeiten", - "annual_info": "๐Ÿ“… Du ~{you} kg/Jahr ยท ร˜ ~{avg} kg/Jahr ยท Spanne {min}โ€“{max}%", + "annual_info": "๐Ÿ“… Du ~{you} kg/Jahr ยท ร˜ ~{avg} kg/Jahr", "badge_rate": "Verlustquote", "badge_saved_money": "gespart vs ร˜", "badge_wasted": "verloren", diff --git a/translations/en.json b/translations/en.json index cdef7ce..3b4cb5f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -699,7 +699,7 @@ "live_on": "Live data", "live_off": "Offline", "meals": "meals", - "annual_info": "๐Ÿ“… You ~{you} kg/yr ยท avg ~{avg} kg/yr ยท range {min}โ€“{max}%", + "annual_info": "๐Ÿ“… You ~{you} kg/yr ยท avg ~{avg} kg/yr", "badge_rate": "loss rate", "badge_saved_money": "saved vs avg", "badge_wasted": "items lost", diff --git a/translations/it.json b/translations/it.json index 77e22b5..2f8d458 100644 --- a/translations/it.json +++ b/translations/it.json @@ -699,7 +699,7 @@ "live_on": "Dati in tempo reale", "live_off": "Offline", "meals": "pasti", - "annual_info": "๐Ÿ“… Tu ~{you} kg/anno ยท media ~{avg} kg/anno ยท intervallo {min}โ€“{max}%", + "annual_info": "๐Ÿ“… Tu ~{you} kg/anno ยท media ~{avg} kg/anno", "badge_rate": "tasso perdita", "badge_saved_money": "risparmio vs media", "badge_wasted": "prod. persi",