Add 'Da revisionare' dashboard section for suspicious quantities

- Flags inventory items with abnormally small or large quantities
- Per-unit thresholds (pz/conf, g, kg, ml, l)
- Confirm button (✓) to mark as correct (stored in localStorage)
- Edit button (✏️) opens existing edit modal
- Smooth dismiss animation on confirm
- Amber-themed styling to distinguish from expiry alerts
This commit is contained in:
dadaloop82
2026-03-11 13:48:24 +00:00
parent c6d7c1f27d
commit ff1f27fe8d
4 changed files with 234 additions and 0 deletions
+121
View File
@@ -2579,6 +2579,127 @@ body {
font-size: 0.8rem;
}
/* ===== REVIEW SECTION ===== */
.alert-review {
background: #fffbeb;
border-color: #f59e0b;
}
.alert-review h3 {
color: #92400e;
}
.review-hint {
font-size: 0.8rem;
color: #92400e;
margin: -4px 0 10px;
opacity: 0.8;
}
.review-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: rgba(255,255,255,0.7);
border-radius: var(--radius-sm);
margin-bottom: 6px;
}
.review-item-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.review-item-icon {
font-size: 1.5rem;
flex-shrink: 0;
width: 36px;
text-align: center;
}
.review-item-icon img {
width: 36px;
height: 36px;
border-radius: 8px;
object-fit: cover;
}
.review-item-text {
min-width: 0;
}
.review-item-name {
font-weight: 600;
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.review-item-meta {
font-size: 0.75rem;
color: #78716c;
}
.review-warn {
color: #dc2626;
font-weight: 600;
}
.review-item-qty {
flex-shrink: 0;
}
.review-qty-value {
font-size: 1.1rem;
font-weight: 800;
color: #dc2626;
background: #fee2e2;
padding: 3px 10px;
border-radius: 16px;
white-space: nowrap;
}
.review-item-actions {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.btn-review {
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
font-size: 1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.btn-review-ok {
background: #d1fae5;
color: #059669;
}
.btn-review-ok:active {
background: #a7f3d0;
}
.btn-review-edit {
background: #e0e7ff;
color: #4338ca;
}
.btn-review-edit:active {
background: #c7d2fe;
}
/* ===== ALERT QUANTITY BADGES ===== */
.alert-item-qty {
font-size: 0.78rem;
+106
View File
@@ -619,11 +619,117 @@ async function loadDashboard() {
expiredSection.style.display = 'none';
}
// Review suspicious quantities
loadReviewItems();
} catch (err) {
console.error('Dashboard load error:', err);
}
}
// === SUSPICIOUS QUANTITY REVIEW ===
const QTY_THRESHOLDS = {
'pz': { min: 0.3, max: 50 },
'conf': { min: 0.3, max: 50 },
'g': { min: 3, max: 10000 },
'kg': { min: 0.005, max: 50 },
'ml': { min: 3, max: 10000 },
'l': { min: 0.005, max: 50 },
};
function isSuspiciousQty(qty, unit) {
const n = parseFloat(qty);
if (isNaN(n) || n <= 0) return false;
const t = QTY_THRESHOLDS[unit] || QTY_THRESHOLDS['pz'];
return n < t.min || n > t.max;
}
function getReviewConfirmed() {
try { return JSON.parse(localStorage.getItem('review_confirmed') || '{}'); } catch(e) { return {}; }
}
function setReviewConfirmed(inventoryId) {
const c = getReviewConfirmed();
c[inventoryId] = Date.now();
localStorage.setItem('review_confirmed', JSON.stringify(c));
}
async function loadReviewItems() {
const section = document.getElementById('alert-review');
const list = document.getElementById('review-list');
try {
const data = await api('inventory_list');
const items = data.inventory || [];
const confirmed = getReviewConfirmed();
const suspicious = items.filter(item => {
if (confirmed[item.id]) return false;
return isSuspiciousQty(item.quantity, item.unit);
});
if (suspicious.length === 0) {
section.style.display = 'none';
return;
}
section.style.display = 'block';
list.innerHTML = suspicious.map(item => {
const catIcon = CATEGORY_ICONS[mapToLocalCategory(item.category, item.name)] || '📦';
const qtyDisplay = formatQuantity(item.quantity, item.unit);
const locInfo = LOCATIONS[item.location] || { icon: '📦', label: item.location };
const t = QTY_THRESHOLDS[item.unit] || QTY_THRESHOLDS['pz'];
const isTooSmall = parseFloat(item.quantity) < t.min;
const warning = isTooSmall ? '⬇️ Troppo poco' : '⬆️ Troppo';
return `
<div class="review-item" id="review-item-${item.id}">
<div class="review-item-info">
<span class="review-item-icon">${item.image_url ? `<img src="${escapeHtml(item.image_url)}" alt="">` : catIcon}</span>
<div class="review-item-text">
<div class="review-item-name">${escapeHtml(item.name)}</div>
<div class="review-item-meta">${locInfo.icon} ${locInfo.label} · <span class="review-warn">${warning}</span></div>
</div>
</div>
<div class="review-item-qty">
<span class="review-qty-value">${qtyDisplay}</span>
</div>
<div class="review-item-actions">
<button class="btn-review btn-review-ok" onclick="confirmReviewItem(${item.id})" title="È corretto">✓</button>
<button class="btn-review btn-review-edit" onclick="editReviewItem(${item.id}, ${item.product_id})" title="Modifica">✏️</button>
</div>
</div>`;
}).join('');
} catch(e) {
section.style.display = 'none';
}
}
function confirmReviewItem(inventoryId) {
setReviewConfirmed(inventoryId);
const el = document.getElementById(`review-item-${inventoryId}`);
if (el) {
el.style.transition = 'opacity 0.3s, transform 0.3s';
el.style.opacity = '0';
el.style.transform = 'translateX(60px)';
setTimeout(() => {
el.remove();
// Hide section if empty
const list = document.getElementById('review-list');
if (!list.children.length) {
document.getElementById('alert-review').style.display = 'none';
}
}, 300);
}
showToast('✓ Quantità confermata', 'success');
}
function editReviewItem(inventoryId, productId) {
api('inventory_list').then(data => {
currentInventory = data.inventory || [];
showItemDetail(inventoryId, productId);
});
}
// Group items by local category and render with category headers
function renderGroupedByCategory(items, compact = false) {
const catGroups = {};
BIN
View File
Binary file not shown.
+7
View File
@@ -64,6 +64,13 @@
<div id="expiring-list"></div>
</div>
<!-- Review suspicious quantities -->
<div class="alert-section alert-review" id="alert-review" style="display:none">
<h3>🔍 Da revisionare</h3>
<p class="review-hint">Quantità che sembrano anomale. Conferma se corrette o modifica.</p>
<div id="review-list"></div>
</div>
</section>
<!-- ===== INVENTORY LIST ===== -->