Add safety badges for expired products
- 🗑️ Buttare (red): perishable items (latticini, carne, pesce, frutta, verdura) expired >2 days, or any items expired >6 months - 👀 Controlla (yellow): semi-perishable (pane, surgelati) within 30 days, or long-shelf items 1-6 months past, or fresh items just expired in frigo - ✅ OK (green): long-shelf items (pasta, conserve, condimenti, cereali, snack) within 30 days of TMC date - Tooltip on hover explains the safety recommendation - Added p.category to expired items SQL query - Safety logic uses mapToLocalCategory + location + days expired
This commit is contained in:
+1
-1
@@ -524,7 +524,7 @@ function getStats(PDO $db): void {
|
|||||||
|
|
||||||
// Expired
|
// Expired
|
||||||
$expired = $db->query("
|
$expired = $db->query("
|
||||||
SELECT i.*, p.name, p.brand
|
SELECT i.*, p.name, p.brand, p.category
|
||||||
FROM inventory i JOIN products p ON i.product_id = p.id
|
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')
|
WHERE i.expiry_date IS NOT NULL AND i.expiry_date < date('now')
|
||||||
ORDER BY i.expiry_date ASC
|
ORDER BY i.expiry_date ASC
|
||||||
|
|||||||
@@ -310,6 +310,39 @@ body {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Safety badges for expired items */
|
||||||
|
.alert-item-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safety-badge {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safety-badge.safety-danger {
|
||||||
|
background: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safety-badge.safety-warning {
|
||||||
|
background: #ffc107;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safety-badge.safety-ok {
|
||||||
|
background: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== SECTION CARD ===== */
|
/* ===== SECTION CARD ===== */
|
||||||
.section-card {
|
.section-card {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
|
|||||||
+56
-6
@@ -105,6 +105,52 @@ function guessCategoryFromName(name) {
|
|||||||
return 'altro';
|
return 'altro';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine safety level for expired products
|
||||||
|
// Returns { level: 'danger'|'warning'|'ok', icon, label, tip }
|
||||||
|
function getExpiredSafety(item, daysExpired) {
|
||||||
|
const cat = mapToLocalCategory(item.category || '', item.name || '');
|
||||||
|
const loc = (item.location || '').toLowerCase();
|
||||||
|
const name = (item.name || '').toLowerCase();
|
||||||
|
|
||||||
|
// HIGH RISK categories - perishable, can be dangerous
|
||||||
|
// Latticini freschi, carne, pesce, verdura, frutta
|
||||||
|
const highRisk = ['latticini', 'carne', 'pesce', 'verdura', 'frutta'];
|
||||||
|
// MEDIUM RISK - check before consuming
|
||||||
|
// Pane, surgelati, bevande (fresh juices, milk)
|
||||||
|
const medRisk = ['pane', 'surgelati'];
|
||||||
|
|
||||||
|
// Items in frigo are more perishable
|
||||||
|
const inFrigo = loc === 'frigo';
|
||||||
|
|
||||||
|
if (highRisk.includes(cat)) {
|
||||||
|
if (daysExpired <= 2 && inFrigo) {
|
||||||
|
return { level: 'warning', icon: '👀', label: 'Controlla', tip: 'Scaduto da poco, controlla odore e aspetto prima di consumare' };
|
||||||
|
}
|
||||||
|
return { level: 'danger', icon: '🗑️', label: 'Buttare', tip: 'Prodotto deperibile scaduto: da buttare per sicurezza' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (medRisk.includes(cat)) {
|
||||||
|
if (daysExpired <= 7) {
|
||||||
|
return { level: 'warning', icon: '👀', label: 'Controlla', tip: 'Controlla aspetto e odore prima di consumare' };
|
||||||
|
}
|
||||||
|
if (daysExpired <= 30) {
|
||||||
|
return { level: 'warning', icon: '👀', label: 'Controlla', tip: 'Scaduto da un po\', verificare bene prima dell\'uso' };
|
||||||
|
}
|
||||||
|
return { level: 'danger', icon: '🗑️', label: 'Buttare', tip: 'Troppo tempo dalla scadenza, meglio buttare' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW RISK - long shelf life items
|
||||||
|
// Pasta, conserve, condimenti, cereali, snack, bevande confezionate
|
||||||
|
// "Da consumarsi preferibilmente entro" = TMC, safe well past expiry
|
||||||
|
if (daysExpired <= 30) {
|
||||||
|
return { level: 'ok', icon: '✅', label: 'OK', tip: 'Prodotto a lunga conservazione, ancora sicuro da consumare' };
|
||||||
|
}
|
||||||
|
if (daysExpired <= 180) {
|
||||||
|
return { level: 'warning', icon: '👀', label: 'Controlla', tip: 'Scaduto da oltre un mese, controllare integrità confezione' };
|
||||||
|
}
|
||||||
|
return { level: 'danger', icon: '🗑️', label: 'Buttare', tip: 'Scaduto da troppo tempo, meglio non rischiare' };
|
||||||
|
}
|
||||||
|
|
||||||
// Nice Italian labels for local categories
|
// Nice Italian labels for local categories
|
||||||
const CATEGORY_LABELS = {
|
const CATEGORY_LABELS = {
|
||||||
'latticini': '🥛 Latticini', 'carne': '🥩 Carne', 'pesce': '🐟 Pesce',
|
'latticini': '🥛 Latticini', 'carne': '🥩 Carne', 'pesce': '🐟 Pesce',
|
||||||
@@ -355,17 +401,21 @@ async function loadDashboard() {
|
|||||||
expiredSection.style.display = 'block';
|
expiredSection.style.display = 'block';
|
||||||
expiredList.innerHTML = statsData.expired.map(item => {
|
expiredList.innerHTML = statsData.expired.map(item => {
|
||||||
const days = Math.abs(daysUntilExpiry(item.expiry_date));
|
const days = Math.abs(daysUntilExpiry(item.expiry_date));
|
||||||
let badgeText;
|
let daysText;
|
||||||
if (days === 0) badgeText = 'Oggi';
|
if (days === 0) daysText = 'Oggi';
|
||||||
else if (days === 1) badgeText = 'Da ieri';
|
else if (days === 1) daysText = 'Da ieri';
|
||||||
else badgeText = `Da ${days} giorni`;
|
else daysText = `Da ${days}g`;
|
||||||
|
const safety = getExpiredSafety(item, days);
|
||||||
return `
|
return `
|
||||||
<div class="alert-item">
|
<div class="alert-item expired-item">
|
||||||
<div class="alert-item-info">
|
<div class="alert-item-info">
|
||||||
<span class="alert-item-name">${escapeHtml(item.name)}</span>
|
<span class="alert-item-name">${escapeHtml(item.name)}</span>
|
||||||
${item.brand ? `<span class="alert-item-brand">${escapeHtml(item.brand)}</span>` : ''}
|
${item.brand ? `<span class="alert-item-brand">${escapeHtml(item.brand)}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<span class="alert-item-badge expired">${badgeText}</span>
|
<div class="alert-item-badges">
|
||||||
|
<span class="alert-item-badge expired">${daysText}</span>
|
||||||
|
<span class="safety-badge safety-${safety.level}" title="${safety.tip}">${safety.icon} ${safety.label}</span>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user