From 2abcec6fe5b38124ac2b0358401e792921bbd0f3 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Tue, 10 Mar 2026 18:20:31 +0000 Subject: [PATCH] feat: pagina Log con diario operazioni MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- api/index.php | 8 +++--- assets/css/style.css | 63 +++++++++++++++++++++++++++++++++++++++++++ assets/js/app.js | 64 ++++++++++++++++++++++++++++++++++++++++++++ index.html | 15 +++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-) diff --git a/api/index.php b/api/index.php index 1813945..3be07b4 100644 --- a/api/index.php +++ b/api/index.php @@ -532,7 +532,8 @@ function inventorySummary(PDO $db): void { // ===== TRANSACTION FUNCTIONS ===== function listTransactions(PDO $db): void { - $limit = $_GET['limit'] ?? 50; + $limit = (int)($_GET['limit'] ?? 50); + $offset = (int)($_GET['offset'] ?? 0); $productId = $_GET['product_id'] ?? ''; $query = " @@ -545,8 +546,9 @@ function listTransactions(PDO $db): void { $query .= " WHERE t.product_id = ?"; $params[] = $productId; } - $query .= " ORDER BY t.created_at DESC LIMIT ?"; - $params[] = (int)$limit; + $query .= " ORDER BY t.created_at DESC LIMIT ? OFFSET ?"; + $params[] = $limit; + $params[] = $offset; $stmt = $db->prepare($query); $stmt->execute($params); diff --git a/assets/css/style.css b/assets/css/style.css index 4fc9fc1..927a77a 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -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); diff --git a/assets/js/app.js b/assets/js/app.js index 66e9e6c..b51f1d8 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -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 = '

Caricamento...

'; + } + + 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 = '

Nessuna operazione registrata.

'; + } 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 += `
${dateStr}
`; + 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 ? ` (${t.brand})` : ''; + const loc = t.location || ''; + const locLabels = { 'frigo': '🧊 Frigo', 'freezer': '❄️ Freezer', 'dispensa': 'πŸ—„οΈ Dispensa' }; + const locStr = locLabels[loc] || ('πŸ“ ' + loc); + + html += `
`; + html += `${icon}`; + html += `
`; + html += `
${t.name}${brand}
`; + html += `
${typeLabel} ${t.quantity} ${t.unit || ''} Β· ${locStr} Β· ${timeStr}
`; + html += `
`; + html += `
`; + }); + } + + 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 = '

Errore nel caricamento log

'; + } +} + // ===== RECIPE GENERATION ===== function getMealType() { const hour = new Date().getHours(); diff --git a/index.html b/index.html index f2c3c3c..2a8fe36 100644 --- a/index.html +++ b/index.html @@ -473,6 +473,17 @@ + +
+ +
+ +
+ @@ -493,6 +504,10 @@ πŸ›’ Spesa +