feat(ui): optimize interface for small tablet with Main-Scan-Manage navigation
Build and Push Fridger Container / Build & Push Fridger Image (push) Successful in 1m23s
Build and Push Fridger Container / Build & Push Fridger Image (push) Successful in 1m23s
This commit is contained in:
+103
-90
@@ -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 = `
|
||||
<button onclick="setCategoryFilter('All')" class="px-2.5 py-1 rounded-lg border whitespace-nowrap transition text-xs ${
|
||||
<button onclick="setCategoryFilter('All')" class="px-3.5 py-2 rounded-xl border whitespace-nowrap transition text-xs sm:text-sm min-h-[38px] touch-manipulation ${
|
||||
state.filters.category === 'All'
|
||||
? 'bg-teal-500/20 text-teal-300 border-teal-500/40 font-semibold'
|
||||
? 'bg-teal-500/20 text-teal-300 border-teal-500/50 font-bold'
|
||||
: 'bg-slate-900 text-slate-400 border-slate-800 hover:text-slate-200'
|
||||
}">
|
||||
All Categories
|
||||
@@ -172,9 +174,9 @@ function renderCategoryFilters() {
|
||||
state.meta.categories.forEach(cat => {
|
||||
const isSelected = state.filters.category === cat.id;
|
||||
html += `
|
||||
<button onclick="setCategoryFilter('${escapeHtml(cat.id)}')" class="px-2.5 py-1 rounded-lg border whitespace-nowrap transition text-xs ${
|
||||
<button onclick="setCategoryFilter('${escapeHtml(cat.id)}')" class="px-3.5 py-2 rounded-xl border whitespace-nowrap transition text-xs sm:text-sm min-h-[38px] touch-manipulation ${
|
||||
isSelected
|
||||
? 'bg-teal-500/20 text-teal-300 border-teal-500/40 font-semibold'
|
||||
? 'bg-teal-500/20 text-teal-300 border-teal-500/50 font-bold'
|
||||
: 'bg-slate-900 text-slate-400 border-slate-800 hover:text-slate-200'
|
||||
}">
|
||||
${escapeHtml(cat.name)}
|
||||
@@ -195,7 +197,7 @@ function populateModalSelects() {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Rendering UI
|
||||
// UI Rendering
|
||||
// ----------------------------------------------------
|
||||
|
||||
function renderApp() {
|
||||
@@ -210,7 +212,7 @@ function renderApp() {
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
if (state.currentMode === 'view') {
|
||||
if (state.currentTab === 'main') {
|
||||
renderFridgeView();
|
||||
} else {
|
||||
renderManageView();
|
||||
@@ -219,7 +221,7 @@ function renderApp() {
|
||||
initLucide();
|
||||
}
|
||||
|
||||
// MODE 1: Fridge View Mode (No drawers/shelves - clean grid)
|
||||
// MAIN TAB: Fridge View (Tablet-optimized cards)
|
||||
function renderFridgeView() {
|
||||
const container = document.getElementById('fridge-view-content');
|
||||
if (!container) return;
|
||||
@@ -235,65 +237,65 @@ function renderFridgeView() {
|
||||
else if (percent <= 50) progressBarColor = 'bg-amber-500';
|
||||
|
||||
return `
|
||||
<div class="shelf-card rounded-2xl p-4 flex flex-col justify-between transition hover:border-teal-500/40 space-y-3">
|
||||
<div class="shelf-card rounded-2xl sm:rounded-3xl p-4 sm:p-5 flex flex-col justify-between transition hover:border-teal-500/40 space-y-3.5">
|
||||
|
||||
<div>
|
||||
<!-- Header: Brand & Expiry -->
|
||||
<div class="flex items-center justify-between gap-2 mb-2">
|
||||
<span class="text-[11px] font-semibold text-teal-400 bg-teal-500/10 px-2 py-0.5 rounded-md truncate max-w-[130px]">
|
||||
<span class="text-xs font-bold text-teal-400 bg-teal-500/10 px-2.5 py-1 rounded-lg truncate max-w-[140px]">
|
||||
${escapeHtml(item.brand || item.category)}
|
||||
</span>
|
||||
<span class="text-[11px] font-semibold px-2 py-0.5 rounded-md flex items-center gap-1 shrink-0 ${expiryInfo.badgeClass}">
|
||||
<i data-lucide="${expiryInfo.icon}" class="w-3 h-3"></i>
|
||||
<span class="text-xs font-bold px-2.5 py-1 rounded-lg flex items-center gap-1.5 shrink-0 ${expiryInfo.badgeClass}">
|
||||
<i data-lucide="${expiryInfo.icon}" class="w-3.5 h-3.5"></i>
|
||||
<span>${expiryInfo.label}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Product Details & Image -->
|
||||
<div class="flex items-start gap-3 my-1">
|
||||
<div class="flex items-center gap-3.5 my-2">
|
||||
${item.image_url ? `
|
||||
<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="w-12 h-12 object-contain rounded-xl bg-white/5 p-1 border border-slate-700/60 shrink-0">
|
||||
<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="w-14 h-14 object-contain rounded-2xl bg-white/5 p-1 border border-slate-700/60 shrink-0">
|
||||
` : `
|
||||
<div class="w-12 h-12 rounded-xl bg-slate-800/80 border border-slate-700 text-teal-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="${getCategoryIcon(item.category)}" class="w-6 h-6"></i>
|
||||
<div class="w-14 h-14 rounded-2xl bg-slate-800/80 border border-slate-700 text-teal-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="${getCategoryIcon(item.category)}" class="w-7 h-7"></i>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<h4 class="font-bold text-slate-100 text-sm leading-snug">${escapeHtml(item.name)}</h4>
|
||||
<h4 class="font-bold text-white text-base leading-snug line-clamp-2">${escapeHtml(item.name)}</h4>
|
||||
${item.notes ? `<p class="text-xs text-slate-400 italic mt-0.5 truncate">${escapeHtml(item.notes)}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grams / Quantity Progress -->
|
||||
<div class="mt-2.5 bg-slate-950/70 rounded-xl p-2.5 border border-slate-800/80">
|
||||
<div class="flex items-center justify-between text-xs mb-1">
|
||||
<div class="mt-3 bg-slate-950/80 rounded-2xl p-3 border border-slate-800/80">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm mb-1.5">
|
||||
<span class="font-bold text-white flex items-center gap-1">
|
||||
<span class="text-base font-black text-teal-300">${item.quantity}</span>
|
||||
<span class="text-lg font-black text-teal-300">${item.quantity}</span>
|
||||
<span class="text-slate-400 font-normal">${item.unit}</span>
|
||||
</span>
|
||||
<span class="text-[11px] text-slate-400">
|
||||
<span class="text-xs text-slate-400">
|
||||
Pack: ${item.initial_quantity} ${item.unit} (${percent}%)
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden">
|
||||
<div class="w-full bg-slate-800 h-2 rounded-full overflow-hidden">
|
||||
<div class="${progressBarColor} h-full rounded-full transition-all duration-300" style="width: ${percent}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3 Primary Action Buttons: Add Pack, Remove Pack, Manage Grams -->
|
||||
<div class="grid grid-cols-3 gap-1.5 pt-2 border-t border-slate-800/80">
|
||||
<button onclick="addWholePacket(${item.id})" class="flex items-center justify-center gap-1 py-1.5 px-2 rounded-xl bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-xs font-semibold transition active:scale-95" title="Add 1 whole packet (+${item.initial_quantity}${item.unit})">
|
||||
<i data-lucide="plus" class="w-3.5 h-3.5"></i>
|
||||
<!-- 3 Quick Tablet Action Buttons -->
|
||||
<div class="grid grid-cols-3 gap-2 pt-2 border-t border-slate-800/80">
|
||||
<button onclick="addWholePacket(${item.id})" class="flex items-center justify-center gap-1.5 py-2.5 px-2 rounded-xl bg-emerald-500/15 hover:bg-emerald-500/25 text-emerald-300 border border-emerald-500/30 text-xs sm:text-sm font-bold transition active:scale-95 touch-manipulation min-h-[42px]" title="Add 1 whole packet">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
<span>Add</span>
|
||||
</button>
|
||||
<button onclick="removeWholePacket(${item.id})" class="flex items-center justify-center gap-1 py-1.5 px-2 rounded-xl bg-rose-500/10 hover:bg-rose-500/20 text-rose-300 border border-rose-500/30 text-xs font-semibold transition active:scale-95" title="Remove 1 whole packet (-${item.initial_quantity}${item.unit})">
|
||||
<i data-lucide="minus" class="w-3.5 h-3.5"></i>
|
||||
<button onclick="removeWholePacket(${item.id})" class="flex items-center justify-center gap-1.5 py-2.5 px-2 rounded-xl bg-rose-500/15 hover:bg-rose-500/25 text-rose-300 border border-rose-500/30 text-xs sm:text-sm font-bold transition active:scale-95 touch-manipulation min-h-[42px]" title="Remove 1 whole packet">
|
||||
<i data-lucide="minus" class="w-4 h-4"></i>
|
||||
<span>Remove</span>
|
||||
</button>
|
||||
<button onclick="openGramsModal(${item.id})" class="flex items-center justify-center gap-1 py-1.5 px-2 rounded-xl bg-teal-500/20 hover:bg-teal-500/30 text-teal-300 border border-teal-500/40 text-xs font-semibold transition active:scale-95" title="Adjust grams when not used up">
|
||||
<i data-lucide="sliders" class="w-3.5 h-3.5"></i>
|
||||
<button onclick="openGramsModal(${item.id})" class="flex items-center justify-center gap-1.5 py-2.5 px-2 rounded-xl bg-teal-500/20 hover:bg-teal-500/30 text-teal-300 border border-teal-500/40 text-xs sm:text-sm font-bold transition active:scale-95 touch-manipulation min-h-[42px]" title="Manage grams remaining">
|
||||
<i data-lucide="sliders" class="w-4 h-4"></i>
|
||||
<span>Manage</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -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 `
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-2xl p-3.5 shadow-md transition hover:border-slate-700 space-y-3">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-2xl sm:rounded-3xl p-4 sm:p-5 shadow-lg transition hover:border-slate-700 space-y-4">
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||
<div class="flex flex-col tablet:flex-row items-start tablet:items-center justify-between gap-4">
|
||||
|
||||
<!-- Item info -->
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||
<!-- Product info -->
|
||||
<div class="flex items-center gap-3.5 min-w-0 flex-1">
|
||||
${item.image_url ? `
|
||||
<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="w-11 h-11 object-contain rounded-xl bg-white/5 p-1 border border-slate-800 shrink-0">
|
||||
<img src="${escapeHtml(item.image_url)}" alt="${escapeHtml(item.name)}" class="w-14 h-14 object-contain rounded-2xl bg-white/5 p-1 border border-slate-800 shrink-0">
|
||||
` : `
|
||||
<div class="w-11 h-11 rounded-xl bg-slate-800 border border-slate-700 text-teal-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="${getCategoryIcon(item.category)}" class="w-5 h-5"></i>
|
||||
<div class="w-14 h-14 rounded-2xl bg-slate-800 border border-slate-700 text-teal-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="${getCategoryIcon(item.category)}" class="w-7 h-7"></i>
|
||||
</div>
|
||||
`}
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h4 class="font-bold text-white text-sm truncate">${escapeHtml(item.name)}</h4>
|
||||
${item.brand ? `<span class="text-[11px] px-1.5 py-0.5 rounded bg-slate-800 text-slate-300">${escapeHtml(item.brand)}</span>` : ''}
|
||||
<h4 class="font-extrabold text-white text-base sm:text-lg truncate">${escapeHtml(item.name)}</h4>
|
||||
${item.brand ? `<span class="text-xs px-2 py-0.5 rounded-lg bg-slate-800 text-slate-300 font-medium">${escapeHtml(item.brand)}</span>` : ''}
|
||||
</div>
|
||||
<div class="text-xs text-slate-400 flex items-center gap-2 mt-0.5 flex-wrap">
|
||||
<span class="font-semibold text-teal-300">${item.quantity} ${item.unit}</span>
|
||||
<div class="text-xs sm:text-sm text-slate-400 flex items-center gap-2.5 mt-1 flex-wrap">
|
||||
<span class="font-black text-teal-300 text-sm sm:text-base">${item.quantity} ${item.unit}</span>
|
||||
<span>(Pack size: ${item.initial_quantity} ${item.unit})</span>
|
||||
<span>•</span>
|
||||
<span class="${expiryInfo.badgeClass} px-1.5 py-0.2 rounded font-medium text-[11px]">${expiryInfo.label}</span>
|
||||
<span class="${expiryInfo.badgeClass} px-2 py-0.5 rounded-lg font-bold text-xs">${expiryInfo.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The 3 Core Action Buttons: Manage, Add Pack, Remove Pack (+ Edit/Delete icons) -->
|
||||
<div class="flex items-center gap-2 w-full sm:w-auto justify-end">
|
||||
<!-- The 3 Core Tablet Action Buttons: Add, Remove, Manage Product Grams -->
|
||||
<div class="flex items-center gap-2 w-full tablet:w-auto justify-end flex-wrap">
|
||||
|
||||
<!-- 1. Add Whole Packet -->
|
||||
<button onclick="addWholePacket(${item.id})" class="px-3 py-1.5 rounded-xl bg-emerald-500/15 hover:bg-emerald-500/25 text-emerald-300 border border-emerald-500/30 text-xs font-semibold flex items-center gap-1 transition active:scale-95" title="Add 1 Whole Packet">
|
||||
<i data-lucide="plus" class="w-3.5 h-3.5"></i>
|
||||
<button
|
||||
onclick="addWholePacket(${item.id})"
|
||||
class="flex-1 tablet:flex-initial px-4 py-3 rounded-2xl bg-emerald-500/15 hover:bg-emerald-500/25 text-emerald-300 border border-emerald-500/30 text-xs sm:text-sm font-bold flex items-center justify-center gap-1.5 transition active:scale-95 touch-manipulation min-h-[46px]"
|
||||
title="Add 1 Whole Packet"
|
||||
>
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
<span>Add</span>
|
||||
</button>
|
||||
|
||||
<!-- 2. Remove Whole Packet -->
|
||||
<button onclick="removeWholePacket(${item.id})" class="px-3 py-1.5 rounded-xl bg-rose-500/15 hover:bg-rose-500/25 text-rose-300 border border-rose-500/30 text-xs font-semibold flex items-center gap-1 transition active:scale-95" title="Remove 1 Whole Packet">
|
||||
<i data-lucide="minus" class="w-3.5 h-3.5"></i>
|
||||
<button
|
||||
onclick="removeWholePacket(${item.id})"
|
||||
class="flex-1 tablet:flex-initial px-4 py-3 rounded-2xl bg-rose-500/15 hover:bg-rose-500/25 text-rose-300 border border-rose-500/30 text-xs sm:text-sm font-bold flex items-center justify-center gap-1.5 transition active:scale-95 touch-manipulation min-h-[46px]"
|
||||
title="Remove 1 Whole Packet"
|
||||
>
|
||||
<i data-lucide="minus" class="w-4 h-4"></i>
|
||||
<span>Remove</span>
|
||||
</button>
|
||||
|
||||
<!-- 3. Manage Grams / Partial Amount -->
|
||||
<button onclick="openGramsModal(${item.id})" class="px-3.5 py-1.5 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-xs font-semibold flex items-center gap-1 shadow-sm transition active:scale-95" title="Adjust grams when partially used">
|
||||
<i data-lucide="sliders" class="w-3.5 h-3.5"></i>
|
||||
<!-- 3. Manage Grams / Partial -->
|
||||
<button
|
||||
onclick="openGramsModal(${item.id})"
|
||||
class="flex-1 tablet:flex-initial px-5 py-3 rounded-2xl bg-teal-500 hover:bg-teal-400 text-white text-xs sm:text-sm font-bold flex items-center justify-center gap-1.5 shadow-md shadow-teal-500/20 transition active:scale-95 touch-manipulation min-h-[46px]"
|
||||
title="Manage product grams"
|
||||
>
|
||||
<i data-lucide="sliders" class="w-4 h-4"></i>
|
||||
<span>Manage</span>
|
||||
</button>
|
||||
|
||||
<!-- Secondary: Edit / Delete -->
|
||||
<button onclick="openItemModal(${item.id})" class="p-1.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white border border-slate-700 text-xs ml-1" title="Edit Item Details">
|
||||
<i data-lucide="pencil" class="w-3.5 h-3.5"></i>
|
||||
<button onclick="openItemModal(${item.id})" class="p-3 rounded-2xl bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white border border-slate-700 min-h-[46px] min-w-[46px] flex items-center justify-center touch-manipulation" title="Edit details">
|
||||
<i data-lucide="pencil" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onclick="confirmDeleteItem(${item.id}, '${escapeHtml(item.name)}')" class="p-1.5 rounded-xl bg-slate-800 hover:bg-rose-900/40 text-slate-400 hover:text-rose-300 border border-slate-700 text-xs" title="Delete">
|
||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||
<button onclick="confirmDeleteItem(${item.id}, '${escapeHtml(item.name)}')" class="p-3 rounded-2xl bg-slate-800 hover:bg-rose-900/40 text-slate-400 hover:text-rose-300 border border-slate-700 min-h-[46px] min-w-[46px] flex items-center justify-center touch-manipulation" title="Delete">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@@ -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 = `<span>${escapeHtml(message)}</span>`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
Reference in New Issue
Block a user