From 4d1d6673905f6a24d49783822ccb87ff53b7dcc8 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 22 Nov 2025 17:22:34 +0000 Subject: [PATCH] version 2 --- assets/js/main.js | 598 ++++++++++------------ dashboard.php | 4 +- db/migrations/002_add_sample_products.sql | 3 +- db/migrations/003_add_gemini_product.sql | 2 + service-worker.js | 88 ++-- 5 files changed, 329 insertions(+), 366 deletions(-) create mode 100644 db/migrations/003_add_gemini_product.sql diff --git a/assets/js/main.js b/assets/js/main.js index 752d989..d911aef 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,364 +1,314 @@ -''' -// Main javascript file for Opulent POS -document.addEventListener('DOMContentLoaded', () => { - const page = document.body.dataset.page; +document.addEventListener('DOMContentLoaded', function () { + 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 --- - if (page === 'cashier_checkout') { - // --- 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'); + let cart = []; + const TAX_RATE = 0.00; - // --- State Management --- - let cart = JSON.parse(localStorage.getItem('cart')) || {}; + // ========================================================================= + // CORE FUNCTIONS + // ========================================================================= - // --- Utility Functions --- - const debounce = (func, delay) => { - let timeout; - return function(...args) { - const context = this; - clearTimeout(timeout); - timeout = setTimeout(() => func.apply(context, args), delay); - }; - }; + const showToast = (message, type = 'success') => { + // A simple toast notification function (can be replaced with a library) + const toast = document.createElement('div'); + toast.className = `toast show align-items-center text-white bg-${type} border-0`; + toast.innerHTML = `
${message}
`; + document.body.appendChild(toast); + setTimeout(() => toast.remove(), 3000); + }; - const formatCurrency = (amount) => `PKR ${parseFloat(amount).toFixed(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 = '

Could not fetch products.

'; - } - }; - - 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) => { + const searchProducts = (query, searchBy = 'name') => { + if (query.length < 2) { productGrid.innerHTML = ''; - if (products.length === 0) { - productGridPlaceholder.innerHTML = '

No products found.

'; - productGridPlaceholder.style.display = 'block'; - return; - } - productGridPlaceholder.style.display = 'none'; - products.forEach(product => { - const productCard = document.createElement('div'); - productCard.className = 'col'; - productCard.innerHTML = ` -
-
-
${product.name}
-

${formatCurrency(product.price)}

-
-
- `; - productGrid.appendChild(productCard); + if (productGridPlaceholder) productGridPlaceholder.style.display = 'block'; + return; + } + + const url = searchBy === 'barcode' + ? `../api/search_products.php?barcode=${encodeURIComponent(query)}` + : `../api/search_products.php?name=${encodeURIComponent(query)}`; + + fetch(url) + .then(response => response.json()) + .then(products => { + if (productGridPlaceholder) productGridPlaceholder.style.display = 'none'; + productGrid.innerHTML = ''; + if (products.length > 0) { + if (searchBy === 'barcode' && products.length === 1) { + addToCart(products[0]); + barcodeInput.value = ''; // Clear input after successful scan + barcodeInput.focus(); + } else { + products.forEach(product => { + const productCard = ` +
+
+
+
${product.name}
+

PKR ${parseFloat(product.price).toFixed(2)}

+
+
+
`; + 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 = () => { - cartItemsContainer.innerHTML = ''; - let subtotal = 0; - let itemCount = 0; + const addToCart = (product) => { + const existingItem = cart.find(item => item.id === product.id); + if (existingItem) { + existingItem.quantity++; + } else { + cart.push({ ...product, quantity: 1 }); + } + updateCartView(); + showToast(`${product.name} added to cart.`, 'success'); + }; - if (Object.keys(cart).length === 0) { - cartItemsContainer.appendChild(cartPlaceholder); - } else { - const table = document.createElement('table'); - table.className = 'table table-sm'; - table.innerHTML = ` + 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 = ` + - + - `; - const tbody = table.querySelector('tbody'); + + ${cart.map(item => ` + + + + + + + `).join('')} + +
Item Qty PriceActions
${item.name} + + PKR ${parseFloat(item.price).toFixed(2)} + +
`; + cartItemsContainer.innerHTML = table; + } + updateCartTotal(); + }; - for (const productId in cart) { - const item = cart[productId]; - subtotal += item.price * item.quantity; - itemCount += item.quantity; - const row = document.createElement('tr'); - row.innerHTML = ` - -
${item.name}
- ${formatCurrency(item.price)} - - -
- - - -
- - ${formatCurrency(item.price * item.quantity)} - - - - `; - tbody.appendChild(row); - } - cartItemsContainer.appendChild(table); - } + const updateCartTotal = () => { + const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); + const tax = subtotal * TAX_RATE; + const total = subtotal + tax; - const tax = subtotal * 0; - const total = subtotal + tax; - cartSubtotal.textContent = formatCurrency(subtotal); - cartTax.textContent = formatCurrency(tax); - cartTotal.textContent = formatCurrency(total); - cartItemCount.textContent = itemCount; - completeSaleBtn.disabled = itemCount === 0; - }; + cartSubtotal.textContent = `PKR ${subtotal.toFixed(2)}`; + cartTax.textContent = `PKR ${tax.toFixed(2)}`; + cartTotal.textContent = `PKR ${total.toFixed(2)}`; + cartItemCount.textContent = cart.reduce((sum, item) => sum + item.quantity, 0); - // --- Cart Logic --- - const saveCart = () => localStorage.setItem('cart', JSON.stringify(cart)); - const addToCart = (product) => { - const id = product.id; - if (cart[id]) { - cart[id].quantity++; + completeSaleBtn.disabled = cart.length === 0; + }; + + const completeSale = () => { + if (cart.length === 0) return; + + 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 { - cart[id] = { id: id, name: product.name, price: parseFloat(product.price), quantity: 1, barcode: product.barcode }; + showToast(`Sale failed: ${data.message}`, 'danger'); } - saveCart(); - renderCart(); - }; - const updateCartQuantity = (productId, change) => { - if (cart[productId]) { - cart[productId].quantity += change; - if (cart[productId].quantity <= 0) delete cart[productId]; - saveCart(); - renderCart(); + }) + .catch(error => { + console.error('Error completing sale:', error); + showToast('An error occurred while completing the sale.', 'danger'); + }); + }; + + const cancelSale = () => { + 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) => ` + + ${index + 1} + ${item.name} + ${item.quantity} + PKR ${parseFloat(item.price).toFixed(2)} + PKR ${(item.quantity * item.price).toFixed(2)} + + `).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('Print Invoice'); + // Optional: Add bootstrap for styling the print view + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write(invoiceHtml.replace('d-none', '')); // Show the invoice + printWindow.document.write(''); + 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'); } - }; - const removeFromCart = (productId) => { - if (cart[productId]) { - delete cart[productId]; - saveCart(); - renderCart(); - } - }; - const clearCart = () => { cart = {}; saveCart(); renderCart(); }; + } catch (e) { + console.error('Could not retrieve or print last invoice:', e); + showToast('Failed to print last invoice.', 'danger'); + } + }; - // --- 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 = ' Processing...'; - 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'; - } - }; + // ========================================================================= + // EVENT LISTENERS + // ========================================================================= - const printInvoice = () => { - const lastSale = JSON.parse(localStorage.getItem('lastSale')); - if (!lastSale) { - alert('No last sale found to print.'); - return; - } - - // 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 = ` - ${++i} - ${item.name} - ${item.quantity} - ${formatCurrency(item.price)} - ${formatCurrency(total)} - `; - } - 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('Print Invoice'); - // Include bootstrap for styling - printWindow.document.write(''); - printWindow.document.write(''); - printWindow.document.write(''); - printWindow.document.write(invoiceContent); - printWindow.document.write(''); - 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(); - } + if (barcodeInput) { + barcodeInput.addEventListener('change', (e) => { // 'change' is often better for scanners + const barcode = e.target.value.trim(); + if (barcode) { + searchProducts(barcode, 'barcode'); } }); + 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) => { const card = e.target.closest('.product-card'); if (card) { - addToCart({ - id: card.dataset.productId, - name: card.dataset.productName, - price: card.dataset.productPrice, - barcode: card.dataset.productBarcode - }); + const productId = card.dataset.productId; + // We need to fetch the full product details to add to cart + fetch(`../api/search_products.php?id=${productId}`) + .then(res => res.json()) + .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) => { - const quantityChangeBtn = e.target.closest('.cart-quantity-change'); - const removeItemBtn = e.target.closest('.cart-remove-item'); - if (quantityChangeBtn) { - updateCartQuantity(quantityChangeBtn.dataset.productId, parseInt(quantityChangeBtn.dataset.change, 10)); - } - 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 = '

Loading...

'; - 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 = ''; - - saleDetailsContent.innerHTML = ` -
Receipt: ${sale.receipt_number}
-

Cashier: ${sale.cashier_name || 'N/A'}

-

Date: ${new Date(sale.created_at).toLocaleString()}

-
-
Items Sold
- ${itemsHtml} -
-
-

Total: PKR ${parseFloat(sale.total_amount).toFixed(2)}

-
- `; - } catch (error) { - saleDetailsContent.innerHTML = `

Error: ${error.message}

`; - } + if (e.target.classList.contains('remove-item-btn')) { + const productId = parseInt(e.target.dataset.productId); + cart = cart.filter(item => item.id !== productId); + updateCartView(); } }); } + + if (completeSaleBtn) completeSaleBtn.addEventListener('click', completeSale); + if (cancelSaleBtn) cancelSaleBtn.addEventListener('click', cancelSale); + if (printLastInvoiceBtn) printLastInvoiceBtn.addEventListener('click', printLastInvoice); + + // Initial setup + updateCartView(); }); -'' \ No newline at end of file diff --git a/dashboard.php b/dashboard.php index 8406f43..36923f3 100644 --- a/dashboard.php +++ b/dashboard.php @@ -22,7 +22,7 @@ $page = $_GET['page'] ?? ($role === 'admin' ? 'products' : 'checkout'); // Defau - + @@ -87,6 +87,6 @@ $page = $_GET['page'] ?? ($role === 'admin' ? 'products' : 'checkout'); // Defau - + \ No newline at end of file diff --git a/db/migrations/002_add_sample_products.sql b/db/migrations/002_add_sample_products.sql index 521acb3..c0c0c10 100644 --- a/db/migrations/002_add_sample_products.sql +++ b/db/migrations/002_add_sample_products.sql @@ -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'), ('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'), -('Handcrafted Wooden Pen', 'A unique, handcrafted pen made from reclaimed oak.', 35.00, '5678901234567'); \ No newline at end of file +('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'); \ No newline at end of file diff --git a/db/migrations/003_add_gemini_product.sql b/db/migrations/003_add_gemini_product.sql new file mode 100644 index 0000000..b5ef006 --- /dev/null +++ b/db/migrations/003_add_gemini_product.sql @@ -0,0 +1,2 @@ +INSERT INTO `products` (`name`, `description`, `price`, `barcode`) VALUES +('Gemini Test Product', 'A special product for testing purposes.', 1.00, '9999999999999'); \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index bdc3288..8077173 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,7 +1,10 @@ -''' -const CACHE_NAME = 'opulent-pos-cache-v1'; +const CACHE_NAME = 'opulent-pos-cache-v2'; const urlsToCache = [ + '/', + '/index.php', + '/dashboard.php', '/dashboard.php?view=cashier_checkout', + '/manifest.json', '/assets/css/custom.css', '/assets/js/main.js', '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.skipWaiting(); // Activate worker immediately event.waitUntil( caches.open(CACHE_NAME) .then(cache => { - console.log('Opened cache'); - // Add all the assets to the cache + console.log('Opened cache and caching assets'); 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 => { const cacheWhitelist = [CACHE_NAME]; event.waitUntil( @@ -60,11 +29,52 @@ self.addEventListener('activate', event => { return Promise.all( cacheNames.map(cacheName => { if (cacheWhitelist.indexOf(cacheName) === -1) { + console.log('Deleting old cache:', 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 + } + }) ); }); -''' \ No newline at end of file