uas-web-22412001/client/js/script.js
2025-07-15 14:15:58 +07:00

372 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: parseFloat(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: parseFloat(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-gray-800 truncate" title="${
item.name
}">
${item.name}
</h3>
<div class="flex items-center justify-between mt-2">
<span class="text-2xl font-bold text-green-600 price-display">
$${item.price.toFixed(2)}
</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-600">Quantity:</span>
<span class="text-lg font-semibold text-gray-800">
${item.qty} units
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600">Total Value:</span>
<span class="text-lg font-semibold text-blue-600 price-display">
$${(item.price * item.qty).toFixed(2)}
</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 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 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();
});