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,364 +1,314 @@
'''
// Main javascript file for Opulent POS
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', function () {
const page = document.body.dataset.page; const barcodeInput = document.getElementById('barcode-scanner-input');
const productSearchInput = document.getElementById('product-search');
const productGrid = document.getElementById('product-grid');
const cartItemsContainer = document.getElementById('cart-items');
const cartItemCount = document.getElementById('cart-item-count');
const cartSubtotal = document.getElementById('cart-subtotal');
const cartTax = document.getElementById('cart-tax');
const cartTotal = document.getElementById('cart-total');
const completeSaleBtn = document.getElementById('complete-sale-btn');
const cancelSaleBtn = document.getElementById('cancel-sale-btn');
const printLastInvoiceBtn = document.getElementById('print-last-invoice-btn');
const cartPlaceholder = document.getElementById('cart-placeholder');
const productGridPlaceholder = document.getElementById('product-grid-placeholder');
// --- Logic for Cashier Checkout Page --- let cart = [];
if (page === 'cashier_checkout') { const TAX_RATE = 0.00;
// --- Element Selectors ---
const barcodeInput = document.getElementById('barcode-scanner-input');
const productSearchInput = document.getElementById('product-search');
const productGrid = document.getElementById('product-grid');
const productGridPlaceholder = document.getElementById('product-grid-placeholder');
const cartItemsContainer = document.getElementById('cart-items');
const cartPlaceholder = document.getElementById('cart-placeholder');
const cartItemCount = document.getElementById('cart-item-count');
const cartSubtotal = document.getElementById('cart-subtotal');
const cartTax = document.getElementById('cart-tax');
const cartTotal = document.getElementById('cart-total');
const completeSaleBtn = document.getElementById('complete-sale-btn');
const cancelSaleBtn = document.getElementById('cancel-sale-btn');
const printLastInvoiceBtn = document.getElementById('print-last-invoice-btn');
// --- State Management --- // =========================================================================
let cart = JSON.parse(localStorage.getItem('cart')) || {}; // CORE FUNCTIONS
// =========================================================================
// --- Utility Functions --- const showToast = (message, type = 'success') => {
const debounce = (func, delay) => { // A simple toast notification function (can be replaced with a library)
let timeout; const toast = document.createElement('div');
return function(...args) { toast.className = `toast show align-items-center text-white bg-${type} border-0`;
const context = this; 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>`;
clearTimeout(timeout); document.body.appendChild(toast);
timeout = setTimeout(() => func.apply(context, args), delay); setTimeout(() => toast.remove(), 3000);
}; };
};
const formatCurrency = (amount) => `PKR ${parseFloat(amount).toFixed(2)}`; const searchProducts = (query, searchBy = 'name') => {
if (query.length < 2) {
// --- API Communication ---
const searchProducts = async (query) => {
if (query.length < 2) {
productGrid.innerHTML = '';
productGridPlaceholder.style.display = 'block';
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) => {
try {
const response = await fetch(`api/search_products.php?q=${encodeURIComponent(barcode)}&exact=true`);
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 ---
const renderProductGrid = (products) => {
productGrid.innerHTML = ''; productGrid.innerHTML = '';
if (products.length === 0) { if (productGridPlaceholder) productGridPlaceholder.style.display = 'block';
productGridPlaceholder.innerHTML = '<p>No products found.</p>'; return;
productGridPlaceholder.style.display = 'block'; }
return;
} const url = searchBy === 'barcode'
productGridPlaceholder.style.display = 'none'; ? `../api/search_products.php?barcode=${encodeURIComponent(query)}`
products.forEach(product => { : `../api/search_products.php?name=${encodeURIComponent(query)}`;
const productCard = document.createElement('div');
productCard.className = 'col'; fetch(url)
productCard.innerHTML = ` .then(response => response.json())
<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}"> .then(products => {
<div class="card-body text-center"> if (productGridPlaceholder) productGridPlaceholder.style.display = 'none';
<h6 class="card-title fs-sm">${product.name}</h6> productGrid.innerHTML = '';
<p class="card-text text-muted small">${formatCurrency(product.price)}</p> if (products.length > 0) {
</div> if (searchBy === 'barcode' && products.length === 1) {
</div> addToCart(products[0]);
`; barcodeInput.value = ''; // Clear input after successful scan
productGrid.appendChild(productCard); barcodeInput.focus();
} else {
products.forEach(product => {
const productCard = `
<div class="col">
<div class="card h-100 product-card" data-product-id="${product.id}">
<div class="card-body text-center">
<h6 class="card-title fs-sm">${product.name}</h6>
<p class="card-text fw-bold">PKR ${parseFloat(product.price).toFixed(2)}</p>
</div>
</div>
</div>`;
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++;
} else {
cart.push({ ...product, quantity: 1 });
}
updateCartView();
showToast(`${product.name} added to cart.`, 'success');
};
if (Object.keys(cart).length === 0) { const updateCartView = () => {
cartItemsContainer.appendChild(cartPlaceholder); if (cart.length === 0) {
} else { if (cartPlaceholder) cartPlaceholder.style.display = 'block';
const table = document.createElement('table'); cartItemsContainer.innerHTML = cartPlaceholder ? cartPlaceholder.outerHTML : '';
table.className = 'table table-sm'; } else {
table.innerHTML = ` 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>
<td class="w-50">${item.name}</td>
<td class="text-center">
<input type="number" class="form-control form-control-sm quantity-input" data-product-id="${item.id}" value="${item.quantity}" min="1">
</td>
<td class="text-end">PKR ${parseFloat(item.price).toFixed(2)}</td>
<td class="text-end">
<button class="btn btn-outline-danger btn-sm remove-item-btn" data-product-id="${item.id}">
&times;
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>`;
cartItemsContainer.innerHTML = table;
}
updateCartTotal();
};
for (const productId in cart) { const updateCartTotal = () => {
const item = cart[productId]; const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
subtotal += item.price * item.quantity; const tax = subtotal * TAX_RATE;
itemCount += item.quantity; const total = subtotal + tax;
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">
<div class="input-group input-group-sm" style="width: 90px; margin: auto;">
<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 class="text-end fw-bold">${formatCurrency(item.price * item.quantity)}</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-danger cart-remove-item" data-product-id="${productId}"><i class="bi bi-trash"></i></button>
</td>
`;
tbody.appendChild(row);
}
cartItemsContainer.appendChild(table);
}
const tax = subtotal * 0; cartSubtotal.textContent = `PKR ${subtotal.toFixed(2)}`;
const total = subtotal + tax; cartTax.textContent = `PKR ${tax.toFixed(2)}`;
cartSubtotal.textContent = formatCurrency(subtotal); cartTotal.textContent = `PKR ${total.toFixed(2)}`;
cartTax.textContent = formatCurrency(tax); cartItemCount.textContent = cart.reduce((sum, item) => sum + item.quantity, 0);
cartTotal.textContent = formatCurrency(total);
cartItemCount.textContent = itemCount;
completeSaleBtn.disabled = itemCount === 0;
};
// --- Cart Logic --- completeSaleBtn.disabled = cart.length === 0;
const saveCart = () => localStorage.setItem('cart', JSON.stringify(cart)); };
const addToCart = (product) => {
const id = product.id; const completeSale = () => {
if (cart[id]) { if (cart.length === 0) return;
cart[id].quantity++;
fetch('../api/complete_sale.php', {
method: 'POST',
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);
const updateCartQuantity = (productId, change) => { showToast('An error occurred while completing the sale.', 'danger');
if (cart[productId]) { });
cart[productId].quantity += change; };
if (cart[productId].quantity <= 0) delete cart[productId];
saveCart(); const cancelSale = () => {
renderCart(); if (confirm('Are you sure you want to cancel this sale?')) {
cart = [];
updateCartView();
showToast('Sale cancelled.', 'info');
}
};
const saveInvoiceForOffline = (saleDetails) => {
try {
localStorage.setItem('lastInvoice', JSON.stringify(saleDetails));
} catch (e) {
console.error('Could not save invoice to localStorage:', e);
}
};
const printInvoice = (invoiceData) => {
if (!invoiceData) {
showToast('No invoice data to print.', 'warning');
return;
}
document.getElementById('invoice-receipt-number').textContent = invoiceData.sale_id;
document.getElementById('invoice-cashier-name').textContent = invoiceData.cashier_name || 'N/A';
document.getElementById('invoice-date').textContent = new Date(invoiceData.sale_date).toLocaleString();
const itemsHtml = invoiceData.items.map((item, index) => `
<tr>
<td>${index + 1}</td>
<td>${item.name}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">PKR ${parseFloat(item.price).toFixed(2)}</td>
<td class="text-end">PKR ${(item.quantity * item.price).toFixed(2)}</td>
</tr>
`).join('');
document.getElementById('invoice-items-table').innerHTML = itemsHtml;
const subtotal = invoiceData.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const tax = subtotal * TAX_RATE;
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>');
// Optional: Add bootstrap for styling the print view
printWindow.document.write('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">');
printWindow.document.write('</head><body>');
printWindow.document.write(invoiceHtml.replace('d-none', '')); // Show the invoice
printWindow.document.write('</body></html>');
printWindow.document.close();
setTimeout(() => { // Wait for content to load
printWindow.print();
printWindow.close();
}, 500);
};
const printLastInvoice = () => {
try {
const lastInvoice = localStorage.getItem('lastInvoice');
if (lastInvoice) {
printInvoice(JSON.parse(lastInvoice));
} else {
showToast('No previously saved invoice found.', 'info');
} }
}; } catch (e) {
const removeFromCart = (productId) => { console.error('Could not retrieve or print last invoice:', e);
if (cart[productId]) { showToast('Failed to print last invoice.', 'danger');
delete cart[productId]; }
saveCart(); };
renderCart();
}
};
const clearCart = () => { cart = {}; saveCart(); renderCart(); };
// --- Sale Completion & Invoicing ---
const completeSale = async () => {
if (Object.keys(cart).length === 0) return alert('Cannot complete sale with an empty cart.');
if (!confirm('Are you sure you want to complete this sale?')) return;
completeSaleBtn.disabled = true; // =========================================================================
completeSaleBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Processing...'; // EVENT LISTENERS
try { // =========================================================================
const response = await fetch('api/complete_sale.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cart) });
const result = await response.json();
if (response.ok && result.success) {
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 = () => { if (barcodeInput) {
const lastSale = JSON.parse(localStorage.getItem('lastSale')); barcodeInput.addEventListener('change', (e) => { // 'change' is often better for scanners
if (!lastSale) { const barcode = e.target.value.trim();
alert('No last sale found to print.'); if (barcode) {
return; searchProducts(barcode, 'barcode');
}
// Populate invoice details
document.getElementById('invoice-receipt-number').textContent = lastSale.receipt_number;
document.getElementById('invoice-cashier-name').textContent = lastSale.cashier_name || 'N/A';
document.getElementById('invoice-date').textContent = new Date(lastSale.created_at).toLocaleString();
const itemsTable = document.getElementById('invoice-items-table');
itemsTable.innerHTML = '';
let subtotal = 0;
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 class="text-center">${item.quantity}</td>
<td class="text-end">${formatCurrency(item.price)}</td>
<td class="text-end">${formatCurrency(total)}</td>
`;
}
const tax = subtotal * 0;
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 invoiceContent = document.getElementById('invoice-container').innerHTML;
const printWindow = window.open('', '_blank', 'height=600,width=800');
printWindow.document.write('<html><head><title>Print Invoice</title>');
// Include bootstrap for styling
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">');
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(invoiceContent);
printWindow.document.write('</body></html>');
printWindow.document.close();
setTimeout(() => { // Wait for content to load
printWindow.print();
}, 500);
};
// --- Event Listeners ---
barcodeInput.addEventListener('keyup', async (e) => {
if (e.key === 'Enter') {
const barcode = barcodeInput.value.trim();
if (barcode) {
barcodeInput.disabled = true;
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
}
})
); );
}); });
'''