feat(ui): implement dual view/manage modes, live barcode camera scanner, and interactive grams adjuster
This commit is contained in:
+1050
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,504 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full bg-slate-900 text-slate-100">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Fridger - Smart Fridge Inventory</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#f0fdfa',
|
||||
100: '#ccfbf1',
|
||||
400: '#2dd4bf',
|
||||
500: '#14b8a6',
|
||||
600: '#0d9488',
|
||||
700: '#0f766e',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<!-- HTML5 QR Code Scanner -->
|
||||
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
|
||||
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
<body class="h-full flex flex-col bg-slate-950 text-slate-100 antialiased selection:bg-teal-500 selection:text-white">
|
||||
|
||||
<!-- TOP APP BAR -->
|
||||
<header class="sticky top-0 z-30 bg-slate-900/90 backdrop-blur-md border-b border-slate-800">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16 gap-4">
|
||||
|
||||
<!-- Brand Logo -->
|
||||
<div class="flex items-center gap-3 shrink-0">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-teal-600 to-cyan-500 flex items-center justify-center shadow-lg shadow-teal-500/20 text-white font-black text-xl">
|
||||
🧊
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="font-bold text-lg leading-tight tracking-tight bg-gradient-to-r from-teal-400 to-cyan-300 bg-clip-text text-transparent">Fridger</h1>
|
||||
<p class="text-xs text-slate-400 hidden sm:block">Fridge Inventory & Grams Tracker</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TWO-MODE SWITCHER -->
|
||||
<div class="flex items-center bg-slate-800/80 p-1 rounded-xl border border-slate-700/80 shadow-inner">
|
||||
<button id="mode-view-btn" onclick="switchMode('view')" class="flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition-all duration-200 bg-teal-500 text-white shadow-md">
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
<span>Fridge View</span>
|
||||
</button>
|
||||
<button id="mode-manage-btn" onclick="switchMode('manage')" class="flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition-all duration-200 text-slate-300 hover:text-white">
|
||||
<i data-lucide="sliders-horizontal" class="w-4 h-4"></i>
|
||||
<span>Manage Fridge</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- QUICK SCAN / ADD ACTIONS -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="openScannerModal()" class="flex items-center gap-2 px-3 sm:px-4 py-2 rounded-xl bg-gradient-to-r from-teal-500 to-emerald-600 hover:from-teal-400 hover:to-emerald-500 text-white text-sm font-medium shadow-lg shadow-teal-500/25 transition-transform active:scale-95" title="Scan Barcode">
|
||||
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
|
||||
<span class="hidden md:inline">Scan Barcode</span>
|
||||
</button>
|
||||
<button onclick="openItemModal()" class="flex items-center gap-2 px-3 sm:px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 text-sm font-medium transition active:scale-95" title="Add Manually">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
<span class="hidden md:inline">Add Item</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- STATS BAR -->
|
||||
<section class="bg-slate-900/50 border-b border-slate-800/60 py-3">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
|
||||
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-teal-500/10 text-teal-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="refrigerator" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stat-total" class="text-lg font-bold text-white leading-none">0</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">Total In Fridge</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-emerald-500/10 text-emerald-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="check-circle-2" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stat-fresh" class="text-lg font-bold text-emerald-400 leading-none">0</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">Fresh & Good</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-amber-500/10 text-amber-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stat-expiring" class="text-lg font-bold text-amber-400 leading-none">0</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">Expiring ≤ 3 Days</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-indigo-500/10 text-indigo-400 flex items-center justify-center shrink-0">
|
||||
<i data-lucide="pie-chart" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stat-opened" class="text-lg font-bold text-indigo-300 leading-none">0</div>
|
||||
<div class="text-xs text-slate-400 mt-0.5">Opened / Partial</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SEARCH & FILTER CONTROLS -->
|
||||
<section class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-5 pb-3 w-full">
|
||||
<div class="flex flex-col md:flex-row gap-3 items-stretch md:items-center justify-between">
|
||||
|
||||
<!-- Search Input -->
|
||||
<div class="relative flex-1">
|
||||
<i data-lucide="search" class="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2"></i>
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
placeholder="Search items, brands, barcodes, or notes..."
|
||||
oninput="handleSearch(this.value)"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-teal-500 focus:ring-1 focus:ring-teal-500 rounded-xl pl-10 pr-10 py-2.5 text-sm text-slate-100 placeholder-slate-500 transition outline-none"
|
||||
>
|
||||
<button id="clear-search" onclick="clearSearch()" class="hidden absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Quick Status Filters -->
|
||||
<div class="flex items-center gap-1.5 overflow-x-auto pb-1 md:pb-0 scrollbar-none text-xs font-medium">
|
||||
<button onclick="setStatusFilter('all')" data-status-btn="all" class="status-chip active px-3 py-2 rounded-lg bg-slate-800 border border-slate-700 text-white whitespace-nowrap transition">All</button>
|
||||
<button onclick="setStatusFilter('fresh')" data-status-btn="fresh" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-slate-400 hover:text-slate-200 whitespace-nowrap transition">Fresh</button>
|
||||
<button onclick="setStatusFilter('expiring_soon')" data-status-btn="expiring_soon" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-amber-400/80 hover:text-amber-300 whitespace-nowrap transition">Expiring Soon</button>
|
||||
<button onclick="setStatusFilter('low')" data-status-btn="low" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-indigo-400/80 hover:text-indigo-300 whitespace-nowrap transition">Low ≤ 25%</button>
|
||||
<button onclick="setStatusFilter('expired')" data-status-btn="expired" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-rose-400/80 hover:text-rose-300 whitespace-nowrap transition">Expired</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Category Filter Bar -->
|
||||
<div id="category-filter-bar" class="flex items-center gap-2 overflow-x-auto pt-3 pb-1 scrollbar-none text-xs font-medium">
|
||||
<!-- Injected by JS -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MAIN CONTENT AREA -->
|
||||
<main class="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 w-full">
|
||||
|
||||
<!-- MODE 1: FRIDGE VIEW CONTAINER -->
|
||||
<div id="view-mode-container" class="space-y-6">
|
||||
<!-- View controls: Group by Shelf or Group by Category or Card Grid -->
|
||||
<div class="flex items-center justify-between pb-2 border-b border-slate-800 text-xs text-slate-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-slate-300" id="item-count-label">Showing all items</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="hidden sm:inline">Group by:</span>
|
||||
<button onclick="setViewGrouping('shelf')" id="grp-shelf-btn" class="px-2.5 py-1 rounded bg-teal-500/20 text-teal-300 border border-teal-500/30 font-medium">Shelves / Location</button>
|
||||
<button onclick="setViewGrouping('category')" id="grp-cat-btn" class="px-2.5 py-1 rounded bg-slate-800 text-slate-400 border border-slate-700 font-medium">Category</button>
|
||||
<button onclick="setViewGrouping('grid')" id="grp-grid-btn" class="px-2.5 py-1 rounded bg-slate-800 text-slate-400 border border-slate-700 font-medium">Grid</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fridge Visual Display Area -->
|
||||
<div id="fridge-view-content" class="space-y-6">
|
||||
<!-- Rendered items grouped by shelf/location or category -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODE 2: MANAGE INVENTORY CONTAINER -->
|
||||
<div id="manage-mode-container" class="hidden space-y-6">
|
||||
|
||||
<!-- Top Action Ribbon for Management -->
|
||||
<div class="bg-gradient-to-r from-slate-900 to-slate-850 p-4 rounded-2xl border border-slate-800 flex flex-col sm:flex-row items-center justify-between gap-4 shadow-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-teal-500/10 border border-teal-500/20 text-teal-400 flex items-center justify-center">
|
||||
<i data-lucide="package-plus" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-white text-base">Inventory Management</h3>
|
||||
<p class="text-xs text-slate-400">Add products, adjust grams after cooking, and manage stock</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 w-full sm:w-auto">
|
||||
<button onclick="openScannerModal()" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-md shadow-teal-500/20 transition">
|
||||
<i data-lucide="scan" class="w-4 h-4"></i>
|
||||
<span>Scan Barcode</span>
|
||||
</button>
|
||||
<button onclick="openItemModal()" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 text-sm font-semibold transition">
|
||||
<i data-lucide="plus-circle" class="w-4 h-4"></i>
|
||||
<span>Add Item</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manage Items List -->
|
||||
<div id="manage-items-list" class="space-y-3">
|
||||
<!-- Dynamic list of management rows with quick grams adjustment sliders and +/- buttons -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EMPTY STATE -->
|
||||
<div id="empty-state" class="hidden text-center py-16 px-4">
|
||||
<div class="w-16 h-16 rounded-2xl bg-slate-800 text-slate-400 flex items-center justify-center mx-auto mb-4 border border-slate-700">
|
||||
<i data-lucide="refrigerator" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-white mb-1">No items found</h3>
|
||||
<p class="text-sm text-slate-400 max-w-sm mx-auto mb-6">Your fridge is either empty or matches no search filters. Scan a barcode or add an item to get started!</p>
|
||||
<div class="flex items-center justify-center gap-3">
|
||||
<button onclick="openScannerModal()" class="px-4 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-medium flex items-center gap-2">
|
||||
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
|
||||
<span>Scan Product</span>
|
||||
</button>
|
||||
<button onclick="seedDemoData()" class="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-300 text-sm font-medium">
|
||||
Load Sample Fridge
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- QUICK GRAMS / QUANTITY ADJUSTMENT MODAL -->
|
||||
<div id="grams-modal" class="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm hidden flex items-center justify-center p-4">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-2xl max-w-md w-full p-6 shadow-2xl space-y-5 animate-scale-up">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<span class="text-xs uppercase tracking-wider font-bold text-teal-400">Adjust Amount</span>
|
||||
<h3 id="grams-modal-title" class="text-lg font-bold text-white mt-0.5">Item Name</h3>
|
||||
<p id="grams-modal-sub" class="text-xs text-slate-400">Initial: 500g • Opened 2 days ago</p>
|
||||
</div>
|
||||
<button onclick="closeGramsModal()" class="text-slate-400 hover:text-white p-1">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Remaining Grams Gauge / Slider -->
|
||||
<div class="bg-slate-950/60 p-4 rounded-xl border border-slate-800/80 space-y-4 text-center">
|
||||
<div>
|
||||
<div class="text-3xl font-black text-white flex items-center justify-center gap-1.5">
|
||||
<span id="grams-current-val">250</span>
|
||||
<span id="grams-modal-unit" class="text-lg font-normal text-teal-400">g</span>
|
||||
</div>
|
||||
<div id="grams-percent-text" class="text-xs text-slate-400 mt-1">50% remaining</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
id="grams-slider"
|
||||
min="0"
|
||||
max="500"
|
||||
step="5"
|
||||
value="250"
|
||||
oninput="onGramsSliderChange(this.value)"
|
||||
class="w-full accent-teal-500 cursor-pointer h-2 bg-slate-800 rounded-lg"
|
||||
>
|
||||
|
||||
<!-- Quick Decrement Buttons -->
|
||||
<div class="flex items-center justify-center gap-2 pt-1 flex-wrap">
|
||||
<button onclick="applyGramsDelta(-10)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-10</button>
|
||||
<button onclick="applyGramsDelta(-25)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-25</button>
|
||||
<button onclick="applyGramsDelta(-50)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-50</button>
|
||||
<button onclick="applyGramsDelta(-100)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-100</button>
|
||||
<button onclick="setGramsPercent(0.5)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">Half</button>
|
||||
<button onclick="setGramsPercent(0)" class="px-2.5 py-1.5 rounded-lg bg-rose-950/40 hover:bg-rose-900/50 text-xs text-rose-300 border border-rose-800/40 font-medium">Empty (0)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Direct Numeric Input Option -->
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-xs text-slate-400 shrink-0">Exact remaining:</label>
|
||||
<div class="relative flex-1">
|
||||
<input
|
||||
type="number"
|
||||
id="grams-exact-input"
|
||||
min="0"
|
||||
step="any"
|
||||
oninput="onGramsExactInput(this.value)"
|
||||
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-3 py-1.5 text-sm text-white focus:border-teal-500 outline-none"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-2">
|
||||
<button onclick="consumeAllAndRemove()" class="text-xs text-rose-400 hover:text-rose-300 flex items-center gap-1.5">
|
||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||
<span>Consumed all / Remove</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="closeGramsModal()" class="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-sm font-medium">Cancel</button>
|
||||
<button onclick="saveGramsAdjustment()" class="px-5 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-md">Save Amount</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADD / EDIT ITEM MODAL -->
|
||||
<div id="item-modal" class="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm hidden flex items-center justify-center p-4 overflow-y-auto">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-2xl max-w-lg w-full p-6 shadow-2xl space-y-4 my-8 animate-scale-up">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 id="item-modal-title" class="text-lg font-bold text-white">Add New Fridge Item</h3>
|
||||
<p class="text-xs text-slate-400">Scan barcode or type item details below</p>
|
||||
</div>
|
||||
<button onclick="closeItemModal()" class="text-slate-400 hover:text-white p-1">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Barcode Search & Scan quick strip -->
|
||||
<div class="bg-slate-950/60 p-3 rounded-xl border border-slate-800/80 flex items-center gap-2">
|
||||
<div class="relative flex-1">
|
||||
<i data-lucide="barcode" class="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
|
||||
<input
|
||||
type="text"
|
||||
id="modal-barcode"
|
||||
placeholder="Barcode (e.g. 4008452023451)"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-teal-500 rounded-lg pl-9 pr-3 py-1.5 text-xs text-white outline-none"
|
||||
>
|
||||
</div>
|
||||
<button type="button" onclick="lookupCurrentBarcode()" id="modal-barcode-lookup-btn" class="px-3 py-1.5 rounded-lg bg-teal-600 hover:bg-teal-500 text-white text-xs font-semibold shrink-0">
|
||||
Lookup
|
||||
</button>
|
||||
<button type="button" onclick="openScannerModal(true)" class="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 shrink-0" title="Scan with Camera">
|
||||
<i data-lucide="camera" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="item-form" onsubmit="handleItemFormSubmit(event)" class="space-y-3.5 text-sm">
|
||||
<input type="hidden" id="modal-item-id">
|
||||
<input type="hidden" id="modal-image-url">
|
||||
|
||||
<!-- Product Image Preview if found -->
|
||||
<div id="modal-image-container" class="hidden items-center gap-3 p-2 bg-slate-950 rounded-xl border border-slate-800">
|
||||
<img id="modal-image-preview" src="" alt="Product" class="w-12 h-12 object-contain rounded-lg bg-white/5">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-xs text-teal-400 font-medium">Product photo matched</span>
|
||||
<p id="modal-image-label" class="text-xs text-slate-400 truncate"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Name & Brand -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Item Name *</label>
|
||||
<input type="text" id="modal-name" required placeholder="e.g. Gouda Cheese" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Brand</label>
|
||||
<input type="text" id="modal-brand" placeholder="e.g. Milbona" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity & Unit -->
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Current Amount *</label>
|
||||
<input type="number" id="modal-quantity" step="any" min="0" required placeholder="250" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Package Total</label>
|
||||
<input type="number" id="modal-initial-quantity" step="any" min="0" placeholder="250" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Unit</label>
|
||||
<select id="modal-unit" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
<option value="g">Grams (g)</option>
|
||||
<option value="ml">Milliliters (ml)</option>
|
||||
<option value="pcs">Pieces (Stk)</option>
|
||||
<option value="kg">Kilograms (kg)</option>
|
||||
<option value="l">Liters (L)</option>
|
||||
<option value="pack">Pack / Packung</option>
|
||||
<option value="slices">Slices</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category & Location -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Category</label>
|
||||
<select id="modal-category" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
<!-- Options populated by meta -->
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Location in Fridge</label>
|
||||
<select id="modal-location" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
<!-- Options populated by meta -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expiration Date & Helpers -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label class="block text-xs text-slate-300 font-medium">Expiration Date (MHD)</label>
|
||||
<div class="flex items-center gap-1.5 text-[11px]">
|
||||
<button type="button" onclick="setModalExpiryDays(3)" class="text-slate-400 hover:text-teal-400">+3d</button>
|
||||
<span>•</span>
|
||||
<button type="button" onclick="setModalExpiryDays(7)" class="text-slate-400 hover:text-teal-400">+1w</button>
|
||||
<span>•</span>
|
||||
<button type="button" onclick="setModalExpiryDays(14)" class="text-slate-400 hover:text-teal-400">+2w</button>
|
||||
<span>•</span>
|
||||
<button type="button" onclick="setModalExpiryDays(30)" class="text-slate-400 hover:text-teal-400">+1m</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="date" id="modal-expiry" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
|
||||
<!-- Opened Status & Notes -->
|
||||
<div class="flex items-center gap-2 pt-1">
|
||||
<input type="checkbox" id="modal-opened" class="w-4 h-4 accent-teal-500 rounded bg-slate-950 border-slate-800">
|
||||
<label for="modal-opened" class="text-xs text-slate-300 cursor-pointer">Already opened pack / container</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs text-slate-300 font-medium mb-1">Notes (Optional)</label>
|
||||
<input type="text" id="modal-notes" placeholder="e.g. Left half for pasta tonight" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 pt-3 border-t border-slate-800">
|
||||
<button type="button" onclick="closeItemModal()" class="px-4 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-sm font-medium">Cancel</button>
|
||||
<button type="submit" id="modal-save-btn" class="px-6 py-2.5 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-lg shadow-teal-500/20">Save Item</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LIVE CAMERA BARCODE SCANNER MODAL -->
|
||||
<div id="scanner-modal" class="fixed inset-0 z-50 bg-black/85 backdrop-blur-md hidden flex items-center justify-center p-4">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-3xl max-w-md w-full overflow-hidden shadow-2xl flex flex-col animate-scale-up">
|
||||
|
||||
<div class="p-4 bg-slate-850 border-b border-slate-800 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-teal-500/10 text-teal-400 flex items-center justify-center">
|
||||
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<span class="font-bold text-white text-sm">Scan Product Barcode</span>
|
||||
</div>
|
||||
<button onclick="closeScannerModal()" class="text-slate-400 hover:text-white p-1">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Camera Preview Box -->
|
||||
<div class="relative bg-black flex items-center justify-center min-h-[300px] w-full overflow-hidden">
|
||||
<div id="barcode-reader" class="w-full"></div>
|
||||
<div id="scanner-overlay" class="absolute inset-0 pointer-events-none border-2 border-teal-400/40 rounded-2xl m-8 flex items-center justify-center">
|
||||
<div class="w-full h-0.5 bg-red-500/80 animate-pulse shadow-lg shadow-red-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scanner Status / Manual Fallback -->
|
||||
<div class="p-4 bg-slate-900 space-y-3">
|
||||
<div id="scanner-status" class="text-xs text-center text-slate-400">
|
||||
Point camera at standard EAN / UPC product barcode
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
id="scanner-manual-input"
|
||||
placeholder="Or type barcode number manually..."
|
||||
class="flex-1 bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-xs text-white outline-none"
|
||||
onkeydown="if(event.key === 'Enter') handleManualBarcodeSubmit()"
|
||||
>
|
||||
<button onclick="handleManualBarcodeSubmit()" class="px-3 py-2 rounded-xl bg-teal-600 hover:bg-teal-500 text-white text-xs font-semibold">
|
||||
Lookup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOAST NOTIFICATION CONTAINER -->
|
||||
<div id="toast-container" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2 max-w-sm pointer-events-none"></div>
|
||||
|
||||
<!-- MAIN SCRIPT -->
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* Custom styling & animations for Fridger */
|
||||
|
||||
@keyframes scaleUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-scale-up {
|
||||
animation: scaleUp 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
/* Glass Shelf Card effect */
|
||||
.shelf-card {
|
||||
background: linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.8) 100%);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(51, 65, 85, 0.6);
|
||||
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.shelf-card:hover {
|
||||
border-color: rgba(45, 212, 191, 0.4);
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(51, 65, 85, 0.6);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(20, 184, 166, 0.6);
|
||||
}
|
||||
|
||||
/* Hide scrollbar for category pills */
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-none {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* HTML5 Barcode Reader overlay styling */
|
||||
#barcode-reader video {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover !important;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
#barcode-reader {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
#barcode-reader__scan_region {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
#barcode-reader__dashboard_section_csr button {
|
||||
background-color: #0d9488 !important;
|
||||
color: white !important;
|
||||
border-radius: 0.5rem !important;
|
||||
padding: 0.35rem 0.75rem !important;
|
||||
border: none !important;
|
||||
font-size: 0.75rem !important;
|
||||
margin: 0.25rem !important;
|
||||
}
|
||||
Reference in New Issue
Block a user