|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ElectroCatalog - Electrical & Electronics Inventory</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> |
|
|
|
|
|
.product-card { |
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
|
|
} |
|
|
.product-card:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
.gradient-bg { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
} |
|
|
.gradient-text { |
|
|
background-clip: text; |
|
|
-webkit-background-clip: text; |
|
|
color: transparent; |
|
|
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar { |
|
|
width: 8px; |
|
|
} |
|
|
::-webkit-scrollbar-track { |
|
|
background: #f1f1f1; |
|
|
} |
|
|
::-webkit-scrollbar-thumb { |
|
|
background: #888; |
|
|
border-radius: 4px; |
|
|
} |
|
|
::-webkit-scrollbar-thumb:hover { |
|
|
background: #555; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
|
|
|
<header class="gradient-bg text-white shadow-lg"> |
|
|
<div class="container mx-auto px-4 py-6"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i class="fas fa-bolt text-2xl"></i> |
|
|
<h1 class="text-2xl font-bold">ElectroCatalog</h1> |
|
|
</div> |
|
|
<nav class="hidden md:flex space-x-6"> |
|
|
<a href="#" class="hover:text-gray-200 transition" onclick="showView('dashboard')">Dashboard</a> |
|
|
<a href="#" class="hover:text-gray-200 transition" onclick="showView('all-products')">All Products</a> |
|
|
<a href="#" class="hover:text-gray-200 transition" onclick="showView('add-product')">Add Product</a> |
|
|
<a href="#" class="hover:text-gray-200 transition" onclick="showView('boxes')">Boxes</a> |
|
|
</nav> |
|
|
<button class="md:hidden text-xl" onclick="toggleMobileMenu()"> |
|
|
<i class="fas fa-bars"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="mobileMenu" class="hidden md:hidden bg-indigo-800 px-4 py-2"> |
|
|
<div class="flex flex-col space-y-3"> |
|
|
<a href="#" class="text-white hover:text-gray-200 transition" onclick="showView('dashboard')">Dashboard</a> |
|
|
<a href="#" class="text-white hover:text-gray-200 transition" onclick="showView('all-products')">All Products</a> |
|
|
<a href="#" class="text-white hover:text-gray-200 transition" onclick="showView('add-product')">Add Product</a> |
|
|
<a href="#" class="text-white hover:text-gray-200 transition" onclick="showView('boxes')">Boxes</a> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<div id="dashboard" class="view"> |
|
|
<div class="flex justify-between items-center mb-8"> |
|
|
<h2 class="text-2xl font-bold gradient-text">Dashboard</h2> |
|
|
<div class="relative"> |
|
|
<input type="text" placeholder="Quick search..." class="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" id="quickSearch"> |
|
|
<button class="absolute right-3 top-2 text-gray-500 hover:text-indigo-600"> |
|
|
<i class="fas fa-search"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> |
|
|
<div class="bg-white rounded-lg shadow p-6"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<p class="text-gray-500">Total Products</p> |
|
|
<h3 class="text-3xl font-bold" id="totalProducts">0</h3> |
|
|
</div> |
|
|
<div class="p-3 rounded-full bg-indigo-100 text-indigo-600"> |
|
|
<i class="fas fa-boxes text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-lg shadow p-6"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<p class="text-gray-500">Categories</p> |
|
|
<h3 class="text-3xl font-bold" id="totalCategories">0</h3> |
|
|
</div> |
|
|
<div class="p-3 rounded-full bg-green-100 text-green-600"> |
|
|
<i class="fas fa-tags text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-lg shadow p-6"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div> |
|
|
<p class="text-gray-500">Boxes Used</p> |
|
|
<h3 class="text-3xl font-bold" id="totalBoxes">0</h3> |
|
|
</div> |
|
|
<div class="p-3 rounded-full bg-purple-100 text-purple-600"> |
|
|
<i class="fas fa-archive text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<h3 class="text-xl font-semibold mb-4">Browse Categories</h3> |
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-8"> |
|
|
<div class="category-card bg-white rounded-lg shadow overflow-hidden cursor-pointer transition hover:shadow-lg" onclick="filterByCategory('cables')"> |
|
|
<div class="h-2 bg-blue-500"></div> |
|
|
<div class="p-4"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<div class="p-2 rounded-full bg-blue-100 text-blue-600"> |
|
|
<i class="fas fa-plug"></i> |
|
|
</div> |
|
|
<h4 class="font-semibold">Cables</h4> |
|
|
</div> |
|
|
<p class="text-gray-500 text-sm mt-2">Power cables, data cables, connectors</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="category-card bg-white rounded-lg shadow overflow-hidden cursor-pointer transition hover:shadow-lg" onclick="filterByCategory('components')"> |
|
|
<div class="h-2 bg-green-500"></div> |
|
|
<div class="p-4"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<div class="p-2 rounded-full bg-green-100 text-green-600"> |
|
|
<i class="fas fa-microchip"></i> |
|
|
</div> |
|
|
<h4 class="font-semibold">Components</h4> |
|
|
</div> |
|
|
<p class="text-gray-500 text-sm mt-2">Resistors, capacitors, ICs, transistors</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="category-card bg-white rounded-lg shadow overflow-hidden cursor-pointer transition hover:shadow-lg" onclick="filterByCategory('sensors')"> |
|
|
<div class="h-2 bg-purple-500"></div> |
|
|
<div class="p-4"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<div class="p-2 rounded-full bg-purple-100 text-purple-600"> |
|
|
<i class="fas fa-sensor"></i> |
|
|
</div> |
|
|
<h4 class="font-semibold">Sensors</h4> |
|
|
</div> |
|
|
<p class="text-gray-500 text-sm mt-2">Temperature, motion, light, pressure sensors</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<h3 class="text-xl font-semibold mb-4">Recently Added Products</h3> |
|
|
<div class="bg-white rounded-lg shadow overflow-hidden"> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Product</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Category</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Quantity</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Box #</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="recentProductsTable"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="all-products" class="view hidden"> |
|
|
<div class="flex justify-between items-center mb-8"> |
|
|
<h2 class="text-2xl font-bold gradient-text">All Products</h2> |
|
|
<div class="flex space-x-2"> |
|
|
<div class="relative"> |
|
|
<input type="text" placeholder="Search products..." class="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" id="productSearch"> |
|
|
<button class="absolute right-3 top-2 text-gray-500 hover:text-indigo-600"> |
|
|
<i class="fas fa-search"></i> |
|
|
</button> |
|
|
</div> |
|
|
<select id="productFilter" class="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> |
|
|
<option value="all">All Categories</option> |
|
|
<option value="cables">Cables</option> |
|
|
<option value="components">Components</option> |
|
|
<option value="sensors">Sensors</option> |
|
|
</select> |
|
|
<select id="boxFilter" class="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> |
|
|
<option value="all">All Boxes</option> |
|
|
|
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" id="allProductsGrid"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="add-product" class="view hidden"> |
|
|
<h2 class="text-2xl font-bold gradient-text mb-8">Add New Product</h2> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-6 max-w-3xl mx-auto"> |
|
|
<form id="addProductForm"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div> |
|
|
<label for="productName" class="block text-sm font-medium text-gray-700 mb-1">Product Name*</label> |
|
|
<input type="text" id="productName" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required> |
|
|
</div> |
|
|
<div> |
|
|
<label for="productCategory" class="block text-sm font-medium text-gray-700 mb-1">Category*</label> |
|
|
<select id="productCategory" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required> |
|
|
<option value="">Select a category</option> |
|
|
<option value="cables">Cables</option> |
|
|
<option value="components">Components</option> |
|
|
<option value="sensors">Sensors</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label for="productQuantity" class="block text-sm font-medium text-gray-700 mb-1">Quantity*</label> |
|
|
<input type="number" id="productQuantity" min="1" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required> |
|
|
</div> |
|
|
<div> |
|
|
<label for="productBox" class="block text-sm font-medium text-gray-700 mb-1">Box Number*</label> |
|
|
<input type="text" id="productBox" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required> |
|
|
</div> |
|
|
<div class="md:col-span-2"> |
|
|
<label for="productDescription" class="block text-sm font-medium text-gray-700 mb-1">Description</label> |
|
|
<textarea id="productDescription" rows="3" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"></textarea> |
|
|
</div> |
|
|
<div> |
|
|
<label for="productSupplier" class="block text-sm font-medium text-gray-700 mb-1">Supplier</label> |
|
|
<input type="text" id="productSupplier" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> |
|
|
</div> |
|
|
<div> |
|
|
<label for="productPrice" class="block text-sm font-medium text-gray-700 mb-1">Unit Price ($)</label> |
|
|
<input type="number" step="0.01" id="productPrice" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-8 flex justify-end space-x-4"> |
|
|
<button type="reset" class="px-6 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-100 transition">Reset</button> |
|
|
<button type="submit" class="px-6 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition">Add Product</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="boxes" class="view hidden"> |
|
|
<h2 class="text-2xl font-bold gradient-text mb-8">Box Inventory</h2> |
|
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" id="boxesGrid"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="productModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<h3 class="text-xl font-bold" id="modalProductName">Product Name</h3> |
|
|
<button onclick="closeModal()" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div> |
|
|
<div class="h-48 rounded-lg mb-4" id="modalProductGradient"></div> |
|
|
<div class="flex space-x-4"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Category</p> |
|
|
<p class="font-medium" id="modalProductCategory">Cables</p> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Box #</p> |
|
|
<p class="font-medium" id="modalProductBox">B-12</p> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Quantity</p> |
|
|
<p class="font-medium" id="modalProductQuantity">25</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<h4 class="font-semibold mb-2">Description</h4> |
|
|
<p class="text-gray-700 mb-4" id="modalProductDescription">No description provided.</p> |
|
|
|
|
|
<div class="grid grid-cols-2 gap-4 mb-4"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Supplier</p> |
|
|
<p class="font-medium" id="modalProductSupplier">-</p> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Unit Price</p> |
|
|
<p class="font-medium" id="modalProductPrice">-</p> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Added On</p> |
|
|
<p class="font-medium" id="modalProductDate">-</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6 pt-6 border-t border-gray-200 flex justify-between"> |
|
|
<div class="flex space-x-2"> |
|
|
<button onclick="decrementQuantity()" class="px-4 py-2 bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition"> |
|
|
<i class="fas fa-minus"></i> Decrease |
|
|
</button> |
|
|
<button onclick="incrementQuantity()" class="px-4 py-2 bg-green-100 text-green-600 rounded-lg hover:bg-green-200 transition"> |
|
|
<i class="fas fa-plus"></i> Increase |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex space-x-2"> |
|
|
<button onclick="showEditForm()" class="px-4 py-2 bg-blue-100 text-blue-600 rounded-lg hover:bg-blue-200 transition"> |
|
|
<i class="fas fa-edit"></i> Edit |
|
|
</button> |
|
|
<button onclick="deleteProduct()" class="px-4 py-2 bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition"> |
|
|
<i class="fas fa-trash"></i> Delete |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="editForm" class="mt-6 pt-6 border-t border-gray-200 hidden"> |
|
|
<h4 class="font-semibold mb-4">Edit Product</h4> |
|
|
<form id="editProductForm"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Product Name</label> |
|
|
<input type="text" id="editProductName" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Category</label> |
|
|
<select id="editProductCategory" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
<option value="cables">Cables</option> |
|
|
<option value="components">Components</option> |
|
|
<option value="sensors">Sensors</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Quantity</label> |
|
|
<input type="number" id="editProductQuantity" min="1" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Box Number</label> |
|
|
<input type="text" id="editProductBox" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
</div> |
|
|
<div class="md:col-span-2"> |
|
|
<label class="block text-sm text-gray-500 mb-1">Description</label> |
|
|
<textarea id="editProductDescription" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300"></textarea> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Supplier</label> |
|
|
<input type="text" id="editProductSupplier" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm text-gray-500 mb-1">Unit Price ($)</label> |
|
|
<input type="number" step="0.01" id="editProductPrice" class="w-full px-3 py-2 rounded-lg border border-gray-300"> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-4 flex justify-end space-x-2"> |
|
|
<button type="button" onclick="hideEditForm()" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-100 transition">Cancel</button> |
|
|
<button type="submit" class="px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition">Save Changes</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
|
|
|
<footer class="bg-gray-800 text-white py-8"> |
|
|
<div class="container mx-auto px-4"> |
|
|
<div class="flex flex-col md:flex-row justify-between items-center"> |
|
|
<div class="mb-4 md:mb-0"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i class="fas fa-bolt text-xl"></i> |
|
|
<h2 class="text-xl font-bold">ElectroCatalog</h2> |
|
|
</div> |
|
|
<p class="text-gray-400 mt-2">Your electrical and electronics inventory solution</p> |
|
|
</div> |
|
|
<div class="flex space-x-6"> |
|
|
<a href="#" class="hover:text-indigo-400 transition">About</a> |
|
|
<a href="#" class="hover:text-indigo-400 transition">Contact</a> |
|
|
<a href="#" class="hover:text-indigo-400 transition">Privacy</a> |
|
|
<a href="#" class="hover:text-indigo-400 transition">Terms</a> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-8 pt-6 border-t border-gray-700 text-center text-gray-400 text-sm"> |
|
|
<p>© 2023 ElectroCatalog. All rights reserved.</p> |
|
|
</div> |
|
|
</div> |
|
|
</footer> |
|
|
|
|
|
<script> |
|
|
|
|
|
const dbName = 'electroCatalogDB'; |
|
|
let currentProductId = null; |
|
|
|
|
|
|
|
|
function initDB() { |
|
|
if (!localStorage.getItem(dbName)) { |
|
|
const initialData = { |
|
|
products: [ |
|
|
{ |
|
|
id: 1, |
|
|
name: "HDMI Cable 2.0", |
|
|
category: "cables", |
|
|
quantity: 15, |
|
|
box: "B-12", |
|
|
description: "High speed HDMI cable with Ethernet, 4K@60Hz support", |
|
|
supplier: "CableTech Inc.", |
|
|
price: 8.99, |
|
|
date: "2023-05-15", |
|
|
gradient: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)" |
|
|
}, |
|
|
{ |
|
|
id: 2, |
|
|
name: "Arduino Uno R3", |
|
|
category: "components", |
|
|
quantity: 8, |
|
|
box: "B-05", |
|
|
description: "Microcontroller board based on the ATmega328P", |
|
|
supplier: "Arduino LLC", |
|
|
price: 22.50, |
|
|
date: "2023-06-02", |
|
|
gradient: "linear-gradient(135deg, #f093fb 0%, #f5576c 100%)" |
|
|
}, |
|
|
{ |
|
|
id: 3, |
|
|
name: "DHT22 Sensor", |
|
|
category: "sensors", |
|
|
quantity: 12, |
|
|
box: "B-08", |
|
|
description: "Digital temperature and humidity sensor", |
|
|
supplier: "SensorWorld", |
|
|
price: 9.95, |
|
|
date: "2023-06-10", |
|
|
gradient: "linear-gradient(135deg, #5ee7df 0%, #b490ca 100%)" |
|
|
} |
|
|
], |
|
|
nextId: 4 |
|
|
}; |
|
|
localStorage.setItem(dbName, JSON.stringify(initialData)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getAllProducts() { |
|
|
const db = JSON.parse(localStorage.getItem(dbName)); |
|
|
return db.products; |
|
|
} |
|
|
|
|
|
|
|
|
function getProductById(id) { |
|
|
const db = JSON.parse(localStorage.getItem(dbName)); |
|
|
return db.products.find(product => product.id === id); |
|
|
} |
|
|
|
|
|
|
|
|
function addProduct(product) { |
|
|
const db = JSON.parse(localStorage.getItem(dbName)); |
|
|
const newProduct = { |
|
|
id: db.nextId++, |
|
|
...product, |
|
|
date: new Date().toISOString().split('T')[0], |
|
|
gradient: generateGradient() |
|
|
}; |
|
|
db.products.push(newProduct); |
|
|
localStorage.setItem(dbName, JSON.stringify(db)); |
|
|
return newProduct; |
|
|
} |
|
|
|
|
|
|
|
|
function updateProduct(id, updatedData) { |
|
|
const db = JSON.parse(localStorage.getItem(dbName)); |
|
|
const index = db.products.findIndex(product => product.id === id); |
|
|
if (index !== -1) { |
|
|
db.products[index] = { ...db.products[index], ...updatedData }; |
|
|
localStorage.setItem(dbName, JSON.stringify(db)); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
function deleteProductById(id) { |
|
|
const db = JSON.parse(localStorage.getItem(dbName)); |
|
|
const index = db.products.findIndex(product => product.id === id); |
|
|
if (index !== -1) { |
|
|
db.products.splice(index, 1); |
|
|
localStorage.setItem(dbName, JSON.stringify(db)); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
function generateGradient() { |
|
|
const gradients = [ |
|
|
"linear-gradient(135deg, #667eea 0%, #764ba2 100%)", |
|
|
"linear-gradient(135deg, #f093fb 0%, #f5576c 100%)", |
|
|
"linear-gradient(135deg, #5ee7df 0%, #b490ca 100%)", |
|
|
"linear-gradient(135deg, #f6d365 0%, #fda085 100%)", |
|
|
"linear-gradient(135deg, #c471f5 0%, #fa71cd 100%)", |
|
|
"linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)" |
|
|
]; |
|
|
return gradients[Math.floor(Math.random() * gradients.length)]; |
|
|
} |
|
|
|
|
|
|
|
|
function getUniqueBoxes() { |
|
|
const products = getAllProducts(); |
|
|
const boxes = [...new Set(products.map(product => product.box))]; |
|
|
return boxes.sort(); |
|
|
} |
|
|
|
|
|
|
|
|
function filterByCategory(category) { |
|
|
showView('all-products'); |
|
|
document.getElementById('productFilter').value = category; |
|
|
renderAllProducts(); |
|
|
} |
|
|
|
|
|
|
|
|
function showView(viewId) { |
|
|
document.querySelectorAll('.view').forEach(view => { |
|
|
view.classList.add('hidden'); |
|
|
}); |
|
|
document.getElementById(viewId).classList.remove('hidden'); |
|
|
hideMobileMenu(); |
|
|
|
|
|
|
|
|
document.querySelectorAll('nav a').forEach(link => { |
|
|
link.classList.remove('font-semibold', 'border-b-2', 'border-white'); |
|
|
}); |
|
|
|
|
|
|
|
|
switch(viewId) { |
|
|
case 'dashboard': |
|
|
document.querySelector('nav a:nth-child(1)').classList.add('font-semibold', 'border-b-2', 'border-white'); |
|
|
updateDashboardStats(); |
|
|
renderRecentProducts(); |
|
|
break; |
|
|
case 'all-products': |
|
|
document.querySelector('nav a:nth-child(2)').classList.add('font-semibold', 'border-b-2', 'border-white'); |
|
|
renderAllProducts(); |
|
|
updateBoxFilterOptions(); |
|
|
break; |
|
|
case 'add-product': |
|
|
document.querySelector('nav a:nth-child(3)').classList.add('font-semibold', 'border-b-2', 'border-white'); |
|
|
break; |
|
|
case 'boxes': |
|
|
document.querySelector('nav a:nth-child(4)').classList.add('font-semibold', 'border-b-2', 'border-white'); |
|
|
renderBoxesView(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleMobileMenu() { |
|
|
const menu = document.getElementById('mobileMenu'); |
|
|
menu.classList.toggle('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function hideMobileMenu() { |
|
|
document.getElementById('mobileMenu').classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function updateDashboardStats() { |
|
|
const products = getAllProducts(); |
|
|
const categories = new Set(products.map(product => product.category)); |
|
|
const boxes = new Set(products.map(product => product.box)); |
|
|
|
|
|
document.getElementById('totalProducts').textContent = products.length; |
|
|
document.getElementById('totalCategories').textContent = categories.size; |
|
|
document.getElementById('totalBoxes').textContent = boxes.size; |
|
|
} |
|
|
|
|
|
|
|
|
function renderRecentProducts() { |
|
|
const products = getAllProducts().slice(-5).reverse(); |
|
|
const tableBody = document.getElementById('recentProductsTable'); |
|
|
tableBody.innerHTML = ''; |
|
|
|
|
|
if (products.length === 0) { |
|
|
tableBody.innerHTML = '<tr><td colspan="5" class="px-6 py-4 text-center text-gray-500">No products found</td></tr>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
products.forEach(product => { |
|
|
const row = document.createElement('tr'); |
|
|
row.className = 'hover:bg-gray-50'; |
|
|
row.innerHTML = ` |
|
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
|
<div class="flex items-center"> |
|
|
<div class="flex-shrink-0 h-10 w-10 rounded" style="background: ${product.gradient}"></div> |
|
|
<div class="ml-4"> |
|
|
<div class="text-sm font-medium text-gray-900">${product.name}</div> |
|
|
</div> |
|
|
</div> |
|
|
</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-${getCategoryColor(product.category)}-100 text-${getCategoryColor(product.category)}-800 capitalize"> |
|
|
${product.category} |
|
|
</span> |
|
|
</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${product.quantity}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${product.box}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> |
|
|
<button onclick="openProductModal(${product.id})" class="text-indigo-600 hover:text-indigo-900 mr-3">View</button> |
|
|
<button onclick="editProduct(${product.id})" class="text-blue-600 hover:text-blue-900">Edit</button> |
|
|
</td> |
|
|
`; |
|
|
tableBody.appendChild(row); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderAllProducts() { |
|
|
const searchTerm = document.getElementById('productSearch').value.toLowerCase(); |
|
|
const categoryFilter = document.getElementById('productFilter').value; |
|
|
const boxFilter = document.getElementById('boxFilter').value; |
|
|
|
|
|
const products = getAllProducts().filter(product => { |
|
|
const matchesSearch = product.name.toLowerCase().includes(searchTerm) || |
|
|
(product.description && product.description.toLowerCase().includes(searchTerm)); |
|
|
const matchesCategory = categoryFilter === 'all' || product.category === categoryFilter; |
|
|
const matchesBox = boxFilter === 'all' || product.box === boxFilter; |
|
|
|
|
|
return matchesSearch && matchesCategory && matchesBox; |
|
|
}); |
|
|
|
|
|
const productsGrid = document.getElementById('allProductsGrid'); |
|
|
productsGrid.innerHTML = ''; |
|
|
|
|
|
if (products.length === 0) { |
|
|
productsGrid.innerHTML = '<div class="col-span-full text-center py-10 text-gray-500">No products match your criteria</div>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
products.forEach(product => { |
|
|
const card = document.createElement('div'); |
|
|
card.className = 'product-card bg-white rounded-lg shadow overflow-hidden'; |
|
|
card.innerHTML = ` |
|
|
<div class="h-32" style="background: ${product.gradient}"></div> |
|
|
<div class="p-4"> |
|
|
<h3 class="font-semibold text-lg mb-1 truncate">${product.name}</h3> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<span class="text-xs px-2 py-1 rounded-full bg-${getCategoryColor(product.category)}-100 text-${getCategoryColor(product.category)}-800 capitalize">${product.category}</span> |
|
|
<span class="text-sm font-medium">Qty: ${product.quantity}</span> |
|
|
</div> |
|
|
<div class="flex justify-between items-center text-sm text-gray-600"> |
|
|
<span>Box: ${product.box}</span> |
|
|
<button onclick="openProductModal(${product.id})" class="text-indigo-600 hover:text-indigo-800 font-medium">Details</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
productsGrid.appendChild(card); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderBoxesView() { |
|
|
const boxes = getUniqueBoxes(); |
|
|
const products = getAllProducts(); |
|
|
const boxesGrid = document.getElementById('boxesGrid'); |
|
|
boxesGrid.innerHTML = ''; |
|
|
|
|
|
if (boxes.length === 0) { |
|
|
boxesGrid.innerHTML = '<div class="col-span-full text-center py-10 text-gray-500">No boxes found</div>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
boxes.forEach(box => { |
|
|
const boxProducts = products.filter(product => product.box === box); |
|
|
const boxTotalItems = boxProducts.reduce((sum, product) => sum + product.quantity, 0); |
|
|
const boxCategories = [...new Set(boxProducts.map(product => product.category))]; |
|
|
|
|
|
const boxCard = document.createElement('div'); |
|
|
boxCard.className = 'product-card bg-white rounded-lg shadow overflow-hidden'; |
|
|
boxCard.innerHTML = ` |
|
|
<div class="h-32" style="background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)"></div> |
|
|
<div class="p-4"> |
|
|
<h3 class="font-semibold text-lg mb-1">Box ${box}</h3> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<span class="text-sm text-gray-600">${boxTotalItems} items</span> |
|
|
<span class="text-sm text-gray-600">${boxProducts.length} products</span> |
|
|
</div> |
|
|
<div class="mb-3"> |
|
|
<p class="text-sm text-gray-600 mb-1">Categories:</p> |
|
|
<div class="flex flex-wrap gap-1"> |
|
|
${boxCategories.map(cat => `<span class="text-xs px-2 py-1 rounded-full bg-${getCategoryColor(cat)}-100 text-${getCategoryColor(cat)}-800 capitalize">${cat}</span>`).join('')} |
|
|
</div> |
|
|
</div> |
|
|
<button onclick="showBoxContents('${box}')" class="w-full py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">View Contents</button> |
|
|
</div> |
|
|
`; |
|
|
boxesGrid.appendChild(boxCard); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function showBoxContents(boxNumber) { |
|
|
const products = getAllProducts().filter(product => product.box === boxNumber); |
|
|
showView('all-products'); |
|
|
document.getElementById('boxFilter').value = boxNumber; |
|
|
renderAllProducts(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateBoxFilterOptions() { |
|
|
const boxFilter = document.getElementById('boxFilter'); |
|
|
const currentValue = boxFilter.value; |
|
|
|
|
|
boxFilter.innerHTML = '<option value="all">All Boxes</option>'; |
|
|
const boxes = getUniqueBoxes(); |
|
|
|
|
|
boxes.forEach(box => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = box; |
|
|
option.textContent = box; |
|
|
boxFilter.appendChild(option); |
|
|
}); |
|
|
|
|
|
|
|
|
if (boxes.includes(currentValue)) { |
|
|
boxFilter.value = currentValue; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getCategoryColor(category) { |
|
|
switch(category) { |
|
|
case 'cables': return 'blue'; |
|
|
case 'components': return 'green'; |
|
|
case 'sensors': return 'purple'; |
|
|
default: return 'gray'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function openProductModal(id) { |
|
|
const product = getProductById(id); |
|
|
if (!product) return; |
|
|
|
|
|
currentProductId = id; |
|
|
|
|
|
document.getElementById('modalProductName').textContent = product.name; |
|
|
document.getElementById('modalProductCategory').textContent = product.category; |
|
|
document.getElementById('modalProductBox').textContent = product.box; |
|
|
document.getElementById('modalProductQuantity').textContent = product.quantity; |
|
|
document.getElementById('modalProductDescription').textContent = product.description || 'No description provided.'; |
|
|
document.getElementById('modalProductSupplier').textContent = product.supplier || '-'; |
|
|
document.getElementById('modalProductPrice').textContent = product.price ? `$${product.price.toFixed(2)}` : '-'; |
|
|
document.getElementById('modalProductDate').textContent = product.date; |
|
|
|
|
|
const gradientElement = document.getElementById('modalProductGradient'); |
|
|
gradientElement.style.background = product.gradient; |
|
|
|
|
|
|
|
|
document.getElementById('editForm').classList.add('hidden'); |
|
|
|
|
|
|
|
|
document.getElementById('productModal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function closeModal() { |
|
|
document.getElementById('productModal').classList.add('hidden'); |
|
|
currentProductId = null; |
|
|
} |
|
|
|
|
|
|
|
|
function showEditForm() { |
|
|
const product = getProductById(currentProductId); |
|
|
if (!product) return; |
|
|
|
|
|
document.getElementById('editProductName').value = product.name; |
|
|
document.getElementById('editProductCategory').value = product.category; |
|
|
document.getElementById('editProductQuantity').value = product.quantity; |
|
|
document.getElementById('editProductBox').value = product.box; |
|
|
document.getElementById('editProductDescription').value = product.description || ''; |
|
|
document.getElementById('editProductSupplier').value = product.supplier || ''; |
|
|
document.getElementById('editProductPrice').value = product.price || ''; |
|
|
|
|
|
document.getElementById('editForm').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function hideEditForm() { |
|
|
document.getElementById('editForm').classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function incrementQuantity() { |
|
|
const product = getProductById(currentProductId); |
|
|
if (!product) return; |
|
|
|
|
|
const newQuantity = product.quantity + 1; |
|
|
updateProduct(currentProductId, { quantity: newQuantity }); |
|
|
document.getElementById('modalProductQuantity').textContent = newQuantity; |
|
|
|
|
|
|
|
|
renderRecentProducts(); |
|
|
renderAllProducts(); |
|
|
updateDashboardStats(); |
|
|
} |
|
|
|
|
|
|
|
|
function decrementQuantity() { |
|
|
const product = getProductById(currentProductId); |
|
|
if (!product) return; |
|
|
|
|
|
const newQuantity = Math.max(0, product.quantity - 1); |
|
|
updateProduct(currentProductId, { quantity: newQuantity }); |
|
|
document.getElementById('modalProductQuantity').textContent = newQuantity; |
|
|
|
|
|
|
|
|
renderRecentProducts(); |
|
|
renderAllProducts(); |
|
|
updateDashboardStats(); |
|
|
} |
|
|
|
|
|
|
|
|
function deleteProduct() { |
|
|
if (!confirm('Are you sure you want to delete this product?')) return; |
|
|
|
|
|
if (deleteProductById(currentProductId)) { |
|
|
closeModal(); |
|
|
|
|
|
|
|
|
renderRecentProducts(); |
|
|
renderAllProducts(); |
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
|
alert('Product deleted successfully!'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initApp() { |
|
|
initDB(); |
|
|
showView('dashboard'); |
|
|
|
|
|
|
|
|
document.getElementById('addProductForm').addEventListener('submit', function(e) { |
|
|
e.preventDefault(); |
|
|
|
|
|
const product = { |
|
|
name: document.getElementById('productName').value, |
|
|
category: document.getElementById('productCategory').value, |
|
|
quantity: parseInt(document.getElementById('productQuantity').value), |
|
|
box: document.getElementById('productBox').value, |
|
|
description: document.getElementById('productDescription').value, |
|
|
supplier: document.getElementById('productSupplier').value, |
|
|
price: parseFloat(document.getElementById('productPrice').value) || null |
|
|
}; |
|
|
|
|
|
addProduct(product); |
|
|
this.reset(); |
|
|
|
|
|
|
|
|
renderRecentProducts(); |
|
|
renderAllProducts(); |
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
|
alert('Product added successfully!'); |
|
|
}); |
|
|
|
|
|
document.getElementById('editProductForm').addEventListener('submit', function(e) { |
|
|
e.preventDefault(); |
|
|
|
|
|
const updatedData = { |
|
|
name: document.getElementById('editProductName').value, |
|
|
category: document.getElementById('editProductCategory').value, |
|
|
quantity: parseInt(document.getElementById('editProductQuantity').value), |
|
|
box: document.getElementById('editProductBox').value, |
|
|
description: document.getElementById('editProductDescription').value, |
|
|
supplier: document.getElementById('editProductSupplier').value, |
|
|
price: parseFloat(document.getElementById('editProductPrice').value) || null |
|
|
}; |
|
|
|
|
|
if (updateProduct(currentProductId, updatedData)) { |
|
|
|
|
|
openProductModal(currentProductId); |
|
|
|
|
|
|
|
|
renderRecentProducts(); |
|
|
renderAllProducts(); |
|
|
updateDashboardStats(); |
|
|
|
|
|
|
|
|
alert('Product updated successfully!'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('productSearch').addEventListener('input', renderAllProducts); |
|
|
document.getElementById('productFilter').addEventListener('change', renderAllProducts); |
|
|
document.getElementById('boxFilter').addEventListener('change', renderAllProducts); |
|
|
|
|
|
|
|
|
document.getElementById('quickSearch').addEventListener('keyup', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
showView('all-products'); |
|
|
document.getElementById('productSearch').value = this.value; |
|
|
renderAllProducts(); |
|
|
this.value = ''; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initApp); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=mukoshi/catalog" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |