|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Kitchen Stock - Track Out-of-Stock Items</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
|
|
|
|
|
body { |
|
|
font-family: 'Poppins', sans-serif; |
|
|
background-color: #f8fafc; |
|
|
} |
|
|
|
|
|
.gradient-bg { |
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
|
} |
|
|
|
|
|
.item-card { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.item-card:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
|
} |
|
|
|
|
|
.pulse { |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { |
|
|
transform: scale(0.95); |
|
|
box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7); |
|
|
} |
|
|
70% { |
|
|
transform: scale(1); |
|
|
box-shadow: 0 0 0 10px rgba(74, 222, 128, 0); |
|
|
} |
|
|
100% { |
|
|
transform: scale(0.95); |
|
|
box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="min-h-screen gradient-bg"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-12 h-12 rounded-full bg-white shadow-md flex items-center justify-center mr-4"> |
|
|
<i class="fas fa-utensils text-2xl text-emerald-500"></i> |
|
|
</div> |
|
|
<h1 class="text-3xl font-bold text-gray-800">Kitchen Stock</h1> |
|
|
</div> |
|
|
<button id="addItemBtn" class="bg-emerald-500 hover:bg-emerald-600 text-white px-6 py-2 rounded-full shadow-md flex items-center transition-all"> |
|
|
<i class="fas fa-plus mr-2"></i> Add Item |
|
|
</button> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> |
|
|
<div class="bg-white rounded-xl shadow-md p-6"> |
|
|
<div class="flex items-center"> |
|
|
<div class="p-3 rounded-full bg-emerald-100 text-emerald-600 mr-4"> |
|
|
<i class="fas fa-box-open text-xl"></i> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-gray-500 text-sm">Out of Stock</p> |
|
|
<h3 class="text-2xl font-bold" id="outOfStockCount">0</h3> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-xl shadow-md p-6"> |
|
|
<div class="flex items-center"> |
|
|
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4"> |
|
|
<i class="fas fa-list text-xl"></i> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-gray-500 text-sm">Total Items</p> |
|
|
<h3 class="text-2xl font-bold" id="totalItemsCount">0</h3> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-xl shadow-md p-6"> |
|
|
<div class="flex items-center"> |
|
|
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4"> |
|
|
<i class="fas fa-history text-xl"></i> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-gray-500 text-sm">Recently Added</p> |
|
|
<h3 class="text-2xl font-bold" id="recentItemsCount">0</h3> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden"> |
|
|
<div class="p-4 border-b border-gray-200 flex justify-between items-center"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">Out of Stock Items</h2> |
|
|
<div class="relative"> |
|
|
<input type="text" id="searchInput" placeholder="Search items..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"> |
|
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="p-4" id="itemsContainer"> |
|
|
|
|
|
<div id="emptyState" class="text-center py-12"> |
|
|
<div class="w-24 h-24 mx-auto mb-4 rounded-full bg-gray-100 flex items-center justify-center"> |
|
|
<i class="fas fa-box-open text-3xl text-gray-400"></i> |
|
|
</div> |
|
|
<h3 class="text-xl font-medium text-gray-700 mb-2">No out-of-stock items</h3> |
|
|
<p class="text-gray-500 mb-6">Add items that are out of stock to keep track</p> |
|
|
<button id="addFirstItemBtn" class="bg-emerald-500 hover:bg-emerald-600 text-white px-6 py-2 rounded-full shadow-md inline-flex items-center transition-all"> |
|
|
<i class="fas fa-plus mr-2"></i> Add Your First Item |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="addItemModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-xl shadow-xl w-full max-w-md mx-4"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-bold text-gray-800">Add Out-of-Stock Item</h3> |
|
|
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<form id="addItemForm"> |
|
|
<div class="mb-4"> |
|
|
<label for="itemName" class="block text-sm font-medium text-gray-700 mb-1">Item Name</label> |
|
|
<input type="text" id="itemName" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent" placeholder="e.g. Milk, Eggs, Bread" required> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label for="itemCategory" class="block text-sm font-medium text-gray-700 mb-1">Category</label> |
|
|
<select id="itemCategory" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"> |
|
|
<option value="Dairy">Dairy</option> |
|
|
<option value="Produce">Produce</option> |
|
|
<option value="Pantry">Pantry</option> |
|
|
<option value="Meat">Meat</option> |
|
|
<option value="Beverages">Beverages</option> |
|
|
<option value="Frozen">Frozen</option> |
|
|
<option value="Other">Other</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label for="itemNotes" class="block text-sm font-medium text-gray-700 mb-1">Notes (Optional)</label> |
|
|
<textarea id="itemNotes" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent" placeholder="Any specific details..."></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-end space-x-3"> |
|
|
<button type="button" id="cancelAddBtn" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-all"> |
|
|
Cancel |
|
|
</button> |
|
|
<button type="submit" class="px-6 py-2 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg shadow-md transition-all flex items-center"> |
|
|
<i class="fas fa-save mr-2"></i> Save Item |
|
|
</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="restockModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-xl shadow-xl w-full max-w-md mx-4"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-bold text-gray-800">Item Restocked</h3> |
|
|
<button id="closeRestockModalBtn" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<p class="mb-6 text-gray-700">Are you sure you want to mark <span id="restockItemName" class="font-semibold"></span> as restocked? This will remove it from your out-of-stock list.</p> |
|
|
|
|
|
<input type="hidden" id="restockItemId"> |
|
|
|
|
|
<div class="flex justify-end space-x-3"> |
|
|
<button type="button" id="cancelRestockBtn" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-all"> |
|
|
Cancel |
|
|
</button> |
|
|
<button type="button" id="confirmRestockBtn" class="px-6 py-2 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg shadow-md transition-all flex items-center"> |
|
|
<i class="fas fa-check mr-2"></i> Confirm Restock |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let items = JSON.parse(localStorage.getItem('kitchenStockItems')) || []; |
|
|
let recentItems = JSON.parse(localStorage.getItem('recentKitchenItems')) || []; |
|
|
|
|
|
|
|
|
if (items.length === 0) { |
|
|
recentItems = []; |
|
|
localStorage.setItem('recentKitchenItems', JSON.stringify(recentItems)); |
|
|
} |
|
|
|
|
|
|
|
|
const itemsContainer = document.getElementById('itemsContainer'); |
|
|
const emptyState = document.getElementById('emptyState'); |
|
|
const addItemBtn = document.getElementById('addItemBtn'); |
|
|
const addFirstItemBtn = document.getElementById('addFirstItemBtn'); |
|
|
const addItemModal = document.getElementById('addItemModal'); |
|
|
const closeModalBtn = document.getElementById('closeModalBtn'); |
|
|
const cancelAddBtn = document.getElementById('cancelAddBtn'); |
|
|
const addItemForm = document.getElementById('addItemForm'); |
|
|
const searchInput = document.getElementById('searchInput'); |
|
|
const restockModal = document.getElementById('restockModal'); |
|
|
const closeRestockModalBtn = document.getElementById('closeRestockModalBtn'); |
|
|
const cancelRestockBtn = document.getElementById('cancelRestockBtn'); |
|
|
const confirmRestockBtn = document.getElementById('confirmRestockBtn'); |
|
|
const restockItemName = document.getElementById('restockItemName'); |
|
|
const restockItemId = document.getElementById('restockItemId'); |
|
|
const outOfStockCount = document.getElementById('outOfStockCount'); |
|
|
const totalItemsCount = document.getElementById('totalItemsCount'); |
|
|
const recentItemsCount = document.getElementById('recentItemsCount'); |
|
|
|
|
|
|
|
|
addItemBtn.addEventListener('click', openAddItemModal); |
|
|
addFirstItemBtn.addEventListener('click', openAddItemModal); |
|
|
closeModalBtn.addEventListener('click', closeAddItemModal); |
|
|
cancelAddBtn.addEventListener('click', closeAddItemModal); |
|
|
addItemForm.addEventListener('submit', handleAddItem); |
|
|
searchInput.addEventListener('input', filterItems); |
|
|
closeRestockModalBtn.addEventListener('click', closeRestockModal); |
|
|
cancelRestockBtn.addEventListener('click', closeRestockModal); |
|
|
confirmRestockBtn.addEventListener('click', handleRestock); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
|
|
|
|
function init() { |
|
|
renderItems(); |
|
|
updateStats(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderItems(filteredItems = null) { |
|
|
const itemsToRender = filteredItems || items; |
|
|
|
|
|
if (itemsToRender.length === 0) { |
|
|
emptyState.classList.remove('hidden'); |
|
|
itemsContainer.innerHTML = ''; |
|
|
itemsContainer.appendChild(emptyState); |
|
|
return; |
|
|
} |
|
|
|
|
|
emptyState.classList.add('hidden'); |
|
|
|
|
|
itemsContainer.innerHTML = ''; |
|
|
|
|
|
itemsToRender.forEach((item, index) => { |
|
|
const itemCard = document.createElement('div'); |
|
|
itemCard.className = 'item-card bg-white rounded-lg shadow-sm p-4 mb-3 border-l-4 border-red-500 hover:shadow-md'; |
|
|
itemCard.innerHTML = ` |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<h3 class="font-semibold text-lg text-gray-800">${item.name}</h3> |
|
|
<div class="flex items-center mt-1"> |
|
|
<span class="text-xs px-2 py-1 rounded-full ${getCategoryColor(item.category)} mr-2">${item.category}</span> |
|
|
<span class="text-xs text-gray-500"><i class="far fa-clock mr-1"></i> ${formatDate(item.dateAdded)}</span> |
|
|
</div> |
|
|
${item.notes ? `<p class="text-sm text-gray-600 mt-2">${item.notes}</p>` : ''} |
|
|
</div> |
|
|
<button class="restock-btn px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-sm rounded-full transition-all" data-id="${index}"> |
|
|
<i class="fas fa-check mr-1"></i> Restocked |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
itemsContainer.appendChild(itemCard); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.restock-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const itemId = e.currentTarget.getAttribute('data-id'); |
|
|
openRestockModal(itemId); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function getCategoryColor(category) { |
|
|
const colors = { |
|
|
'Dairy': 'bg-blue-100 text-blue-800', |
|
|
'Produce': 'bg-green-100 text-green-800', |
|
|
'Pantry': 'bg-yellow-100 text-yellow-800', |
|
|
'Meat': 'bg-red-100 text-red-800', |
|
|
'Beverages': 'bg-indigo-100 text-indigo-800', |
|
|
'Frozen': 'bg-purple-100 text-purple-800', |
|
|
'Other': 'bg-gray-100 text-gray-800' |
|
|
}; |
|
|
return colors[category] || 'bg-gray-100 text-gray-800'; |
|
|
} |
|
|
|
|
|
|
|
|
function formatDate(dateString) { |
|
|
const date = new Date(dateString); |
|
|
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); |
|
|
} |
|
|
|
|
|
|
|
|
function openAddItemModal() { |
|
|
addItemModal.classList.remove('hidden'); |
|
|
document.getElementById('itemName').focus(); |
|
|
} |
|
|
|
|
|
|
|
|
function closeAddItemModal() { |
|
|
addItemModal.classList.add('hidden'); |
|
|
addItemForm.reset(); |
|
|
} |
|
|
|
|
|
|
|
|
function openRestockModal(itemId) { |
|
|
const item = items[itemId]; |
|
|
restockItemName.textContent = item.name; |
|
|
restockItemId.value = itemId; |
|
|
restockModal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function closeRestockModal() { |
|
|
restockModal.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function handleAddItem(e) { |
|
|
e.preventDefault(); |
|
|
|
|
|
const name = document.getElementById('itemName').value.trim(); |
|
|
const category = document.getElementById('itemCategory').value; |
|
|
const notes = document.getElementById('itemNotes').value.trim(); |
|
|
|
|
|
const newItem = { |
|
|
name, |
|
|
category, |
|
|
notes, |
|
|
dateAdded: new Date().toISOString() |
|
|
}; |
|
|
|
|
|
|
|
|
items.unshift(newItem); |
|
|
|
|
|
|
|
|
if (!recentItems.some(item => item.name === newItem.name && item.dateAdded === newItem.dateAdded)) { |
|
|
recentItems.unshift(newItem); |
|
|
|
|
|
|
|
|
if (recentItems.length > 5) { |
|
|
recentItems = recentItems.slice(0, 5); |
|
|
} |
|
|
} |
|
|
|
|
|
saveItems(); |
|
|
renderItems(); |
|
|
updateStats(); |
|
|
closeAddItemModal(); |
|
|
|
|
|
|
|
|
const successIcon = document.createElement('div'); |
|
|
successIcon.className = 'fixed bottom-6 right-6 w-16 h-16 rounded-full bg-emerald-500 flex items-center justify-center text-white text-2xl shadow-lg pulse'; |
|
|
successIcon.innerHTML = '<i class="fas fa-check"></i>'; |
|
|
document.body.appendChild(successIcon); |
|
|
|
|
|
setTimeout(() => { |
|
|
successIcon.classList.remove('pulse'); |
|
|
setTimeout(() => { |
|
|
successIcon.remove(); |
|
|
}, 300); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
|
|
|
function handleRestock() { |
|
|
const itemId = restockItemId.value; |
|
|
const restockedItem = items[itemId]; |
|
|
|
|
|
|
|
|
items.splice(itemId, 1); |
|
|
|
|
|
|
|
|
if (!recentItems.some(item => item.name === restockedItem.name && item.dateAdded === restockedItem.dateAdded)) { |
|
|
recentItems.unshift(restockedItem); |
|
|
|
|
|
|
|
|
if (recentItems.length > 5) { |
|
|
recentItems = recentItems.slice(0, 5); |
|
|
} |
|
|
} |
|
|
|
|
|
saveItems(); |
|
|
renderItems(); |
|
|
updateStats(); |
|
|
closeRestock |
|
|
</html> |