From 30954434201d9772f1cb49c7210ef40c5b71ffe3 Mon Sep 17 00:00:00 2001 From: max Date: Sat, 10 Oct 2026 23:45:59 +0200 Subject: [PATCH] feat(ui): optimize interface for small tablet with Main-Scan-Manage navigation --- public/app.js | 193 ++++++++++++++------------ public/index.html | 345 +++++++++++++++++++++++++--------------------- public/styles.css | 28 ++-- 3 files changed, 306 insertions(+), 260 deletions(-) diff --git a/public/app.js b/public/app.js index 8c06154..bc51368 100644 --- a/public/app.js +++ b/public/app.js @@ -1,10 +1,10 @@ /** * Fridger Frontend Application - * Simplified UI: Fridge View (drawer-free) and 3 primary actions: Manage Grams, Add Pack, Remove Pack. + * Tablet-optimized UI: Main -> Scan -> Manage navigation and 3-button product management. */ const state = { - currentMode: 'view', // 'view' or 'manage' + currentTab: 'main', // 'main' or 'manage' items: [], meta: { categories: [], @@ -19,8 +19,7 @@ const state = { activeGramsItem: null, activeGramsValue: 0, html5QrCode: null, - isScanning: false, - openedFromModal: false + isScanning: false }; // Initialize Application @@ -88,25 +87,28 @@ async function loadItems() { } // ---------------------------------------------------- -// Mode Switching & Filtering +// Navigation Tab Switching (Main -> Scan -> Manage) // ---------------------------------------------------- -function switchMode(mode) { - state.currentMode = mode; +function switchTab(tab) { + state.currentTab = tab; - const viewBtn = document.getElementById('mode-view-btn'); - const manageBtn = document.getElementById('mode-manage-btn'); - const viewContainer = document.getElementById('view-mode-container'); - const manageContainer = document.getElementById('manage-mode-container'); + const mainBtn = document.getElementById('nav-main-btn'); + const manageBtn = document.getElementById('nav-manage-btn'); + const viewContainer = document.getElementById('view-tab-container'); + const manageContainer = document.getElementById('manage-tab-container'); - if (mode === 'view') { - viewBtn.className = 'flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition bg-teal-500 text-white shadow-md'; - manageBtn.className = 'flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition text-slate-300 hover:text-white'; + const activeClasses = 'flex items-center justify-center gap-2 px-3 sm:px-6 py-2.5 sm:py-3 rounded-xl text-xs sm:text-sm font-bold transition-all bg-teal-500 text-white shadow-md active:scale-95 touch-manipulation min-h-[44px]'; + const inactiveClasses = 'flex items-center justify-center gap-2 px-3 sm:px-6 py-2.5 sm:py-3 rounded-xl text-xs sm:text-sm font-bold transition-all text-slate-300 hover:text-white hover:bg-slate-800/80 active:scale-95 touch-manipulation min-h-[44px]'; + + if (tab === 'main') { + mainBtn.className = activeClasses; + manageBtn.className = inactiveClasses; viewContainer.classList.remove('hidden'); manageContainer.classList.add('hidden'); - } else { - manageBtn.className = 'flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition bg-teal-500 text-white shadow-md'; - viewBtn.className = 'flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition text-slate-300 hover:text-white'; + } else if (tab === 'manage') { + manageBtn.className = activeClasses; + mainBtn.className = inactiveClasses; manageContainer.classList.remove('hidden'); viewContainer.classList.add('hidden'); } @@ -141,9 +143,9 @@ function setStatusFilter(status) { state.filters.status = status; document.querySelectorAll('[data-status-btn]').forEach(btn => { if (btn.getAttribute('data-status-btn') === status) { - btn.className = 'status-chip active px-3 py-1.5 rounded-lg bg-slate-800 border border-slate-700 text-white font-semibold'; + btn.className = 'status-chip active px-3.5 py-2 rounded-xl bg-slate-800 border border-slate-700 text-white font-bold min-h-[38px] touch-manipulation'; } else { - btn.className = 'status-chip px-3 py-1.5 rounded-lg bg-slate-900 border border-slate-800 text-slate-400 hover:text-slate-200'; + btn.className = 'status-chip px-3.5 py-2 rounded-xl bg-slate-900 border border-slate-800 text-slate-400 hover:text-slate-200 min-h-[38px] touch-manipulation'; } }); loadItems(); @@ -160,9 +162,9 @@ function renderCategoryFilters() { if (!container) return; let html = ` - - - @@ -303,7 +305,7 @@ function renderFridgeView() { }).join(''); } -// MODE 2: Manage Mode (Simplified list with the 3 main buttons) +// MANAGE TAB: Manage Products Interface (Optimized for Tablet with Large Controls) function renderManageView() { const container = document.getElementById('manage-items-list'); if (!container) return; @@ -315,61 +317,73 @@ function renderManageView() { : 100; return ` -
+
-
+
- -
+ +
${item.image_url ? ` - ${escapeHtml(item.name)} + ${escapeHtml(item.name)} ` : ` -
- +
+
`}
-

${escapeHtml(item.name)}

- ${item.brand ? `${escapeHtml(item.brand)}` : ''} +

${escapeHtml(item.name)}

+ ${item.brand ? `${escapeHtml(item.brand)}` : ''}
-
- ${item.quantity} ${item.unit} +
+ ${item.quantity} ${item.unit} (Pack size: ${item.initial_quantity} ${item.unit}) • - ${expiryInfo.label} + ${expiryInfo.label}
- -
+ +
- - - - - -
@@ -382,7 +396,7 @@ function renderManageView() { } // ---------------------------------------------------- -// 3 Primary Action Handlers: Add Pack, Remove Pack, Manage Grams +// 3 Primary Action Handlers // ---------------------------------------------------- async function addWholePacket(itemId) { @@ -395,7 +409,7 @@ async function addWholePacket(itemId) { if (res.ok) { const updated = await res.json(); - showToast(`Added 1 packet of ${updated.name} (Now: ${updated.quantity} ${updated.unit})`, 'success'); + showToast(`+1 Pack of ${updated.name} (Total: ${updated.quantity} ${updated.unit})`, 'success'); await loadStats(); await loadItems(); } else { @@ -417,7 +431,7 @@ async function removeWholePacket(itemId) { if (res.ok) { const updated = await res.json(); - showToast(`Removed 1 packet of ${updated.name} (Remaining: ${updated.quantity} ${updated.unit})`, 'info'); + showToast(`-1 Pack of ${updated.name} (Remaining: ${updated.quantity} ${updated.unit})`, 'info'); await loadStats(); await loadItems(); } else { @@ -520,7 +534,7 @@ async function saveGramsAdjustment() { showToast('Failed to update quantity', 'error'); } } catch (err) { - console.error(err); + console.error('Save grams error:', err); showToast('Network error while updating quantity', 'error'); } } @@ -559,7 +573,7 @@ function openItemModal(itemId = null) { if (!item) return; document.getElementById('modal-item-id').value = item.id; - document.getElementById('item-modal-title').textContent = 'Edit Item'; + document.getElementById('item-modal-title').textContent = 'Edit Product'; document.getElementById('modal-barcode').value = item.barcode || ''; document.getElementById('modal-name').value = item.name || ''; document.getElementById('modal-brand').value = item.brand || ''; @@ -578,7 +592,7 @@ function openItemModal(itemId = null) { } } else { document.getElementById('modal-item-id').value = ''; - document.getElementById('item-modal-title').textContent = 'Add Item'; + document.getElementById('item-modal-title').textContent = 'Add Product'; document.getElementById('modal-unit').value = 'g'; document.getElementById('modal-quantity').value = ''; document.getElementById('modal-initial-quantity').value = ''; @@ -676,17 +690,17 @@ async function handleItemFormSubmit(event) { }); if (res.ok) { - showToast(itemId ? 'Item updated' : 'Item added to fridge', 'success'); + showToast(itemId ? 'Product updated' : 'Product added to fridge', 'success'); closeItemModal(); await loadStats(); await loadItems(); } else { const err = await res.json(); - showToast(err.error || 'Failed to save item', 'error'); + showToast(err.error || 'Failed to save product', 'error'); } } catch (err) { console.error(err); - showToast('Network error while saving item', 'error'); + showToast('Network error while saving product', 'error'); } } @@ -701,17 +715,16 @@ async function confirmDeleteItem(itemId, name) { } } catch (err) { console.error(err); - showToast('Failed to delete item', 'error'); + showToast('Failed to delete product', 'error'); } } } // ---------------------------------------------------- -// Live Camera Barcode Scanner +// Tablet Live Camera Barcode Scanner // ---------------------------------------------------- -function openScannerModal(fromModal = false) { - state.openedFromModal = fromModal; +function openScannerModal() { document.getElementById('scanner-modal').classList.remove('hidden'); document.getElementById('scanner-manual-input').value = ''; document.getElementById('scanner-status').textContent = 'Initializing camera...'; @@ -739,7 +752,7 @@ async function startCameraScanner() { state.html5QrCode = new Html5Qrcode("barcode-reader"); const config = { fps: 10, - qrbox: { width: 240, height: 150 }, + qrbox: { width: 260, height: 160 }, aspectRatio: 1.333 }; @@ -751,7 +764,7 @@ async function startCameraScanner() { ); state.isScanning = true; - document.getElementById('scanner-status').textContent = 'Point camera at barcode.'; + document.getElementById('scanner-status').textContent = 'Point tablet camera at barcode.'; } catch (err) { console.warn('Camera start error:', err); document.getElementById('scanner-status').textContent = 'Camera unavailable. Please enter barcode number below.'; @@ -898,7 +911,7 @@ function showToast(message, type = 'info') { info: 'bg-slate-800 text-slate-100 border-slate-700' }; - toast.className = `p-3 rounded-xl border shadow-xl text-xs font-medium flex items-center gap-2 transform transition-all duration-300 translate-y-2 opacity-0 pointer-events-auto ${colors[type] || colors.info}`; + toast.className = `p-3.5 rounded-2xl border shadow-2xl text-xs sm:text-sm font-semibold flex items-center gap-2 transform transition-all duration-300 translate-y-2 opacity-0 pointer-events-auto ${colors[type] || colors.info}`; toast.innerHTML = `${escapeHtml(message)}`; container.appendChild(toast); diff --git a/public/index.html b/public/index.html index 358dc9f..af5d3b6 100644 --- a/public/index.html +++ b/public/index.html @@ -18,6 +18,9 @@ 500: '#14b8a6', 600: '#0d9488', } + }, + screens: { + 'tablet': '640px', } } } @@ -32,81 +35,102 @@ - + - -
-
-
+ +
+
+
- +
-
+
🧊
-
-

Fridger

-

Fridge Manager

+
- -
- - + + + -
- -
- - -
+ + + +
- -
-
-
-
- - In Fridge: 0 + +
+
+
+
+ + In Fridge: 0
-
- +
+ Fresh: 0
-
- +
+ Expiring Soon: 0
-
- +
+ Opened: 0
- -
+ +
- +
-
-
- - - - - +
+ + + + +
-
- +
+
- -
+ +
- -
-
+ +
+
- -
- -