version 2

This commit is contained in:
Flatlogic Bot 2025-11-22 17:22:34 +00:00
parent b6296eed55
commit 4d1d667390
5 changed files with 329 additions and 366 deletions

View File

@ -1,18 +1,9 @@
'''
// Main javascript file for Opulent POS
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', function () {
const page = document.body.dataset.page;
// --- Logic for Cashier Checkout Page ---
if (page === 'cashier_checkout') {
// --- Element Selectors ---
const barcodeInput = document.getElementById('barcode-scanner-input'); const barcodeInput = document.getElementById('barcode-scanner-input');
const productSearchInput = document.getElementById('product-search'); const productSearchInput = document.getElementById('product-search');
const productGrid = document.getElementById('product-grid'); const productGrid = document.getElementById('product-grid');
const productGridPlaceholder = document.getElementById('product-grid-placeholder');
const cartItemsContainer = document.getElementById('cart-items'); const cartItemsContainer = document.getElementById('cart-items');
const cartPlaceholder = document.getElementById('cart-placeholder');
const cartItemCount = document.getElementById('cart-item-count'); const cartItemCount = document.getElementById('cart-item-count');
const cartSubtotal = document.getElementById('cart-subtotal'); const cartSubtotal = document.getElementById('cart-subtotal');
const cartTax = document.getElementById('cart-tax'); const cartTax = document.getElementById('cart-tax');
@ -20,345 +11,304 @@ document.addEventListener('DOMContentLoaded', () => {
const completeSaleBtn = document.getElementById('complete-sale-btn'); const completeSaleBtn = document.getElementById('complete-sale-btn');
const cancelSaleBtn = document.getElementById('cancel-sale-btn'); const cancelSaleBtn = document.getElementById('cancel-sale-btn');
const printLastInvoiceBtn = document.getElementById('print-last-invoice-btn'); const printLastInvoiceBtn = document.getElementById('print-last-invoice-btn');
const cartPlaceholder = document.getElementById('cart-placeholder');
const productGridPlaceholder = document.getElementById('product-grid-placeholder');
// --- State Management --- let cart = [];
let cart = JSON.parse(localStorage.getItem('cart')) || {}; const TAX_RATE = 0.00;
// --- Utility Functions --- // =========================================================================
const debounce = (func, delay) => { // CORE FUNCTIONS
let timeout; // =========================================================================
return function(...args) {
const context = this; const showToast = (message, type = 'success') => {
clearTimeout(timeout); // A simple toast notification function (can be replaced with a library)
timeout = setTimeout(() => func.apply(context, args), delay); const toast = document.createElement('div');
}; toast.className = `toast show align-items-center text-white bg-${type} border-0`;
toast.innerHTML = `<div class="d-flex"><div class="toast-body">${message}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}; };
const formatCurrency = (amount) => `PKR ${parseFloat(amount).toFixed(2)}`; const searchProducts = (query, searchBy = 'name') => {
// --- API Communication ---
const searchProducts = async (query) => {
if (query.length < 2) { if (query.length < 2) {
productGrid.innerHTML = ''; productGrid.innerHTML = '';
productGridPlaceholder.style.display = 'block'; if (productGridPlaceholder) productGridPlaceholder.style.display = 'block';
return; return;
} }
try {
const response = await fetch(`api/search_products.php?q=${encodeURIComponent(query)}`);
if (!response.ok) throw new Error('Network response was not ok');
const products = await response.json();
renderProductGrid(products);
} catch (error) {
console.error('Error fetching products:', error);
productGrid.innerHTML = '<p class="text-danger col-12">Could not fetch products.</p>';
}
};
const findProductByBarcode = async (barcode) => { const url = searchBy === 'barcode'
try { ? `../api/search_products.php?barcode=${encodeURIComponent(query)}`
const response = await fetch(`api/search_products.php?q=${encodeURIComponent(barcode)}&exact=true`); : `../api/search_products.php?name=${encodeURIComponent(query)}`;
if (!response.ok) throw new Error('Network response was not ok');
const products = await response.json();
if (products.length > 0) {
addToCart(products[0]);
return true;
}
return false;
} catch (error) {
console.error('Error fetching product by barcode:', error);
return false;
}
};
// --- Rendering Functions --- fetch(url)
const renderProductGrid = (products) => { .then(response => response.json())
.then(products => {
if (productGridPlaceholder) productGridPlaceholder.style.display = 'none';
productGrid.innerHTML = ''; productGrid.innerHTML = '';
if (products.length === 0) { if (products.length > 0) {
productGridPlaceholder.innerHTML = '<p>No products found.</p>'; if (searchBy === 'barcode' && products.length === 1) {
productGridPlaceholder.style.display = 'block'; addToCart(products[0]);
return; barcodeInput.value = ''; // Clear input after successful scan
} barcodeInput.focus();
productGridPlaceholder.style.display = 'none'; } else {
products.forEach(product => { products.forEach(product => {
const productCard = document.createElement('div'); const productCard = `
productCard.className = 'col'; <div class="col">
productCard.innerHTML = ` <div class="card h-100 product-card" data-product-id="${product.id}">
<div class="card h-100 product-card" role="button" data-product-id="${product.id}" data-product-name="${product.name}" data-product-price="${product.price}" data-product-barcode="${product.barcode}">
<div class="card-body text-center"> <div class="card-body text-center">
<h6 class="card-title fs-sm">${product.name}</h6> <h6 class="card-title fs-sm">${product.name}</h6>
<p class="card-text text-muted small">${formatCurrency(product.price)}</p> <p class="card-text fw-bold">PKR ${parseFloat(product.price).toFixed(2)}</p>
</div> </div>
</div> </div>
`; </div>`;
productGrid.appendChild(productCard); productGrid.innerHTML += productCard;
});
}
} else {
if (productGridPlaceholder) productGridPlaceholder.style.display = 'block';
if (searchBy === 'barcode') {
showToast('Product not found for this barcode.', 'danger');
barcodeInput.value = '';
barcodeInput.focus();
}
}
})
.catch(error => {
console.error('Error searching products:', error);
showToast('Failed to search for products.', 'danger');
}); });
}; };
const renderCart = () => { const addToCart = (product) => {
cartItemsContainer.innerHTML = ''; const existingItem = cart.find(item => item.id === product.id);
let subtotal = 0; if (existingItem) {
let itemCount = 0; existingItem.quantity++;
if (Object.keys(cart).length === 0) {
cartItemsContainer.appendChild(cartPlaceholder);
} else { } else {
const table = document.createElement('table'); cart.push({ ...product, quantity: 1 });
table.className = 'table table-sm'; }
table.innerHTML = ` updateCartView();
showToast(`${product.name} added to cart.`, 'success');
};
const updateCartView = () => {
if (cart.length === 0) {
if (cartPlaceholder) cartPlaceholder.style.display = 'block';
cartItemsContainer.innerHTML = cartPlaceholder ? cartPlaceholder.outerHTML : '';
} else {
if (cartPlaceholder) cartPlaceholder.style.display = 'none';
const table = `
<table class="table table-sm table-striped">
<thead> <thead>
<tr> <tr>
<th>Item</th> <th>Item</th>
<th class="text-center">Qty</th> <th class="text-center">Qty</th>
<th class="text-end">Price</th> <th class="text-end">Price</th>
<th></th> <th class="text-end">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody>`; <tbody>
const tbody = table.querySelector('tbody'); ${cart.map(item => `
<tr>
for (const productId in cart) { <td class="w-50">${item.name}</td>
const item = cart[productId];
subtotal += item.price * item.quantity;
itemCount += item.quantity;
const row = document.createElement('tr');
row.innerHTML = `
<td>
<div class="fs-sm">${item.name}</div>
<small class="text-muted">${formatCurrency(item.price)}</small>
</td>
<td class="text-center"> <td class="text-center">
<div class="input-group input-group-sm" style="width: 90px; margin: auto;"> <input type="number" class="form-control form-control-sm quantity-input" data-product-id="${item.id}" value="${item.quantity}" min="1">
<button class="btn btn-outline-secondary btn-sm cart-quantity-change" data-product-id="${productId}" data-change="-1">-</button>
<input type="text" class="form-control text-center" value="${item.quantity}" readonly>
<button class="btn btn-outline-secondary btn-sm cart-quantity-change" data-product-id="${productId}" data-change="1">+</button>
</div>
</td> </td>
<td class="text-end fw-bold">${formatCurrency(item.price * item.quantity)}</td> <td class="text-end">PKR ${parseFloat(item.price).toFixed(2)}</td>
<td class="text-center"> <td class="text-end">
<button class="btn btn-sm btn-outline-danger cart-remove-item" data-product-id="${productId}"><i class="bi bi-trash"></i></button> <button class="btn btn-outline-danger btn-sm remove-item-btn" data-product-id="${item.id}">
&times;
</button>
</td> </td>
`; </tr>
tbody.appendChild(row); `).join('')}
} </tbody>
cartItemsContainer.appendChild(table); </table>`;
cartItemsContainer.innerHTML = table;
} }
updateCartTotal();
};
const tax = subtotal * 0; const updateCartTotal = () => {
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const tax = subtotal * TAX_RATE;
const total = subtotal + tax; const total = subtotal + tax;
cartSubtotal.textContent = formatCurrency(subtotal);
cartTax.textContent = formatCurrency(tax); cartSubtotal.textContent = `PKR ${subtotal.toFixed(2)}`;
cartTotal.textContent = formatCurrency(total); cartTax.textContent = `PKR ${tax.toFixed(2)}`;
cartItemCount.textContent = itemCount; cartTotal.textContent = `PKR ${total.toFixed(2)}`;
completeSaleBtn.disabled = itemCount === 0; cartItemCount.textContent = cart.reduce((sum, item) => sum + item.quantity, 0);
completeSaleBtn.disabled = cart.length === 0;
}; };
// --- Cart Logic --- const completeSale = () => {
const saveCart = () => localStorage.setItem('cart', JSON.stringify(cart)); if (cart.length === 0) return;
const addToCart = (product) => {
const id = product.id; fetch('../api/complete_sale.php', {
if (cart[id]) { method: 'POST',
cart[id].quantity++; headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cart: cart })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Sale completed successfully!', 'success');
saveInvoiceForOffline(data.sale_details);
printInvoice(data.sale_details);
cart = [];
updateCartView();
} else { } else {
cart[id] = { id: id, name: product.name, price: parseFloat(product.price), quantity: 1, barcode: product.barcode }; showToast(`Sale failed: ${data.message}`, 'danger');
} }
saveCart(); })
renderCart(); .catch(error => {
console.error('Error completing sale:', error);
showToast('An error occurred while completing the sale.', 'danger');
});
}; };
const updateCartQuantity = (productId, change) => {
if (cart[productId]) {
cart[productId].quantity += change;
if (cart[productId].quantity <= 0) delete cart[productId];
saveCart();
renderCart();
}
};
const removeFromCart = (productId) => {
if (cart[productId]) {
delete cart[productId];
saveCart();
renderCart();
}
};
const clearCart = () => { cart = {}; saveCart(); renderCart(); };
// --- Sale Completion & Invoicing --- const cancelSale = () => {
const completeSale = async () => { if (confirm('Are you sure you want to cancel this sale?')) {
if (Object.keys(cart).length === 0) return alert('Cannot complete sale with an empty cart.'); cart = [];
if (!confirm('Are you sure you want to complete this sale?')) return; updateCartView();
showToast('Sale cancelled.', 'info');
}
};
completeSaleBtn.disabled = true; const saveInvoiceForOffline = (saleDetails) => {
completeSaleBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Processing...';
try { try {
const response = await fetch('api/complete_sale.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cart) }); localStorage.setItem('lastInvoice', JSON.stringify(saleDetails));
const result = await response.json(); } catch (e) {
if (response.ok && result.success) { console.error('Could not save invoice to localStorage:', e);
alert(`Sale Completed Successfully!
Receipt Number: ${result.receipt_number}`);
localStorage.setItem('lastSale', JSON.stringify({ cart: { ...cart }, ...result }));
clearCart();
} else {
throw new Error(result.error || 'An unknown error occurred.');
}
} catch (error) {
alert(`Failed to complete sale: ${error.message}`);
} finally {
completeSaleBtn.disabled = false;
completeSaleBtn.innerHTML = 'Complete Sale';
} }
}; };
const printInvoice = () => { const printInvoice = (invoiceData) => {
const lastSale = JSON.parse(localStorage.getItem('lastSale')); if (!invoiceData) {
if (!lastSale) { showToast('No invoice data to print.', 'warning');
alert('No last sale found to print.');
return; return;
} }
// Populate invoice details document.getElementById('invoice-receipt-number').textContent = invoiceData.sale_id;
document.getElementById('invoice-receipt-number').textContent = lastSale.receipt_number; document.getElementById('invoice-cashier-name').textContent = invoiceData.cashier_name || 'N/A';
document.getElementById('invoice-cashier-name').textContent = lastSale.cashier_name || 'N/A'; document.getElementById('invoice-date').textContent = new Date(invoiceData.sale_date).toLocaleString();
document.getElementById('invoice-date').textContent = new Date(lastSale.created_at).toLocaleString();
const itemsTable = document.getElementById('invoice-items-table'); const itemsHtml = invoiceData.items.map((item, index) => `
itemsTable.innerHTML = ''; <tr>
let subtotal = 0; <td>${index + 1}</td>
let i = 0;
for (const productId in lastSale.cart) {
const item = lastSale.cart[productId];
const total = item.price * item.quantity;
subtotal += total;
const row = itemsTable.insertRow();
row.innerHTML = `
<td>${++i}</td>
<td>${item.name}</td> <td>${item.name}</td>
<td class="text-center">${item.quantity}</td> <td class="text-center">${item.quantity}</td>
<td class="text-end">${formatCurrency(item.price)}</td> <td class="text-end">PKR ${parseFloat(item.price).toFixed(2)}</td>
<td class="text-end">${formatCurrency(total)}</td> <td class="text-end">PKR ${(item.quantity * item.price).toFixed(2)}</td>
`; </tr>
} `).join('');
const tax = subtotal * 0; document.getElementById('invoice-items-table').innerHTML = itemsHtml;
document.getElementById('invoice-subtotal').textContent = formatCurrency(subtotal);
document.getElementById('invoice-tax').textContent = formatCurrency(tax);
document.getElementById('invoice-total').textContent = formatCurrency(subtotal + tax);
// Print logic const subtotal = invoiceData.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const invoiceContent = document.getElementById('invoice-container').innerHTML; const tax = subtotal * TAX_RATE;
const printWindow = window.open('', '_blank', 'height=600,width=800'); const total = subtotal + tax;
document.getElementById('invoice-subtotal').textContent = `PKR ${subtotal.toFixed(2)}`;
document.getElementById('invoice-tax').textContent = `PKR ${tax.toFixed(2)}`;
document-getElementById('invoice-total').textContent = `PKR ${total.toFixed(2)}`;
const invoiceHtml = document.getElementById('invoice-container').innerHTML;
const printWindow = window.open('', '_blank');
printWindow.document.write('<html><head><title>Print Invoice</title>'); printWindow.document.write('<html><head><title>Print Invoice</title>');
// Include bootstrap for styling // Optional: Add bootstrap for styling the print view
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">'); printWindow.document.write('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">');
printWindow.document.write('<style>body { -webkit-print-color-adjust: exact; } @media print { .d-none { display: block !important; } }</style>');
printWindow.document.write('</head><body>'); printWindow.document.write('</head><body>');
printWindow.document.write(invoiceContent); printWindow.document.write(invoiceHtml.replace('d-none', '')); // Show the invoice
printWindow.document.write('</body></html>'); printWindow.document.write('</body></html>');
printWindow.document.close(); printWindow.document.close();
setTimeout(() => { // Wait for content to load setTimeout(() => { // Wait for content to load
printWindow.print(); printWindow.print();
printWindow.close();
}, 500); }, 500);
}; };
// --- Event Listeners --- const printLastInvoice = () => {
barcodeInput.addEventListener('keyup', async (e) => { try {
if (e.key === 'Enter') { const lastInvoice = localStorage.getItem('lastInvoice');
const barcode = barcodeInput.value.trim(); if (lastInvoice) {
printInvoice(JSON.parse(lastInvoice));
} else {
showToast('No previously saved invoice found.', 'info');
}
} catch (e) {
console.error('Could not retrieve or print last invoice:', e);
showToast('Failed to print last invoice.', 'danger');
}
};
// =========================================================================
// EVENT LISTENERS
// =========================================================================
if (barcodeInput) {
barcodeInput.addEventListener('change', (e) => { // 'change' is often better for scanners
const barcode = e.target.value.trim();
if (barcode) { if (barcode) {
barcodeInput.disabled = true; searchProducts(barcode, 'barcode');
const found = await findProductByBarcode(barcode);
if (!found) {
alert(`Product with barcode "${barcode}" not found.`);
}
barcodeInput.value = '';
barcodeInput.disabled = false;
barcodeInput.focus();
}
} }
}); });
barcodeInput.focus(); // Keep focus on the barcode input
}
productSearchInput.addEventListener('keyup', debounce((e) => searchProducts(e.target.value.trim()), 300)); if (productSearchInput) {
productSearchInput.addEventListener('keyup', (e) => {
searchProducts(e.target.value.trim(), 'name');
});
}
if (productGrid) {
productGrid.addEventListener('click', (e) => { productGrid.addEventListener('click', (e) => {
const card = e.target.closest('.product-card'); const card = e.target.closest('.product-card');
if (card) { if (card) {
addToCart({ const productId = card.dataset.productId;
id: card.dataset.productId, // We need to fetch the full product details to add to cart
name: card.dataset.productName, fetch(`../api/search_products.php?id=${productId}`)
price: card.dataset.productPrice, .then(res => res.json())
barcode: card.dataset.productBarcode .then(product => {
if(product) addToCart(product);
}); });
} }
}); });
}
if (cartItemsContainer) {
cartItemsContainer.addEventListener('change', (e) => {
if (e.target.classList.contains('quantity-input')) {
const productId = parseInt(e.target.dataset.productId);
const newQuantity = parseInt(e.target.value);
const itemInCart = cart.find(item => item.id === productId);
if (itemInCart) {
if (newQuantity > 0) {
itemInCart.quantity = newQuantity;
} else {
// Remove if quantity is 0 or less
cart = cart.filter(item => item.id !== productId);
}
updateCartView();
}
}
});
cartItemsContainer.addEventListener('click', (e) => { cartItemsContainer.addEventListener('click', (e) => {
const quantityChangeBtn = e.target.closest('.cart-quantity-change'); if (e.target.classList.contains('remove-item-btn')) {
const removeItemBtn = e.target.closest('.cart-remove-item'); const productId = parseInt(e.target.dataset.productId);
if (quantityChangeBtn) { cart = cart.filter(item => item.id !== productId);
updateCartQuantity(quantityChangeBtn.dataset.productId, parseInt(quantityChangeBtn.dataset.change, 10)); updateCartView();
}
if (removeItemBtn) {
removeFromCart(removeItemBtn.dataset.productId);
}
});
cancelSaleBtn.addEventListener('click', () => {
if (confirm('Are you sure you want to cancel this sale and clear the cart?')) {
clearCart();
}
});
completeSaleBtn.addEventListener('click', completeSale);
printLastInvoiceBtn.addEventListener('click', printInvoice);
// --- Initial Load ---
renderCart();
barcodeInput.focus();
}
// --- Logic for Admin Sales Page ---
if (page === 'admin_sales') {
const saleDetailsModal = new bootstrap.Modal(document.getElementById('saleDetailsModal'));
const saleDetailsContent = document.getElementById('saleDetailsContent');
document.body.addEventListener('click', async (e) => {
const detailsButton = e.target.closest('a.btn-outline-info[href*="action=details"]');
if (detailsButton) {
e.preventDefault();
const url = new URL(detailsButton.href);
const saleId = url.searchParams.get('id');
saleDetailsContent.innerHTML = '<p>Loading...</p>';
saleDetailsModal.show();
try {
const response = await fetch(`api/get_sale_details.php?id=${saleId}`);
if (!response.ok) throw new Error('Failed to fetch details.');
const sale = await response.json();
let itemsHtml = '<ul class="list-group list-group-flush">';
sale.items.forEach(item => {
itemsHtml += `<li class="list-group-item d-flex justify-content-between align-items-center">
${item.product_name} (x${item.quantity})
<span>PKR ${parseFloat(item.price_at_sale * item.quantity).toFixed(2)}</span>
</li>`;
});
itemsHtml += '</ul>';
saleDetailsContent.innerHTML = `
<h5>Receipt: ${sale.receipt_number}</h5>
<p><strong>Cashier:</strong> ${sale.cashier_name || 'N/A'}</p>
<p><strong>Date:</strong> ${new Date(sale.created_at).toLocaleString()}</p>
<hr>
<h6>Items Sold</h6>
${itemsHtml}
<hr>
<div class="text-end">
<p class="fs-5"><strong>Total: PKR ${parseFloat(sale.total_amount).toFixed(2)}</strong></p>
</div>
`;
} catch (error) {
saleDetailsContent.innerHTML = `<p class="text-danger">Error: ${error.message}</p>`;
}
} }
}); });
} }
if (completeSaleBtn) completeSaleBtn.addEventListener('click', completeSale);
if (cancelSaleBtn) cancelSaleBtn.addEventListener('click', cancelSale);
if (printLastInvoiceBtn) printLastInvoiceBtn.addEventListener('click', printLastInvoice);
// Initial setup
updateCartView();
}); });
''

View File

@ -22,7 +22,7 @@ $page = $_GET['page'] ?? ($role === 'admin' ? 'products' : 'checkout'); // Defau
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css"> <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo filemtime('assets/css/custom.css'); ?>">
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body data-page="<?php echo htmlspecialchars($role . '_' . $page); ?>"> <body data-page="<?php echo htmlspecialchars($role . '_' . $page); ?>">
@ -87,6 +87,6 @@ $page = $_GET['page'] ?? ($role === 'admin' ? 'products' : 'checkout'); // Defau
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js"></script> <script src="assets/js/main.js?v=<?php echo filemtime('assets/js/main.js'); ?>"></script>
</body> </body>
</html> </html>

View File

@ -3,4 +3,5 @@ INSERT INTO `products` (`name`, `description`, `price`, `barcode`) VALUES
('Stainless Steel Water Bottle', 'Keeps your drinks cold for 24 hours or hot for 12.', 18.50, '2345678901234'), ('Stainless Steel Water Bottle', 'Keeps your drinks cold for 24 hours or hot for 12.', 18.50, '2345678901234'),
('Organic Blend Coffee Beans', 'A rich, full-bodied coffee blend, ethically sourced.', 15.75, '3456789012345'), ('Organic Blend Coffee Beans', 'A rich, full-bodied coffee blend, ethically sourced.', 15.75, '3456789012345'),
('Wireless Noise-Cancelling Headphones', 'Immerse yourself in sound with these high-fidelity headphones.', 120.00, '4567890123456'), ('Wireless Noise-Cancelling Headphones', 'Immerse yourself in sound with these high-fidelity headphones.', 120.00, '4567890123456'),
('Handcrafted Wooden Pen', 'A unique, handcrafted pen made from reclaimed oak.', 35.00, '5678901234567'); ('Handcrafted Wooden Pen', 'A unique, handcrafted pen made from reclaimed oak.', 35.00, '5678901234567'),
('Gemini Test Product', 'A special product for testing purposes.', 1.00, '9999999999999');

View File

@ -0,0 +1,2 @@
INSERT INTO `products` (`name`, `description`, `price`, `barcode`) VALUES
('Gemini Test Product', 'A special product for testing purposes.', 1.00, '9999999999999');

View File

@ -1,7 +1,10 @@
''' const CACHE_NAME = 'opulent-pos-cache-v2';
const CACHE_NAME = 'opulent-pos-cache-v1';
const urlsToCache = [ const urlsToCache = [
'/',
'/index.php',
'/dashboard.php',
'/dashboard.php?view=cashier_checkout', '/dashboard.php?view=cashier_checkout',
'/manifest.json',
'/assets/css/custom.css', '/assets/css/custom.css',
'/assets/js/main.js', '/assets/js/main.js',
'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
@ -9,50 +12,16 @@ const urlsToCache = [
]; ];
self.addEventListener('install', event => { self.addEventListener('install', event => {
self.skipWaiting(); // Activate worker immediately
event.waitUntil( event.waitUntil(
caches.open(CACHE_NAME) caches.open(CACHE_NAME)
.then(cache => { .then(cache => {
console.log('Opened cache'); console.log('Opened cache and caching assets');
// Add all the assets to the cache
return cache.addAll(urlsToCache); return cache.addAll(urlsToCache);
}) })
); );
}); });
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Clone the request because it's a stream and can only be consumed once.
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
response => {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response because it also is a stream
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
self.addEventListener('activate', event => { self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME]; const cacheWhitelist = [CACHE_NAME];
event.waitUntil( event.waitUntil(
@ -60,11 +29,52 @@ self.addEventListener('activate', event => {
return Promise.all( return Promise.all(
cacheNames.map(cacheName => { cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) { if (cacheWhitelist.indexOf(cacheName) === -1) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName); return caches.delete(cacheName);
} }
}) })
); );
}) })
.then(() => self.clients.claim()) // Take control of all clients
);
});
self.addEventListener('fetch', event => {
// We only want to cache GET requests
if (event.request.method !== 'GET') {
return;
}
event.respondWith(
caches.open(CACHE_NAME).then(async (cache) => {
// Try to get the response from the cache
const cachedResponse = await cache.match(event.request);
// Return the cached response if found
if (cachedResponse) {
// Re-fetch in the background to update the cache for next time
fetch(event.request).then(response => {
if (response.ok) {
cache.put(event.request, response.clone());
}
});
return cachedResponse;
}
// If not in cache, fetch from the network
try {
const networkResponse = await fetch(event.request);
// If the fetch is successful, clone it and store it in the cache
if (networkResponse.ok) {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
// If the fetch fails (e.g., offline), return a fallback or an error
console.log('Fetch failed; returning offline page instead.', error);
// You could return a generic offline page here if you had one
// For now, just let the browser handle the error
}
})
); );
}); });
'''