feat: banner opened items show 'aperto da X giorni in frigo' instead of 'scaduto'

When inventory item has opened_at set, the expired banner now shows:
- Title: '[Nome] — Aperto da troppo tempo!' (instead of '— Scaduto!')
- Detail: 'Aperto da N giorni in [icon] [location] · hai ancora X'
Also removed hardcoded Italian 'scade il' string from non-opened expired detail.
This commit is contained in:
dadaloop82
2026-05-11 17:11:07 +00:00
parent 85090ecc9f
commit 3391106010
5 changed files with 63 additions and 15 deletions
+41 -11
View File
@@ -3746,10 +3746,32 @@ function renderBannerItem() {
if (entry.type === 'expired') { if (entry.type === 'expired') {
const item = entry.data; const item = entry.data;
const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit); const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
const daysText = item.days_expired === 0 const isOpenedExpiry = !!item.opened_at;
const safety = getExpiredSafety(item, item.days_expired);
let daysText, suffix;
if (isOpenedExpiry) {
const todayMs = new Date(); todayMs.setHours(0, 0, 0, 0);
const daysSinceOpened = Math.round((todayMs - new Date(item.opened_at)) / 86400000);
daysText = daysSinceOpened === 0
? t('expiry.opened_today_long')
: t('expiry.opened_ago_long').replace('{n}', daysSinceOpened);
suffix = safety.level === 'ok'
? t('expiry.opened_suffix_ok')
: safety.level === 'warning'
? t('expiry.opened_suffix_warning')
: t('expiry.opened_suffix');
} else {
daysText = item.days_expired === 0
? t('expiry.expired_today_long') ? t('expiry.expired_today_long')
: t('expiry.expired_ago_long').replace('{n}', item.days_expired); : t('expiry.expired_ago_long').replace('{n}', item.days_expired);
const safety = getExpiredSafety(item, item.days_expired); suffix = safety.level === 'ok'
? t('expiry.expired_suffix_ok')
: safety.level === 'warning'
? t('expiry.expired_suffix_warning')
: t('expiry.expired_suffix');
}
if (safety.level === 'danger') { if (safety.level === 'danger') {
banner.className = 'alert-banner banner-expired banner-expired-danger'; banner.className = 'alert-banner banner-expired banner-expired-danger';
iconEl.textContent = '🚫'; iconEl.textContent = '🚫';
@@ -3760,16 +3782,24 @@ function renderBannerItem() {
banner.className = 'alert-banner banner-expired banner-expired-ok'; banner.className = 'alert-banner banner-expired banner-expired-ok';
iconEl.textContent = '✅'; iconEl.textContent = '✅';
} }
const expiredSuffix = safety.level === 'ok' titleEl.textContent = `${item.name}${item.brand ? ' (' + item.brand + ')' : ''} ${suffix}`;
? t('expiry.expired_suffix_ok')
: safety.level === 'warning' let baseDetail;
? t('expiry.expired_suffix_warning') if (isOpenedExpiry) {
: t('expiry.expired_suffix'); const locLabel = (LOCATIONS[item.location]
titleEl.textContent = `${item.name}${item.brand ? ' (' + item.brand + ')' : ''} ${expiredSuffix}`; ? LOCATIONS[item.location].icon + ' ' + LOCATIONS[item.location].label
const baseDetail = t('dashboard.banner_expired_detail').replace('{when}', daysText).replace('{qty}', qtyDisplay); : (item.location || ''));
baseDetail = t('dashboard.banner_opened_detail')
.replace('{when}', daysText)
.replace('{location}', escapeHtml(locLabel))
.replace('{qty}', qtyDisplay);
} else {
baseDetail = t('dashboard.banner_expired_detail').replace('{when}', daysText).replace('{qty}', qtyDisplay);
const locationTag = item.location ? ` · <strong>${escapeHtml(item.location)}</strong>` : ''; const locationTag = item.location ? ` · <strong>${escapeHtml(item.location)}</strong>` : '';
const expiryTag = item.expiry_date ? ` · scade il <strong>${escapeHtml(item.expiry_date)}</strong>` : ''; const expiryTag = item.expiry_date ? ` · ${escapeHtml(item.expiry_date)}` : '';
detailEl.innerHTML = `${baseDetail}${locationTag}${expiryTag} <span class="banner-safety-tip banner-safety-${safety.level}">${safety.icon} ${safety.tip}</span>`; baseDetail += locationTag + expiryTag;
}
detailEl.innerHTML = `${baseDetail} <span class="banner-safety-tip banner-safety-${safety.level}">${safety.icon} ${safety.tip}</span>`;
let btns = ''; let btns = '';
if (safety.level !== 'danger') { if (safety.level !== 'danger') {
btns += `<button class="btn-banner btn-banner-use" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`; btns += `<button class="btn-banner btn-banner-use" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`;
+2 -2
View File
@@ -11,7 +11,7 @@
<title>EverShelf</title> <title>EverShelf</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png"> <link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png">
<link rel="stylesheet" href="assets/css/style.css?v=20260511e"> <link rel="stylesheet" href="assets/css/style.css?v=20260511f">
<!-- QuaggaJS for barcode scanning --> <!-- QuaggaJS for barcode scanning -->
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
<!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise --> <!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise -->
@@ -1469,6 +1469,6 @@
</div> </div>
</div> </div>
<script src="assets/js/app.js?v=20260511e"></script> <script src="assets/js/app.js?v=20260511f"></script>
</body> </body>
</html> </html>
+6
View File
@@ -142,6 +142,7 @@
"wasted": "Weggeworfen: {n} ({pct}%)", "wasted": "Weggeworfen: {n} ({pct}%)",
"more_opened": "und {n} weitere geöffnet...", "more_opened": "und {n} weitere geöffnet...",
"banner_expired_detail": "{when} · du hast noch <strong>{qty}</strong>.", "banner_expired_detail": "{when} · du hast noch <strong>{qty}</strong>.",
"banner_opened_detail": "{when} in {location} · du hast noch <strong>{qty}</strong>.",
"banner_explain_title": "Gemini um eine Erklärung bitten", "banner_explain_title": "Gemini um eine Erklärung bitten",
"banner_explain_btn": "Erklären", "banner_explain_btn": "Erklären",
"banner_analyzing": "🤖 Analysiere…" "banner_analyzing": "🤖 Analysiere…"
@@ -693,6 +694,11 @@
"expired_suffix": "— Abgelaufen!", "expired_suffix": "— Abgelaufen!",
"expired_suffix_ok": "— Abgelaufen (noch ok)", "expired_suffix_ok": "— Abgelaufen (noch ok)",
"expired_suffix_warning": "— Abgelaufen (erst prüfen)", "expired_suffix_warning": "— Abgelaufen (erst prüfen)",
"opened_ago_long": "Seit {n} Tagen geöffnet",
"opened_today_long": "Heute geöffnet",
"opened_suffix": "— Zu lange geöffnet!",
"opened_suffix_ok": "— Geöffnet (noch ok)",
"opened_suffix_warning": "— Geöffnet (erst prüfen)",
"days_compact": "{n}T" "days_compact": "{n}T"
}, },
"status": { "status": {
+6
View File
@@ -142,6 +142,7 @@
"wasted": "Wasted: {n} ({pct}%)", "wasted": "Wasted: {n} ({pct}%)",
"more_opened": "and {n} more opened...", "more_opened": "and {n} more opened...",
"banner_expired_detail": "{when} · you still have <strong>{qty}</strong>.", "banner_expired_detail": "{when} · you still have <strong>{qty}</strong>.",
"banner_opened_detail": "{when} in {location} · you still have <strong>{qty}</strong>.",
"banner_explain_title": "Ask Gemini for an explanation", "banner_explain_title": "Ask Gemini for an explanation",
"banner_explain_btn": "Explain", "banner_explain_btn": "Explain",
"banner_analyzing": "🤖 Analyzing…" "banner_analyzing": "🤖 Analyzing…"
@@ -693,6 +694,11 @@
"expired_suffix": "— Expired!", "expired_suffix": "— Expired!",
"expired_suffix_ok": "— Expired (still ok)", "expired_suffix_ok": "— Expired (still ok)",
"expired_suffix_warning": "— Expired (check first)", "expired_suffix_warning": "— Expired (check first)",
"opened_ago_long": "Opened {n} days ago",
"opened_today_long": "Opened today",
"opened_suffix": "— Opened too long!",
"opened_suffix_ok": "— Opened (still ok)",
"opened_suffix_warning": "— Opened (check first)",
"days_compact": "{n}d" "days_compact": "{n}d"
}, },
"status": { "status": {
+6
View File
@@ -142,6 +142,7 @@
"wasted": "Buttati: {n} ({pct}%)", "wasted": "Buttati: {n} ({pct}%)",
"more_opened": "e altri {n} prodotti aperti...", "more_opened": "e altri {n} prodotti aperti...",
"banner_expired_detail": "{when} · hai ancora <strong>{qty}</strong>.", "banner_expired_detail": "{when} · hai ancora <strong>{qty}</strong>.",
"banner_opened_detail": "{when} in {location} · hai ancora <strong>{qty}</strong>.",
"banner_explain_title": "Chiedi a Gemini una spiegazione", "banner_explain_title": "Chiedi a Gemini una spiegazione",
"banner_explain_btn": "Spiega", "banner_explain_btn": "Spiega",
"banner_analyzing": "🤖 Analizzo…" "banner_analyzing": "🤖 Analizzo…"
@@ -693,6 +694,11 @@
"expired_suffix": "— Scaduto!", "expired_suffix": "— Scaduto!",
"expired_suffix_ok": "— Scaduto (ancora ok)", "expired_suffix_ok": "— Scaduto (ancora ok)",
"expired_suffix_warning": "— Scaduto (controlla)", "expired_suffix_warning": "— Scaduto (controlla)",
"opened_ago_long": "Aperto da {n} giorni",
"opened_today_long": "Aperto oggi",
"opened_suffix": "— Aperto da troppo tempo!",
"opened_suffix_ok": "— Aperto (ancora ok)",
"opened_suffix_warning": "— Aperto (controlla)",
"days_compact": "{n}gg" "days_compact": "{n}gg"
}, },
"status": { "status": {