From 443ae2521feff47b864627fa4f5a00e17626a05b Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 19 Mar 2026 14:18:18 +0000 Subject: [PATCH] feat: Limit POS items grid to 100 initially and add dynamic server-side search --- index.php | 176 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 126 insertions(+), 50 deletions(-) diff --git a/index.php b/index.php index c1974df..1310f72 100644 --- a/index.php +++ b/index.php @@ -453,6 +453,68 @@ if (isset($_GET['action']) || isset($_POST['action'])) { exit; } + + if ($action === 'pos_search_items') { + $q = $_GET['q'] ?? ''; + $searchTerm = "%$q%"; + + $sql = "SELECT * FROM stock_items WHERE (name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ?) ORDER BY name_en ASC LIMIT 100"; + $products_raw = db()->prepare($sql); + $products_raw->execute([$searchTerm, $searchTerm, $searchTerm]); + while($p = $products_raw->fetch(PDO::FETCH_ASSOC)) { + $p['original_price'] = (float)$p['sale_price']; + $p['sale_price'] = getPromotionalPrice($p); + + // Render Card HTML + ?> +
+ + <?= htmlspecialchars($p['name_en']) ?> + +
+ +
+ +
+
+
+
+ + OMR + + OMR +
+ left +
+
+ prepare("SELECT * FROM stock_items WHERE sku = ? LIMIT 1"); + $stmt->execute([$sku]); + $p = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($p) { + $p['original_price'] = (float)$p['sale_price']; + $p['sale_price'] = getPromotionalPrice($p); + $p['nameEn'] = $p['name_en']; + $p['nameAr'] = $p['name_ar']; + $p['vatRate'] = $p['vat_rate']; + echo json_encode($p); + } else { + echo json_encode(null); + } + exit; + } + + if ($action === 'get_payments') { header('Content-Type: application/json'); $invoice_id = (int)$_GET['invoice_id']; @@ -5310,7 +5372,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; $registers = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll(); $allow_zero_stock_sell = ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1'; - $sql = "SELECT * FROM stock_items ORDER BY name_en ASC"; + $sql = "SELECT * FROM stock_items ORDER BY name_en ASC LIMIT 100"; $products_raw = db()->query($sql)->fetchAll(PDO::FETCH_ASSOC); $products = []; foreach ($products_raw as $p) { @@ -6228,31 +6290,48 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; } }; - document.querySelectorAll('.product-card').forEach(card => { - card.addEventListener('click', () => { - const product = { - id: parseInt(card.dataset.id), - nameEn: card.dataset.nameEn, - nameAr: card.dataset.nameAr, - price: parseFloat(card.dataset.price), - stock_quantity: parseFloat(card.dataset.stockQuantity), - vatRate: parseFloat(card.dataset.vatRate) || 0 - }; - cart.add(product); - }); + + // Event Delegation for clicking on cards + document.getElementById('productGrid').addEventListener('click', (e) => { + const card = e.target.closest('.product-card'); + if (card) { + addToCartFromCard(card); + } }); + function addToCartFromCard(card) { + const product = { + id: parseInt(card.dataset.id), + nameEn: card.dataset.nameEn, + nameAr: card.dataset.nameAr, + price: parseFloat(card.dataset.price), + sku: card.dataset.sku, + stock_quantity: parseFloat(card.dataset.stockQuantity), + vatRate: parseFloat(card.dataset.vatRate) || 0 + }; + cart.add(product); + } + + let searchTimeout; document.getElementById('productSearch').addEventListener('input', (e) => { - const q = e.target.value.toLowerCase(); - document.querySelectorAll('.product-grid .product-card').forEach(card => { - const name = card.dataset.nameEn.toLowerCase() + ' ' + card.dataset.nameAr.toLowerCase(); - const sku = card.dataset.sku.toLowerCase(); - if (name.includes(q) || sku.includes(q)) { - card.style.display = 'flex'; - } else { - card.style.display = 'none'; - } - }); + const q = e.target.value.trim(); + clearTimeout(searchTimeout); + + searchTimeout = setTimeout(() => { + const grid = document.getElementById('productGrid'); + grid.style.opacity = '0.5'; + + fetch('index.php?action=pos_search_items&q=' + encodeURIComponent(q)) + .then(response => response.text()) + .then(html => { + grid.innerHTML = html; + grid.style.opacity = '1'; + }) + .catch(err => { + console.error(err); + grid.style.opacity = '1'; + }); + }, 300); }); document.getElementById('barcodeInput').addEventListener('keypress', (e) => { @@ -6260,41 +6339,38 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; const barcode = e.target.value.trim(); if (!barcode) return; + // Try finding in DOM first (current view) const card = Array.from(document.querySelectorAll('.product-card')).find(c => c.dataset.sku === barcode); + if (card) { - const product = { - id: parseInt(card.dataset.id), - nameEn: card.dataset.nameEn, - nameAr: card.dataset.nameAr, - price: parseFloat(card.dataset.price), - sku: card.dataset.sku, - stock_quantity: parseFloat(card.dataset.stockQuantity), - vatRate: parseFloat(card.dataset.vatRate) || 0 - }; - cart.add(product); + addToCartFromCard(card); e.target.value = ''; - Swal.fire({ - toast: true, - position: 'top-end', - icon: 'success', - title: 'Added: ' + product.nameEn, - showConfirmButton: false, - timer: 1000 - }); } else { - Swal.fire({ - toast: true, - position: 'top-end', - icon: 'error', - title: 'Product not found', - showConfirmButton: false, - timer: 1500 - }); - e.target.select(); + // Not found in current view, check server + fetch('index.php?action=pos_get_item_by_sku&sku=' + encodeURIComponent(barcode)) + .then(response => response.json()) + .then(product => { + if (product) { + cart.add(product); + e.target.value = ''; + Swal.fire({ + toast: true, position: 'top-end', icon: 'success', + title: (document.documentElement.lang === 'ar' ? 'تم إضافة: ' : 'Added: ') + (document.documentElement.lang === 'ar' ? (product.nameAr || product.nameEn) : (product.nameEn || product.nameAr)), + showConfirmButton: false, timer: 1000 + }); + } else { + Swal.fire({ + toast: true, position: 'top-end', icon: 'error', + title: 'Product not found', showConfirmButton: false, timer: 1500 + }); + e.target.select(); + } + }); } } }); + // Keep barcode input focused document.addEventListener('click', () => { if (document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'SELECT' && document.activeElement.tagName !== 'TEXTAREA') {