Banner: adapt expired icon/color/title to safety level (non-alarmist)
- ok level (long-life/freezer safe): green banner, ✅ icon, 'Scaduto (ancora ok)' - warning level: amber banner, 👀 icon, 'Scaduto (controlla)' - danger level: unchanged red 🚫 banner - Added banner-expired-ok / banner-expired-warning CSS variants - Added expiry.expired_suffix_ok / expired_suffix_warning i18n keys (IT/EN/DE) - Updated README and CHANGELOG
This commit is contained in:
@@ -5,6 +5,16 @@ All notable changes to EverShelf will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased] - 2026-04-30
|
||||
|
||||
### Changed
|
||||
- **Non-alarmist expired banner** — Banner icon, CSS class, and title suffix now adapt to the `getExpiredSafety()` level:
|
||||
- `ok` (long-life products, freezer within margin): green banner, ✅ icon, "— Scaduto (ancora ok)"
|
||||
- `warning` (items that should be inspected): amber/yellow banner, 👀 icon, "— Scaduto (controlla)"
|
||||
- `danger` (raw meat, dairy, fish, etc.): unchanged red 🚫 banner and "— Scaduto!" title
|
||||
- Added `expiry.expired_suffix_ok` and `expiry.expired_suffix_warning` i18n keys to all three language files (IT/EN/DE)
|
||||
- Added `banner-expired-ok` and `banner-expired-warning` CSS variants (green / amber) in `style.css`
|
||||
|
||||
## [1.5.0] - 2026-04-28
|
||||
|
||||
### Added
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Recent i18n Updates
|
||||
## 🌍 Recent Updates
|
||||
|
||||
- **Non-alarmist expired banner** — The expired-product banner now adapts its icon, colour, and title to the actual safety level: green ✅ for long-life products that are still safe, amber 👀 for items that should be checked, and the original red 🚫 only for genuinely dangerous items (raw meat, dairy, fish). Low-risk products like canned tomatoes or pasta are no longer shown with a scary red banner.
|
||||
- Recipe and meal-plan labels now resolve at runtime from translations, preventing raw placeholders like `meal_types.*` and `meal_plan_types.*` from appearing in the UI.
|
||||
- Recipe generation now receives the selected app language (`it`/`en`/`de`) and enforces localized output in both streaming and non-streaming API flows.
|
||||
- Added missing shared error keys (`error.network`, `error.no_api_key`) across all language files to keep fallback/error toasts fully translated.
|
||||
@@ -64,7 +65,7 @@
|
||||
- **Opened products panel** — Tracks partially-used items; expiry is recalculated from the opening date using AI (Gemini) + per-category rule fallback; whole sealed packages always keep their original manufacturer expiry; conf items with mixed whole + fractional units are shown as two separate entries
|
||||
- **Freezer shelf-life** — Granular per-product estimates (USDA/EFSA): fish 120 d, poultry 270 d, whole red-meat cuts 365 d, mince 120 d, vegetables/fruit 270 d, generic 180 d; AI + cache still take priority over rules
|
||||
- **Safety ratings** — Smart assessment of expired product safety (by category and location); expired unsafe items shown with a red danger banner and "L'ho buttato" as the primary action
|
||||
- **Expired product banner** — Products that have passed their effective shelf-life (including opened-product reduced expiry) appear in the top notification banner with safety tip, danger styling for high-risk items, and a prominent discard action
|
||||
- **Expired product banner** — Products that have passed their effective shelf-life (including opened-product reduced expiry) appear in the top notification banner; icon, colour and title adapt to the actual safety level (✅ green for safe, 👀 amber to check, 🚫 red for danger); high-risk items get a prominent discard action
|
||||
- **Quick recipe bar** — One-tap recipe suggestion using expiring products
|
||||
- **Anomaly banner** — Scrollable banner with suspicious quantities and consumption prediction mismatches, with one-tap correction or inline edit
|
||||
- **Expired/expiring alerts** — Priority-sorted banner notifications for expired and soon-to-expire products with use, throw, edit, and dismiss actions
|
||||
|
||||
@@ -5641,6 +5641,28 @@ body {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
.alert-banner.banner-expired-ok {
|
||||
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
||||
border-color: #16a34a;
|
||||
}
|
||||
.banner-expired-ok .alert-banner-title {
|
||||
color: #14532d;
|
||||
}
|
||||
.banner-expired-ok .alert-banner-counter {
|
||||
color: #15803d;
|
||||
}
|
||||
.banner-expired-ok .banner-dot.active { background: #16a34a; }
|
||||
.alert-banner.banner-expired-warning {
|
||||
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
||||
border-color: #d97706;
|
||||
}
|
||||
.banner-expired-warning .alert-banner-title {
|
||||
color: #78350f;
|
||||
}
|
||||
.banner-expired-warning .alert-banner-counter {
|
||||
color: #92400e;
|
||||
}
|
||||
.banner-expired-warning .banner-dot.active { background: #d97706; }
|
||||
.alert-banner.banner-expired-danger {
|
||||
background: linear-gradient(135deg, #fca5a5 0%, #f87171 100%);
|
||||
border-color: #b91c1c;
|
||||
|
||||
+15
-4
@@ -2840,11 +2840,22 @@ function renderBannerItem() {
|
||||
? t('expiry.expired_today_long')
|
||||
: t('expiry.expired_ago_long').replace('{n}', item.days_expired);
|
||||
const safety = getExpiredSafety(item, item.days_expired);
|
||||
banner.className = safety.level === 'danger'
|
||||
? 'alert-banner banner-expired banner-expired-danger'
|
||||
: 'alert-banner banner-expired';
|
||||
if (safety.level === 'danger') {
|
||||
banner.className = 'alert-banner banner-expired banner-expired-danger';
|
||||
iconEl.textContent = '🚫';
|
||||
titleEl.textContent = `${item.name}${item.brand ? ' (' + item.brand + ')' : ''} ${t('expiry.expired_suffix')}`;
|
||||
} else if (safety.level === 'warning') {
|
||||
banner.className = 'alert-banner banner-expired banner-expired-warning';
|
||||
iconEl.textContent = '👀';
|
||||
} else {
|
||||
banner.className = 'alert-banner banner-expired banner-expired-ok';
|
||||
iconEl.textContent = '✅';
|
||||
}
|
||||
const expiredSuffix = safety.level === 'ok'
|
||||
? t('expiry.expired_suffix_ok')
|
||||
: safety.level === 'warning'
|
||||
? t('expiry.expired_suffix_warning')
|
||||
: t('expiry.expired_suffix');
|
||||
titleEl.textContent = `${item.name}${item.brand ? ' (' + item.brand + ')' : ''} ${expiredSuffix}`;
|
||||
const baseDetail = t('dashboard.banner_expired_detail').replace('{when}', daysText).replace('{qty}', qtyDisplay);
|
||||
detailEl.innerHTML = `${baseDetail} <span class="banner-safety-tip banner-safety-${safety.level}">${safety.icon} ${safety.tip}</span>`;
|
||||
let btns = '';
|
||||
|
||||
@@ -641,6 +641,8 @@
|
||||
"expired_today_long": "Heute abgelaufen",
|
||||
"expired_ago_long": "Seit {n} Tagen abgelaufen",
|
||||
"expired_suffix": "— Abgelaufen!",
|
||||
"expired_suffix_ok": "— Abgelaufen (noch ok)",
|
||||
"expired_suffix_warning": "— Abgelaufen (erst prüfen)",
|
||||
"days_compact": "{n}T"
|
||||
},
|
||||
"status": {
|
||||
|
||||
@@ -640,6 +640,8 @@
|
||||
"expired_today_long": "Expired today",
|
||||
"expired_ago_long": "Expired {n} days ago",
|
||||
"expired_suffix": "— Expired!",
|
||||
"expired_suffix_ok": "— Expired (still ok)",
|
||||
"expired_suffix_warning": "— Expired (check first)",
|
||||
"days_compact": "{n}d"
|
||||
},
|
||||
"status": {
|
||||
|
||||
@@ -640,6 +640,8 @@
|
||||
"expired_today_long": "Scaduto oggi",
|
||||
"expired_ago_long": "Scaduto da {n} giorni",
|
||||
"expired_suffix": "— Scaduto!",
|
||||
"expired_suffix_ok": "— Scaduto (ancora ok)",
|
||||
"expired_suffix_warning": "— Scaduto (controlla)",
|
||||
"days_compact": "{n}gg"
|
||||
},
|
||||
"status": {
|
||||
|
||||
Reference in New Issue
Block a user