376 lines
11 KiB
JavaScript
376 lines
11 KiB
JavaScript
// Inventory Management System
|
|
import ky from "https://cdn.jsdelivr.net/npm/ky@1.8.1/+esm";
|
|
|
|
class InventoryManager {
|
|
constructor() {
|
|
this.baseUrl = "http://localhost:11000/api"; // Replace with your Bruno API endpoint
|
|
this.client = ky.extend({
|
|
prefixUrl: this.baseUrl,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
timeout: 10000,
|
|
retry: 2,
|
|
});
|
|
|
|
this.items = [];
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
this.bindEvents();
|
|
await this.loadItems();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Add item form
|
|
document.getElementById("addItemForm").addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
this.addItem();
|
|
});
|
|
|
|
// Edit modal events
|
|
document.getElementById("editItemForm").addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
this.updateItem();
|
|
});
|
|
|
|
document.getElementById("cancelEdit").addEventListener("click", () => {
|
|
this.closeEditModal();
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
document.getElementById("editModal").addEventListener("click", (e) => {
|
|
if (e.target.id === "editModal") {
|
|
this.closeEditModal();
|
|
}
|
|
});
|
|
|
|
// Close modal on escape key
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape") {
|
|
this.closeEditModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
async loadItems() {
|
|
try {
|
|
this.showLoading(true);
|
|
this.hideMessages();
|
|
|
|
const response = await this.client.get("read");
|
|
const result = await response.json();
|
|
|
|
if (result.status === "success") {
|
|
this.items = result;
|
|
this.renderItems();
|
|
this.showLoading(false);
|
|
} else {
|
|
this.showLoading(false);
|
|
this.showError(result.message || "Failed to load items");
|
|
}
|
|
} catch (error) {
|
|
this.showLoading(false);
|
|
this.showError(
|
|
"Failed to load inventory items. Please check your connection."
|
|
);
|
|
console.error("Error loading items:", error);
|
|
}
|
|
}
|
|
|
|
async addItem() {
|
|
const form = document.getElementById("addItemForm");
|
|
const formData = new FormData(form);
|
|
|
|
const newItem = {
|
|
name: formData.get("name").trim(),
|
|
price: parseInt(formData.get("price")),
|
|
qty: parseInt(formData.get("qty")),
|
|
};
|
|
|
|
// Validation
|
|
if (!newItem.name || newItem.price < 0 || newItem.qty < 0) {
|
|
this.showError("Please fill in all fields with valid values.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading(true);
|
|
this.hideMessages();
|
|
|
|
const response = await this.client.post("create", {
|
|
json: newItem,
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === "success") {
|
|
// Add the new item with the ID returned from the API
|
|
this.items.data.push(result.data);
|
|
|
|
this.renderItems();
|
|
form.reset();
|
|
this.showSuccess(result.message || "Item added successfully!");
|
|
this.showLoading(false);
|
|
} else {
|
|
this.showLoading(false);
|
|
this.showError(result.message || "Failed to add item");
|
|
}
|
|
} catch (error) {
|
|
this.showLoading(false);
|
|
this.showError("Failed to add item. Please try again.");
|
|
console.error("Error adding item:", error);
|
|
}
|
|
}
|
|
|
|
async updateItem() {
|
|
const id = document.getElementById("editItemId").value;
|
|
const form = document.getElementById("editItemForm");
|
|
const formData = new FormData(form);
|
|
|
|
const updatedItem = {
|
|
id: parseInt(id),
|
|
name: formData.get("name").trim(),
|
|
price: parseInt(formData.get("price")),
|
|
qty: parseInt(formData.get("qty")),
|
|
};
|
|
|
|
// Validation
|
|
if (!updatedItem.name || updatedItem.price < 0 || updatedItem.qty < 0) {
|
|
this.showError("Please fill in all fields with valid values.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading(true);
|
|
this.hideMessages();
|
|
|
|
const response = await this.client.put(`update`, {
|
|
json: updatedItem,
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === "success") {
|
|
const index = this.items.data.findIndex(
|
|
(item) => item.id === parseInt(id)
|
|
);
|
|
if (index !== -1) {
|
|
this.items.data[index] = result.data;
|
|
}
|
|
|
|
this.renderItems();
|
|
this.closeEditModal();
|
|
this.showSuccess(result.message || "Item updated successfully!");
|
|
this.showLoading(false);
|
|
} else {
|
|
this.showLoading(false);
|
|
this.showError(result.message || "Failed to update item");
|
|
}
|
|
} catch (error) {
|
|
this.showLoading(false);
|
|
this.showError("Failed to update item. Please try again.");
|
|
console.error("Error updating item:", error);
|
|
}
|
|
}
|
|
|
|
async deleteItem(id) {
|
|
if (!confirm("Are you sure you want to delete this item?")) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading(true);
|
|
this.hideMessages();
|
|
|
|
const response = await this.client.delete(`delete`, {
|
|
json: { id: parseInt(id) },
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === "success") {
|
|
this.items.data = this.items.data.filter(
|
|
(item) => item.id !== parseInt(id)
|
|
);
|
|
this.renderItems();
|
|
this.showSuccess(result.message || "Item deleted successfully!");
|
|
this.showLoading(false);
|
|
} else {
|
|
this.showLoading(false);
|
|
this.showError(result.message || "Failed to delete item");
|
|
}
|
|
} catch (error) {
|
|
this.showLoading(false);
|
|
this.showError("Failed to delete item. Please try again.");
|
|
console.error("Error deleting item:", error);
|
|
}
|
|
}
|
|
|
|
openEditModal(item) {
|
|
document.getElementById("editItemId").value = item.id;
|
|
document.getElementById("editItemName").value = item.name;
|
|
document.getElementById("editItemPrice").value = item.price;
|
|
document.getElementById("editItemQty").value = item.qty;
|
|
|
|
document.getElementById("editModal").classList.remove("hidden");
|
|
document.getElementById("editItemName").focus();
|
|
}
|
|
|
|
closeEditModal() {
|
|
document.getElementById("editModal").classList.add("hidden");
|
|
document.getElementById("editItemForm").reset();
|
|
}
|
|
|
|
renderItems() {
|
|
const grid = document.getElementById("inventoryGrid");
|
|
const emptyState = document.getElementById("emptyState");
|
|
|
|
if (!this.items.data || this.items.data.length === 0) {
|
|
grid.innerHTML = "";
|
|
emptyState.classList.remove("hidden");
|
|
return;
|
|
}
|
|
|
|
emptyState.classList.add("hidden");
|
|
|
|
const itemsHTML = this.items.data
|
|
.map((item) => this.createItemCard(item))
|
|
.join("");
|
|
grid.innerHTML = itemsHTML;
|
|
|
|
// Add event listeners for buttons
|
|
this.items.data.forEach((item) => {
|
|
const editBtn = document.getElementById(`edit-${item.id}`);
|
|
const deleteBtn = document.getElementById(`delete-${item.id}`);
|
|
|
|
if (editBtn) {
|
|
editBtn.addEventListener("click", () => this.openEditModal(item));
|
|
}
|
|
|
|
if (deleteBtn) {
|
|
deleteBtn.addEventListener("click", () => this.deleteItem(item.id));
|
|
}
|
|
});
|
|
}
|
|
|
|
createItemCard(item) {
|
|
const stockLevel = this.getStockLevel(item.qty);
|
|
const stockClass = this.getStockClass(stockLevel);
|
|
|
|
return `
|
|
<div class="inventory-card rounded-lg shadow-md overflow-hidden fade-in">
|
|
<div class="card-content">
|
|
<div class="card-header">
|
|
<h3 class="text-lg font-semibold text-white truncate" title="${
|
|
item.name
|
|
}">
|
|
${item.name}
|
|
</h3>
|
|
<div class="flex items-center justify-between mt-2">
|
|
<span class="text-2xl font-bold text-green-400 price-display">
|
|
$${item.price}
|
|
</span>
|
|
<span class="status-badge ${stockClass}">
|
|
${stockLevel}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm text-gray-400">Quantity:</span>
|
|
<span class="text-lg font-semibold text-gray-200">
|
|
${item.qty} units
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm text-gray-400">Total Value:</span>
|
|
<span class="text-lg font-semibold text-blue-400 price-display">
|
|
$${item.price * item.qty}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<div class="flex space-x-2">
|
|
<button id="edit-${item.id}"
|
|
class="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-black transition duration-200 btn-primary">
|
|
<span class="text-sm font-medium">Edit</span>
|
|
</button>
|
|
<button id="delete-${item.id}"
|
|
class="flex-1 bg-red-600 text-white py-2 px-4 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-black transition duration-200 btn-danger">
|
|
<span class="text-sm font-medium">Delete</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getStockLevel(qty) {
|
|
if (qty <= 5) return "Low Stock";
|
|
if (qty <= 20) return "Medium Stock";
|
|
return "High Stock";
|
|
}
|
|
|
|
getStockClass(stockLevel) {
|
|
switch (stockLevel) {
|
|
case "Low Stock":
|
|
return "stock-low";
|
|
case "Medium Stock":
|
|
return "stock-medium";
|
|
case "High Stock":
|
|
return "stock-high";
|
|
default:
|
|
return "stock-medium";
|
|
}
|
|
}
|
|
|
|
showLoading(show) {
|
|
const loading = document.getElementById("loading");
|
|
if (show) {
|
|
loading.classList.remove("hidden");
|
|
} else {
|
|
loading.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
const errorDiv = document.getElementById("errorMessage");
|
|
const errorText = document.getElementById("errorText");
|
|
|
|
errorText.textContent = message;
|
|
errorDiv.classList.remove("hidden");
|
|
|
|
setTimeout(() => {
|
|
errorDiv.classList.add("hidden");
|
|
}, 5000);
|
|
}
|
|
|
|
showSuccess(message) {
|
|
const successDiv = document.getElementById("successMessage");
|
|
const successText = document.getElementById("successText");
|
|
|
|
successText.textContent = message;
|
|
successDiv.classList.remove("hidden");
|
|
|
|
setTimeout(() => {
|
|
successDiv.classList.add("hidden");
|
|
}, 3000);
|
|
}
|
|
|
|
hideMessages() {
|
|
document.getElementById("errorMessage").classList.add("hidden");
|
|
document.getElementById("successMessage").classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
// Initialize the app when DOM is loaded
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
new InventoryManager();
|
|
});
|