feat: pagina Log con diario operazioni
- Nuova sezione 'Log' nella bottom nav con icona 📒 - Mostra tutte le transazioni (entrate/uscite) raggruppate per data - Ogni voce: icona 📥/📤, nome prodotto, marca, quantità, posizione, orario - Bordo verde per aggiunte, rosso per uscite - Paginazione con 'Carica altri...' (50 per pagina) - Backend: aggiunto supporto offset a listTransactions
This commit is contained in:
@@ -2094,6 +2094,69 @@ body {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ===== LOG PAGE ===== */
|
||||
.log-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 0 var(--space-sm);
|
||||
}
|
||||
|
||||
.log-date-header {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
text-transform: capitalize;
|
||||
padding: 12px 0 4px;
|
||||
border-bottom: 1px solid var(--bg-light);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.log-entry.log-in {
|
||||
border-left: 3px solid var(--success);
|
||||
}
|
||||
|
||||
.log-entry.log-out {
|
||||
border-left: 3px solid var(--danger);
|
||||
}
|
||||
|
||||
.log-icon {
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.log-product {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.log-product em {
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.log-detail {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.3;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* ===== AI IDENTIFICATION RESULTS ===== */
|
||||
.ai-identified-card {
|
||||
background: var(--bg-light);
|
||||
|
||||
@@ -365,6 +365,7 @@ function showPage(pageId, param = null) {
|
||||
case 'scan': initScanner(); clearQuickNameResults(); break;
|
||||
case 'products': loadAllProducts(); break;
|
||||
case 'shopping': loadShoppingList(); break;
|
||||
case 'log': loadLog(); break;
|
||||
case 'ai': initAICamera(); break;
|
||||
}
|
||||
|
||||
@@ -2626,6 +2627,69 @@ function showToast(message, type = '') {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ===== LOG =====
|
||||
let _logOffset = 0;
|
||||
const LOG_PAGE_SIZE = 50;
|
||||
|
||||
async function loadLog(more = false) {
|
||||
if (!more) {
|
||||
_logOffset = 0;
|
||||
document.getElementById('log-list').innerHTML = '<p style="text-align:center;color:var(--text-muted)">Caricamento...</p>';
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await api(`transactions_list&limit=${LOG_PAGE_SIZE}&offset=${_logOffset}`);
|
||||
const txns = result.transactions || [];
|
||||
|
||||
let html = '';
|
||||
if (!more && txns.length === 0) {
|
||||
html = '<p style="text-align:center;color:var(--text-muted)">Nessuna operazione registrata.</p>';
|
||||
} else {
|
||||
let lastDate = more ? '' : null;
|
||||
txns.forEach(t => {
|
||||
const dt = new Date(t.created_at + 'Z');
|
||||
const dateStr = dt.toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
|
||||
const timeStr = dt.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
if (dateStr !== lastDate) {
|
||||
html += `<div class="log-date-header">${dateStr}</div>`;
|
||||
lastDate = dateStr;
|
||||
}
|
||||
|
||||
const isIn = t.type === 'in';
|
||||
const icon = isIn ? '📥' : '📤';
|
||||
const typeLabel = isIn ? 'Aggiunto' : 'Usato';
|
||||
const colorClass = isIn ? 'log-in' : 'log-out';
|
||||
const brand = t.brand ? ` <em>(${t.brand})</em>` : '';
|
||||
const loc = t.location || '';
|
||||
const locLabels = { 'frigo': '🧊 Frigo', 'freezer': '❄️ Freezer', 'dispensa': '🗄️ Dispensa' };
|
||||
const locStr = locLabels[loc] || ('📍 ' + loc);
|
||||
|
||||
html += `<div class="log-entry ${colorClass}">`;
|
||||
html += `<span class="log-icon">${icon}</span>`;
|
||||
html += `<div class="log-info">`;
|
||||
html += `<div class="log-product"><strong>${t.name}</strong>${brand}</div>`;
|
||||
html += `<div class="log-detail">${typeLabel} ${t.quantity} ${t.unit || ''} · ${locStr} · ${timeStr}</div>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
if (more) {
|
||||
document.getElementById('log-list').insertAdjacentHTML('beforeend', html);
|
||||
} else {
|
||||
document.getElementById('log-list').innerHTML = html;
|
||||
}
|
||||
|
||||
_logOffset += txns.length;
|
||||
document.getElementById('log-load-more').style.display = txns.length >= LOG_PAGE_SIZE ? '' : 'none';
|
||||
|
||||
} catch (err) {
|
||||
console.error('Log load error:', err);
|
||||
if (!more) document.getElementById('log-list').innerHTML = '<p style="text-align:center;color:var(--danger)">Errore nel caricamento log</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RECIPE GENERATION =====
|
||||
function getMealType() {
|
||||
const hour = new Date().getHours();
|
||||
|
||||
Reference in New Issue
Block a user