<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>SeedVault Inventory</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Custom focus ring for better accessibility and touch */
input:focus, button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.4);
}
/* Smooth transition for all interactive elements */
* { transition: background-color 0.2s, border-color 0.2s, transform 0.1s; }
</style>
</head>
<body class="bg-slate-50 font-sans antialiased text-slate-900">
<div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-green-700 text-white shadow-lg sticky top-0 z-40">
<div class="container mx-auto px-4 py-3 md:py-4 flex justify-between items-center">
<div class="flex items-center gap-2 md:gap-4">
<a href="{{ url_for('index') }}"><h1 class="text-xl md:text-2xl font-bold tracking-tight">🌱 SeedVault</h1></a>
<form action="/" method="GET" class="flex items-center bg-white/10 rounded-lg px-2 py-1 border border-white/20">
<input type="text" name="search" value="{{ search_query }}" placeholder="Search..." class="bg-transparent border-none text-xs md:text-sm text-white placeholder-white/60 focus:ring-0 w-24 md:w-48">
<button type="submit" class="text-white/60 hover:text-white text-xs md:text-sm">🔍</button>
</form>
{% if search_query %}
<a href="/" class="text-xs bg-white/10 hover:bg-white/20 px-2 py-1 rounded text-white/80 transition">Clear</a>
{% endif %}
</div>
<div class="flex items-center gap-2 md:gap-3">
{% if last_sync %}
<div class="flex items-center gap-1.5 text-[10px] md:text-xs text-white/70 bg-black/10 px-2 py-1 rounded-full">
<span class="w-1.5 h-1.5 rounded-full {% if last_sync.status == 'success' %}bg-green-400{% elif last_sync.status == 'partial' %}bg-yellow-400{% else %}bg-red-400{% endif %}"></span>
<span>Sync: {{ last_sync.timestamp.strftime('%H:%M') }}</span>
</div>
{% endif %}
<div class="hidden md:flex items-center bg-white/10 rounded-lg p-1 border border-white/20">
<a href="/?filter=all{% if search_query %}&search={{ search_query }}{% endif %}" class="px-2 py-0.5 text-[10px] md:text-xs rounded {% if filter_type == 'all' %}bg-white text-green-700 font-bold{% else %}text-white/70 hover:text-white{% endif %} transition">All</a>
<a href="/?filter=action_required{% if search_query %}&search={{ search_query }}{% endif %}" class="px-2 py-0.5 text-[10px] md:text-xs rounded {% if filter_type == 'action_required' %}bg-white text-green-700 font-bold{% else %}text-white/70 hover:text-white{% endif %} transition">⚠️ Needs Attention</a>
</div>
<button onclick="document.getElementById('add-product-modal').classList.remove('hidden')" class="bg-white/20 hover:bg-white/30 text-white px-3 py-1.5 md:px-4 md:py-2 rounded-lg transition shadow text-sm font-semibold backdrop-blur-sm">
+ Add
</button>
</div>
</div>
</header>
<main class="container mx-auto p-4 md:p-8 flex-grow">
<!-- Flash Messages -->
<div id="flash-container" class="max-w-2xl mx-auto">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="mb-4 p-4 rounded-xl shadow-sm {% if category == 'success' %}bg-green-50 text-green-800 border border-green-200{% elif category == 'danger' %}bg-red-50 text-red-800 border border-red-200{% else %}bg-yellow-50 text-yellow-800 border border-yellow-200{% endif %} flex items-center gap-3">
<span class="text-xl">{% if category == 'success' %}✅{% elif category == 'danger' %}❌{% else %}⚠️{% endif %}</span>
<div class="text-sm font-medium">{{ message }}</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<!-- Desktop View: Table -->
<div class="hidden md:block bg-white shadow-xl rounded-2xl overflow-hidden border border-slate-200">
<table class="w-full text-left border-collapse">
<thead class="bg-slate-50 border-b border-slate-200 text-slate-500 text-xs uppercase tracking-widest font-semibold">
<tr>
<th class="px-6 py-4">Product Name</th>
<th class="px-6 py-4">SKU</th>
<th class="px-6 py-4 text-right">Stock</th>
<th class="px-6 py-4 text-right">Weight/Pkg</th>
<th class="px-6 py-4 text-center">Status</th>
<th class="px-6 py-4 text-center">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 text-slate-700">
{% for product in products %}
<tr class="hover:bg-slate-50/50 transition-colors">
<td class="px-6 py-4 font-semibold text-slate-900">{{ product.name }}</td>
<td class="px-6 py-4 text-slate-500 text-sm">{{ product.sku or '-' }}</td>
<td class="px-6 py-4 text-right font-mono">
{{ product.current_stock_qty if product.is_unit_based else "%.2f"|format(product.current_stock_g) }}{% if not product.is_unit_based %}g{% else %} units{% endif %}
</td>
<td class="px-6 py-4 text-right text-slate-500">
{% if not product.is_unit_based %}
{{ "%.2f"|format(product.weight_per_packet_g) }}g
{% else %}
-
{% endif %}
</td>
<td class="px-6 py-4 text-center">
{% if product.get_status() == 'OK' %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">OK</span>
{% elif product.get_status() == 'LOW' %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">LOW</span>
{% else %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">OUT</span>
{% endif %}
</td>
<td class="px-6 py-4 text-center">
<div class="flex justify-center gap-4">
<button onclick="openSellModal('{{ product.id }}', '{{ product.name }}')" class="text-blue-600 hover:text-blue-800 font-semibold text-sm">Sell</button>
<button onclick="openRestockModal('{{ product.id }}', '{{ product.name }}', {{ product.is_unit_based|lower }})" class="text-green-600 hover:text-green-800 font-semibold text-sm">Restock</button>
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="text-slate-500 hover:text-slate-700 font-semibold text-sm">Edit</a>
<form action="{{ url_for('delete_product', product_id=product.id) }}" method="POST" class="inline" onsubmit="return confirm('Are you sure?');">
<button type="submit" class="text-red-500 hover:text-red-700 font-semibold text-sm">Delete</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if not products %}
<div class="hidden md:block p-20 text-center text-slate-400 italic">
<p class="text-lg">No products in inventory.</p>
</div>
{% endif %}
<!-- Mobile View: Card List -->
<div class="md:hidden space-y-4">
{% for product in products %}
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden relative
{% if product.get_status() == 'OK' %}border-l-4 border-l-green-500{% elif product.get_status() == 'LOW' %}border-l-4 border-l-yellow-500{% else %}border-l-4 border-l-red-500{% endif %}">
<div class="p-5">
<div class="flex justify-between items-start mb-4">
<div class="pr-2">
<h3 class="text-lg font-bold text-slate-900 leading-tight">{{ product.name }}</h3>
<p class="text-xs font-mono text-slate-400 mt-1">{{ product.sku or 'No SKU' }}</p>
</div>
<div class="flex flex-col items-end">
{% if product.get_status() == 'OK' %}
<span class="px-2 py-1 text-[10px] font-bold uppercase tracking-wider rounded-lg bg-green-100 text-green-700">OK</span>
{% elif product.get_status() == 'LOW' %}
<span class="px-2 py-1 text-[10px] font-bold uppercase tracking-wider rounded-lg bg-yellow-100 text-yellow-700">Low</span>
{% else %}
<span class="px-2 py-1 text-[10px] font-bold uppercase tracking-wider rounded-lg bg-red-100 text-red-700">Out</span>
{% endif %}
</div>
</div>
<div class="grid {% if product.is_unit_based %}grid-cols-1{% else %}grid-cols-2{% endif %} gap-6 py-4 border-y border-slate-50">
<div class="text-right">
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Stock</p>
<p class="text-lg font-bold text-slate-800">{{ product.current_stock_qty if product.is_unit_based else "%.2f"|format(product.current_stock_g) }}{% if not product.is_unit_based %}g{% else %} units{% endif %}</p>
</div>
{% if not product.is_unit_based %}
<div class="text-right">
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Weight/Pkg</p>
<p class="text-lg font-bold text-slate-800">{{ "%.2f"|format(product.weight_per_packet_g) }}g</p>
</div>
{% endif %}
</div>
<!-- Primary Mobile Actions (Tactile) -->
<div class="grid grid-cols-2 gap-3 mt-5">
<button onclick="openSellModal('{{ product.id }}', '{{ product.name }}')"
class="flex items-center justify-center bg-blue-600 text-white py-3.5 rounded-xl font-bold text-sm shadow-md active:scale-95 transition-transform">
Sell
</button>
<button onclick="openRestockModal('{{ product.id }}', '{{ product.name }}', {{ product.is_unit_based|lower }})"
class="flex items-center justify-center bg-green-600 text-white py-3.5 rounded-xl font-bold text-sm shadow-md active:scale-95 transition-transform">
Restock
</button>
</div>
<!-- Secondary Mobile Actions (Subtle) -->
<div class="flex justify-between items-center mt-5 pt-4 border-t border-slate-50">
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="text-slate-400 hover:text-slate-600 text-sm font-medium">Edit</a>
<form action="{{ url_for('delete_product', product_id=product.id) }}" method="POST" class="inline" onsubmit="return confirm('Delete this product?');">
<button type="submit" class="text-red-400 hover:text-red-600 text-sm font-medium">Delete</button>
</form>
</div>
</div>
</div>
{% endfor %}
{% if not products %}
<div class="text-center text-slate-400 py-12 italic">No products found.</div>
{% endif %}
</div>
</main>
<!-- Modals -->
<!-- Add Product Modal -->
<div id="add-product-modal" class="hidden fixed inset-0 bg-slate-900/60 z-50 flex flex-col justify-end md:items-center md:justify-center p-0 md:p-4 backdrop-blur-sm">
<div class="bg-white w-full max-w-md p-6 rounded-t-[2.5rem] md:rounded-3xl shadow-2xl transition-transform duration-300">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold text-slate-900">Add New Product</h3>
<button onclick="document.getElementById('add-product-modal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600 text-3xl leading-none">×</button>
</div>
<form action="{{ url_for('add_product') }}" method="POST" class="space-y-5">
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Product Name</label>
<input type="text" name="name" required class="block w-full rounded-xl border-slate-200 bg-slate-50 p-3 text-base focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">SKU (Optional)</label>
<input type="text" name="sku" class="block w-full rounded-xl border-slate-200 bg-slate-50 p-3 text-base focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Initial Stock (g)</label>
<input type="number" step="0.01" name="current_stock" required class="block w-full rounded-xl border-slate-200 bg-slate-50 p-3 text-base focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Weight/Packet (g)</label>
<input type="number" step="0.01" name="weight_per_packet" required class="block w-full rounded-xl border-slate-200 bg-slate-50 p-3 text-base focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
</div>
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Low Stock Threshold (g)</label>
<input type="number" step="0.01" name="threshold" required class="block w-full rounded-xl border-slate-200 bg-slate-50 p-3 text-base focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
<div class="flex items-center gap-3">
<input type="checkbox" name="is_unit_based" id="is_unit_based" class="w-5 h-5 rounded border-slate-300 text-green-600 focus:ring-green-500">
<label for="is_unit_based" class="text-sm font-medium text-slate-700">Unit-based item (e.g. tools)</label>
</div>
<div class="pt-4">
<button type="submit" class="w-full py-4 bg-green-600 text-white rounded-2xl font-bold shadow-lg shadow-green-200 active:scale-95 transition-all text-base">Save Product</button>
</div>
</form>
</div>
</div>
<!-- Restock Modal -->
<div id="restock-modal" class="hidden fixed inset-0 bg-slate-900/60 z-50 flex flex-col justify-end md:items-center md:justify-center p-0 md:p-4 backdrop-blur-sm">
<div class="bg-white w-full max-w-md p-6 rounded-t-[2.5rem] md:rounded-3xl shadow-2xl transition-transform duration-300">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold text-slate-900">Restock Product</h3>
<button onclick="document.getElementById('restock-modal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600 text-3xl leading-none">×</button>
</div>
<form id="restock-form" action="{{ url_for('restock') }}" method="POST" class="space-y-5">
<input type="hidden" name="product_id" id="restock-product-id">
<div class="space-y-1.5">
<p id="restock-product-name" class="text-lg font-bold text-slate-800"></p>
<p id="restock-desc" class="text-sm text-slate-500">How many grams are you adding?</p>
</div>
<div class="space-y-1.5">
<label id="restock-label" class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Amount (grams)</label>
<input type="number" step="0.01" name="amount" required autofocus class="block w-full rounded-xl border-slate-200 bg-slate-50 p-4 text-lg font-mono focus:ring-2 focus:ring-green-500 border outline-none transition">
</div>
<div class="pt-4">
<button type="submit" class="w-full py-4 bg-green-600 text-white rounded-2xl font-bold shadow-lg shadow-green-200 active:scale-95 transition-all text-base">Update Stock</button>
</div>
</form>
</div>
</div>
<!-- Sell Modal -->
<div id="sell-modal" class="hidden fixed inset-0 bg-slate-900/60 z-50 flex flex-col justify-end md:items-center md:justify-center p-0 md:p-4 backdrop-blur-sm">
<div class="bg-white w-full max-w-md p-6 rounded-t-[2.5rem] md:rounded-3xl shadow-2xl transition-transform duration-300">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold text-slate-900">Record Sale</h3>
<button onclick="document.getElementById('sell-modal').classList.add('hidden')" class="text-slate-400 hover:text-slate-600 text-3xl leading-none">×</button>
</div>
<form id="sell-form" action="{{ url_for('sell') }}" method="POST" class="space-y-5">
<input type="hidden" name="product_id" id="sell-product-id">
<div class="space-y-1.5">
<p id="sell-product-name" class="text-lg font-bold text-slate-800"></p>
<p class="text-sm text-slate-500">Number of packets sold</p>
</div>
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-500 uppercase tracking-wider">Packets</label>
<input type="number" name="packets" required class="block w-full rounded-xl border-slate-200 bg-slate-50 p-4 text-lg font-mono focus:ring-2 focus:ring-blue-500 border outline-none transition">
</div>
<div class="pt-4">
<button type="submit" class="w-full py-4 bg-blue-600 text-white rounded-2xl font-bold shadow-lg shadow-blue-200 active:scale-95 transition-all text-base">Confirm Sale</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openRestockModal(id, name, isUnitBased) {
document.getElementById('restock-product-id').value = id;
document.getElementById('restock-product-name').innerText = name;
const descEl = document.getElementById('restock-desc');
const labelEl = document.getElementById('restock-label');
if (isUnitBased) {
descEl.innerText = 'How many units are you adding?';
labelEl.innerText = 'Amount (units)';
} else {
descEl.innerText = 'How many grams are you adding?';
labelEl.innerText = 'Amount (grams)';
}
document.getElementById('restock-modal').classList.remove('hidden');
}
function openSellModal(id, name) {
document.getElementById('sell-product-id').value = id;
document.getElementById('sell-product-name').innerText = name;
document.getElementById('sell-modal').classList.remove('hidden');
}
</script>
</body>
</html>