enhance load items in pos
This commit is contained in:
parent
923a8e50e7
commit
a052840f3d
151
index.php
151
index.php
@ -393,17 +393,20 @@ if (isset($_GET['action']) || isset($_POST['action'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($action === 'search_items') {
|
if ($action === 'search_items') {
|
||||||
file_put_contents('search_debug.log', date('Y-m-d H:i:s') . " - search_items call: q=" . ($_GET['q'] ?? '') . "\n", FILE_APPEND);
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$q = $_GET['q'] ?? '';
|
$q = $_GET['q'] ?? '';
|
||||||
if (strlen($q) < 1) {
|
|
||||||
echo json_encode([]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$searchTerm = "%$q%";
|
$searchTerm = "%$q%";
|
||||||
$stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 15");
|
// Optimization: Fetch items with promotional prices calculated
|
||||||
|
$stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 50");
|
||||||
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
|
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($items as &$item) {
|
||||||
|
$item['original_price'] = (float)$item['sale_price'];
|
||||||
|
$item['sale_price'] = getPromotionalPrice($item);
|
||||||
|
}
|
||||||
|
echo json_encode($items);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6233,7 +6236,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
$registers = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll();
|
$registers = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll();
|
||||||
|
|
||||||
$allow_zero_stock_sell = ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1';
|
$allow_zero_stock_sell = ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1';
|
||||||
$sql = "SELECT * FROM stock_items ORDER BY name_en ASC";
|
// Optimization: Only load top 50 products initially to prevent heavy load
|
||||||
|
$sql = "SELECT * FROM stock_items ORDER BY id DESC LIMIT 50";
|
||||||
$products_raw = db()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
$products_raw = db()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||||
$products = [];
|
$products = [];
|
||||||
foreach ($products_raw as $p) {
|
foreach ($products_raw as $p) {
|
||||||
@ -7249,38 +7253,80 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.querySelectorAll('.product-card').forEach(card => {
|
function attachProductClickListeners() {
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('productSearch').addEventListener('input', (e) => {
|
|
||||||
const q = e.target.value.toLowerCase();
|
|
||||||
document.querySelectorAll('.product-grid .product-card').forEach(card => {
|
document.querySelectorAll('.product-grid .product-card').forEach(card => {
|
||||||
const name = card.dataset.nameEn.toLowerCase() + ' ' + card.dataset.nameAr.toLowerCase();
|
card.onclick = () => {
|
||||||
const sku = card.dataset.sku.toLowerCase();
|
const product = {
|
||||||
if (name.includes(q) || sku.includes(q)) {
|
id: parseInt(card.dataset.id),
|
||||||
card.style.display = 'flex';
|
nameEn: card.dataset.nameEn,
|
||||||
} else {
|
nameAr: card.dataset.nameAr,
|
||||||
card.style.display = 'none';
|
price: parseFloat(card.dataset.price),
|
||||||
}
|
sku: card.dataset.sku,
|
||||||
|
stock_quantity: parseFloat(card.dataset.stockQuantity),
|
||||||
|
vatRate: parseFloat(card.dataset.vatRate) || 0
|
||||||
|
};
|
||||||
|
cart.add(product);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
attachProductClickListeners();
|
||||||
|
|
||||||
|
let searchTimeout = null;
|
||||||
|
document.getElementById('productSearch').addEventListener('input', (e) => {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
const q = e.target.value.trim();
|
||||||
|
searchTimeout = setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('index.php?action=search_items&q=' + encodeURIComponent(q));
|
||||||
|
const products = await resp.json();
|
||||||
|
renderProductGrid(products);
|
||||||
|
} catch (err) { console.error(err); }
|
||||||
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('barcodeInput').addEventListener('keypress', (e) => {
|
function renderProductGrid(products) {
|
||||||
|
const grid = document.getElementById('productGrid');
|
||||||
|
const lang = document.documentElement.lang || 'en';
|
||||||
|
if (products.length === 0) {
|
||||||
|
grid.innerHTML = '<div class="text-center w-100 p-5 text-muted"><i class="bi bi-search mb-3 d-block" style="font-size: 3rem;"></i> No products found.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.innerHTML = products.map(p => {
|
||||||
|
const salePrice = parseFloat(p.sale_price);
|
||||||
|
const originalPrice = parseFloat(p.original_price || p.sale_price);
|
||||||
|
const img = p.image_path ? `<img src="${p.image_path}" alt="${p.name_en}">` : `
|
||||||
|
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-2" style="height: 120px;">
|
||||||
|
<i class="bi bi-box-seam text-muted" style="font-size: 3rem;"></i>
|
||||||
|
</div>`;
|
||||||
|
const promoHtml = salePrice < originalPrice ? `<span class="text-muted smaller text-decoration-line-through">OMR ${originalPrice.toFixed(3)}</span>` : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="product-card" data-id="${p.id}" data-name-en="${p.name_en}" data-name-ar="${p.name_ar}" data-price="${p.sale_price}" data-sku="${p.sku}" data-stock-quantity="${p.stock_quantity}" data-vat-rate="${p.vat_rate}">
|
||||||
|
${img}
|
||||||
|
<div class="mb-1 product-name">
|
||||||
|
<div class="fw-bold text-truncate" title="${p.name_en}">${p.name_en}</div>
|
||||||
|
<div class="small text-muted fw-normal text-truncate" title="${p.name_ar}" dir="rtl">${p.name_ar}</div>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted mb-2">${p.sku}</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-auto">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
${promoHtml}
|
||||||
|
<span class="price text-primary fw-bold">OMR ${salePrice.toFixed(3)}</span>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-light text-dark small">${parseFloat(p.stock_quantity)} left</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
attachProductClickListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('barcodeInput').addEventListener('keypress', async (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const barcode = e.target.value.trim();
|
const barcode = e.target.value.trim();
|
||||||
if (!barcode) return;
|
if (!barcode) return;
|
||||||
|
|
||||||
|
// First check local DOM
|
||||||
const card = Array.from(document.querySelectorAll('.product-card')).find(c => c.dataset.sku === barcode);
|
const card = Array.from(document.querySelectorAll('.product-card')).find(c => c.dataset.sku === barcode);
|
||||||
if (card) {
|
if (card) {
|
||||||
const product = {
|
const product = {
|
||||||
@ -7294,24 +7340,31 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
};
|
};
|
||||||
cart.add(product);
|
cart.add(product);
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
Swal.fire({
|
Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'Added: ' + product.nameEn, showConfirmButton: false, timer: 1000 });
|
||||||
toast: true,
|
|
||||||
position: 'top-end',
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Added: ' + product.nameEn,
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 1000
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
// Not in DOM, check server
|
||||||
toast: true,
|
try {
|
||||||
position: 'top-end',
|
const resp = await fetch('index.php?action=search_items&q=' + encodeURIComponent(barcode));
|
||||||
icon: 'error',
|
const products = await resp.json();
|
||||||
title: 'Product not found',
|
const found = products.find(p => p.sku === barcode);
|
||||||
showConfirmButton: false,
|
if (found) {
|
||||||
timer: 1500
|
const product = {
|
||||||
});
|
id: found.id,
|
||||||
e.target.select();
|
nameEn: found.name_en,
|
||||||
|
nameAr: found.name_ar,
|
||||||
|
price: parseFloat(found.sale_price),
|
||||||
|
sku: found.sku,
|
||||||
|
stock_quantity: parseFloat(found.stock_quantity),
|
||||||
|
vatRate: parseFloat(found.vat_rate) || 0
|
||||||
|
};
|
||||||
|
cart.add(product);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} catch (err) { console.error(err); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user