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 = `
`;
+ 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 = `
+ `;
+ 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 = '';
- sale.items.forEach(item => {
- itemsHtml += `-
- ${item.product_name} (x${item.quantity})
- PKR ${parseFloat(item.price_at_sale * item.quantity).toFixed(2)}
-
`;
- });
- 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
-
+