Revert to version 538d43c
This commit is contained in:
parent
3e8d320deb
commit
419681ef66
@ -1,15 +0,0 @@
|
|||||||
I've fixed the search bar functionality and resolved the errors you were seeing.
|
|
||||||
|
|
||||||
**Fixes Applied:**
|
|
||||||
|
|
||||||
1. **Search Bar Not Working:** I wrapped the search initialization code in a `DOMContentLoaded` event listener. This ensures that the search bar elements (like `productSearchInput`) are fully loaded in the browser before the JavaScript tries to attach the search logic to them. Previously, the script was running too early, failing to find the elements, and silently doing nothing.
|
|
||||||
2. **POS Page Error:** I fixed a critical bug on the POS page where an undefined variable (`$customers`) was causing a PHP warning to be injected into the JavaScript, leading to the "Invalid or unexpected token" syntax error.
|
|
||||||
3. **PHP Warnings:** I initialized the `$data['customers_list']` variable globally to prevent potential PHP warnings on the Dashboard and other pages where it wasn't being set, which could also break the page layout or scripts.
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
* **Reload the page.**
|
|
||||||
* Try searching for an item in the "New Sales Invoice", "New Quotation", or "New Purchase" modals. The search results should now appear correctly.
|
|
||||||
* Check the POS page to ensure it loads without the previous syntax error.
|
|
||||||
|
|
||||||
Let me know if you encounter any other issues!
|
|
||||||
375
index.php
375
index.php
@ -34,7 +34,6 @@ function numberToWords($num) {
|
|||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
|
||||||
if ($_GET['action'] === 'search_items') {
|
if ($_GET['action'] === 'search_items') {
|
||||||
ob_clean(); // Clear buffer to prevent JSON corruption
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$q = $_GET['q'] ?? '';
|
$q = $_GET['q'] ?? '';
|
||||||
$stmt = db()->prepare("SELECT id, name_en, name_ar, sku, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 10");
|
$stmt = db()->prepare("SELECT id, name_en, name_ar, sku, sale_price, purchase_price, stock_quantity, vat_rate FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 10");
|
||||||
@ -1051,7 +1050,6 @@ $data['suppliers'] = db()->query("SELECT * FROM customers WHERE type = 'supplier
|
|||||||
|
|
||||||
$settings_raw = db()->query("SELECT * FROM settings")->fetchAll();
|
$settings_raw = db()->query("SELECT * FROM settings")->fetchAll();
|
||||||
$data['settings'] = [];
|
$data['settings'] = [];
|
||||||
$data['customers_list'] = [];
|
|
||||||
foreach ($settings_raw as $s) {
|
foreach ($settings_raw as $s) {
|
||||||
$data['settings'][$s['key']] = $s['value'];
|
$data['settings'][$s['key']] = $s['value'];
|
||||||
}
|
}
|
||||||
@ -1112,22 +1110,9 @@ switch ($page) {
|
|||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$term = $_GET['search'];
|
$where[] = "(q.id LIKE ? OR c.name LIKE ?)";
|
||||||
$cleanTerm = str_ireplace(['QUO-', 'INV-'], '', $term);
|
$params[] = "%{$_GET['search']}%";
|
||||||
$cleanTerm = ltrim($cleanTerm, '0');
|
$params[] = "%{$_GET['search']}%";
|
||||||
|
|
||||||
$searchClauses = ["c.name LIKE ?"];
|
|
||||||
$params[] = "%$term%";
|
|
||||||
|
|
||||||
$searchClauses[] = "q.id LIKE ?";
|
|
||||||
$params[] = "%$term%";
|
|
||||||
|
|
||||||
if ($cleanTerm !== '' && $cleanTerm !== $term) {
|
|
||||||
$searchClauses[] = "q.id LIKE ?";
|
|
||||||
$params[] = "%$cleanTerm%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$where[] = "(" . implode(" OR ", $searchClauses) . ")";
|
|
||||||
}
|
}
|
||||||
if (!empty($_GET['customer_id'])) {
|
if (!empty($_GET['customer_id'])) {
|
||||||
$where[] = "q.customer_id = ?";
|
$where[] = "q.customer_id = ?";
|
||||||
@ -1158,9 +1143,6 @@ switch ($page) {
|
|||||||
case 'settings':
|
case 'settings':
|
||||||
// Already fetched globally
|
// Already fetched globally
|
||||||
break;
|
break;
|
||||||
case 'pos':
|
|
||||||
$data['customers'] = db()->query("SELECT id, name, phone, credit_limit, balance FROM customers WHERE type = 'customer'")->fetchAll();
|
|
||||||
break;
|
|
||||||
case 'sales':
|
case 'sales':
|
||||||
case 'purchases':
|
case 'purchases':
|
||||||
$type = ($page === 'sales') ? 'sale' : 'purchase';
|
$type = ($page === 'sales') ? 'sale' : 'purchase';
|
||||||
@ -1169,22 +1151,9 @@ switch ($page) {
|
|||||||
$params = [$type];
|
$params = [$type];
|
||||||
|
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$term = $_GET['search'];
|
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
|
||||||
$cleanTerm = str_ireplace(['INV-', 'QUO-'], '', $term);
|
$params[] = "%{$_GET['search']}%";
|
||||||
$cleanTerm = ltrim($cleanTerm, '0');
|
$params[] = "%{$_GET['search']}%";
|
||||||
|
|
||||||
$searchClauses = ["c.name LIKE ?"];
|
|
||||||
$params[] = "%$term%";
|
|
||||||
|
|
||||||
$searchClauses[] = "v.id LIKE ?";
|
|
||||||
$params[] = "%$term%";
|
|
||||||
|
|
||||||
if ($cleanTerm !== '' && $cleanTerm !== $term) {
|
|
||||||
$searchClauses[] = "v.id LIKE ?";
|
|
||||||
$params[] = "%$cleanTerm%";
|
|
||||||
}
|
|
||||||
|
|
||||||
$where[] = "(" . implode(" OR ", $searchClauses) . ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_GET['customer_id'])) {
|
if (!empty($_GET['customer_id'])) {
|
||||||
@ -2095,7 +2064,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
payments: [],
|
payments: [],
|
||||||
allCreditCustomers: <?php
|
allCreditCustomers: <?php
|
||||||
$custData = [];
|
$custData = [];
|
||||||
foreach ($data['customers'] as $c) {
|
foreach ($customers as $c) {
|
||||||
$custData[] = [
|
$custData[] = [
|
||||||
'value' => (string)$c['id'],
|
'value' => (string)$c['id'],
|
||||||
'text' => $c['name'] . ' (' . ($c['phone'] ?? '') . ')'
|
'text' => $c['name'] . ' (' . ($c['phone'] ?? '') . ')'
|
||||||
@ -3502,12 +3471,12 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const logo = <?= json_encode($data['settings']['company_logo'] ?? '') ?>;
|
const logo = "<?= $data['settings']['company_logo'] ?? '' ?>";
|
||||||
const companyName = <?= json_encode($data['settings']['company_name'] ?? '') ?>;
|
const companyName = "<?= htmlspecialchars($data['settings']['company_name'] ?? '' ) ?>";
|
||||||
const companyPhone = <?= json_encode($data['settings']['company_phone'] ?? '') ?>;
|
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '' ) ?>";
|
||||||
const companyEmail = <?= json_encode($data['settings']['company_email'] ?? '') ?>;
|
const companyEmail = "<?= htmlspecialchars($data['settings']['company_email'] ?? '' ) ?>";
|
||||||
const companyAddress = <?= json_encode($data['settings']['company_address'] ?? '') ?>;
|
const companyAddress = "<?= htmlspecialchars($data['settings']['company_address'] ?? '' ) ?>";
|
||||||
const vatNumber = <?= json_encode($data['settings']['vat_number'] ?? '') ?>;
|
const vatNumber = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '' ) ?>";
|
||||||
|
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
@ -3644,6 +3613,53 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const hasExpiryToggle = document.getElementById('hasExpiryToggle');
|
||||||
|
const expiryDateContainer = document.getElementById('expiryDateContainer');
|
||||||
|
const suggestSkuBtn = document.getElementById('suggestSkuBtn');
|
||||||
|
const skuInput = document.getElementById('skuInput');
|
||||||
|
|
||||||
|
if (suggestSkuBtn && skuInput) {
|
||||||
|
suggestSkuBtn.addEventListener('click', function() {
|
||||||
|
const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString();
|
||||||
|
skuInput.value = sku;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Expiry Date visibility
|
||||||
|
if (hasExpiryToggle && expiryDateContainer) {
|
||||||
|
hasExpiryToggle.addEventListener('change', function() {
|
||||||
|
expiryDateContainer.style.display = this.checked ? 'block' : 'none';
|
||||||
|
if (!this.checked) {
|
||||||
|
expiryDateContainer.querySelector('input').value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status change logic for Paid Amount field
|
||||||
|
const togglePaidAmount = (statusId, containerId) => {
|
||||||
|
const statusEl = document.getElementById(statusId);
|
||||||
|
const containerEl = document.getElementById(containerId);
|
||||||
|
if (statusEl && containerEl) {
|
||||||
|
statusEl.addEventListener('change', function() {
|
||||||
|
if (this.value === 'partially_paid') {
|
||||||
|
containerEl.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
containerEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
togglePaidAmount('add_status', 'addPaidAmountContainer');
|
||||||
|
togglePaidAmount('edit_status', 'editPaidAmountContainer');
|
||||||
|
|
||||||
|
// Show receipt modal if needed
|
||||||
|
<?php if (isset($_SESSION['trigger_receipt_modal'])):
|
||||||
|
$rid = (int)$_SESSION['show_receipt_id'];
|
||||||
|
unset($_SESSION['trigger_receipt_modal']);
|
||||||
|
?>
|
||||||
|
showReceipt(<?= $rid ?>);
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
window.showReceipt = function(paymentId) {
|
window.showReceipt = function(paymentId) {
|
||||||
fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`)
|
fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
@ -3710,60 +3726,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasExpiryToggle = document.getElementById('hasExpiryToggle');
|
|
||||||
const expiryDateContainer = document.getElementById('expiryDateContainer');
|
|
||||||
const suggestSkuBtn = document.getElementById('suggestSkuBtn');
|
|
||||||
const skuInput = document.getElementById('skuInput');
|
|
||||||
|
|
||||||
if (suggestSkuBtn && skuInput) {
|
|
||||||
suggestSkuBtn.addEventListener('click', function() {
|
|
||||||
const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString();
|
|
||||||
skuInput.value = sku;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle Expiry Date visibility
|
|
||||||
if (hasExpiryToggle && expiryDateContainer) {
|
|
||||||
hasExpiryToggle.addEventListener('change', function() {
|
|
||||||
expiryDateContainer.style.display = this.checked ? 'block' : 'none';
|
|
||||||
if (!this.checked) {
|
|
||||||
expiryDateContainer.querySelector('input').value = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status change logic for Paid Amount field
|
|
||||||
const togglePaidAmount = (statusId, containerId) => {
|
|
||||||
const statusEl = document.getElementById(statusId);
|
|
||||||
const containerEl = document.getElementById(containerId);
|
|
||||||
if (statusEl && containerEl) {
|
|
||||||
statusEl.addEventListener('change', function() {
|
|
||||||
if (this.value === 'partially_paid') {
|
|
||||||
containerEl.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
containerEl.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
togglePaidAmount('add_status', 'addPaidAmountContainer');
|
|
||||||
togglePaidAmount('edit_status', 'editPaidAmountContainer');
|
|
||||||
|
|
||||||
// Show receipt modal if needed
|
|
||||||
<?php if (isset($_SESSION['trigger_receipt_modal'])):
|
|
||||||
$rid = (int)$_SESSION['show_receipt_id'];
|
|
||||||
unset($_SESSION['trigger_receipt_modal']);
|
|
||||||
?>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
if (typeof showReceipt === 'function') {
|
|
||||||
showReceipt(<?= $rid ?>);
|
|
||||||
} else {
|
|
||||||
console.error('showReceipt function not found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
|
|
||||||
document.querySelectorAll('.view-payments-btn').forEach(btn => {
|
document.querySelectorAll('.view-payments-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
const invoiceId = this.getAttribute('data-id');
|
const invoiceId = this.getAttribute('data-id');
|
||||||
@ -3835,44 +3797,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.className = 'item-row';
|
row.className = 'item-row';
|
||||||
|
const currentInvoiceType = window.invoiceType || 'sale';
|
||||||
// Determine invoice type from the form context
|
|
||||||
let currentInvoiceType = 'sale';
|
|
||||||
const form = tableBody.closest('form');
|
|
||||||
if (form) {
|
|
||||||
const typeInput = form.querySelector('input[name="type"]');
|
|
||||||
if (typeInput) {
|
|
||||||
currentInvoiceType = typeInput.value;
|
|
||||||
} else if (window.invoiceType) {
|
|
||||||
currentInvoiceType = window.invoiceType;
|
|
||||||
}
|
|
||||||
} else if (window.invoiceType) {
|
|
||||||
currentInvoiceType = window.invoiceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const price = customData ? customData.unit_price : (currentInvoiceType === 'sale' ? item.sale_price : item.purchase_price);
|
const price = customData ? customData.unit_price : (currentInvoiceType === 'sale' ? item.sale_price : item.purchase_price);
|
||||||
const qty = customData ? customData.quantity : 1;
|
const qty = customData ? customData.quantity : 1;
|
||||||
const vatRate = item.vat_rate || 0;
|
const vatRate = item.vat_rate || 0;
|
||||||
|
|
||||||
const escapeHtml = (unsafe) => {
|
|
||||||
return String(unsafe)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
};
|
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" name="item_ids[]" class="item-id-input" value="${escapeHtml(item.id)}">
|
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
|
||||||
<input type="hidden" class="item-vat-rate" value="${escapeHtml(vatRate)}">
|
<input type="hidden" class="item-vat-rate" value="${vatRate}">
|
||||||
<div><strong>${escapeHtml(item.name_en)}</strong></div>
|
<div><strong>${item.name_en}</strong></div>
|
||||||
<div class="small text-muted">${escapeHtml(item.name_ar)} (${escapeHtml(item.sku)})</div>
|
<div class="small text-muted">${item.name_ar} (${item.sku})</div>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${escapeHtml(qty)}" required></td>
|
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
|
||||||
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${escapeHtml(price)}" required></td>
|
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
|
||||||
<td><input type="text" class="form-control bg-light" value="${escapeHtml(vatRate)}%" readonly></td>
|
<td><input type="text" class="form-control bg-light" value="${vatRate}%" readonly></td>
|
||||||
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
|
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
|
||||||
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
|
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
|
||||||
`;
|
`;
|
||||||
@ -3915,201 +3854,65 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
window.initInvoiceForm = function(searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) {
|
window.initInvoiceForm = function(searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) {
|
||||||
const searchInput = document.getElementById(searchInputId);
|
const searchInput = document.getElementById(searchInputId);
|
||||||
let suggestions = document.getElementById(suggestionsId);
|
const suggestions = document.getElementById(suggestionsId);
|
||||||
const tableBody = document.getElementById(tableBodyId);
|
const tableBody = document.getElementById(tableBodyId);
|
||||||
const grandTotalEl = document.getElementById(grandTotalId);
|
const grandTotalEl = document.getElementById(grandTotalId);
|
||||||
const subtotalEl = document.getElementById(subtotalId);
|
const subtotalEl = document.getElementById(subtotalId);
|
||||||
const totalVatEl = document.getElementById(totalVatId);
|
const totalVatEl = document.getElementById(totalVatId);
|
||||||
|
|
||||||
if (!searchInput) { console.error('Search input not found:', searchInputId); return; }
|
if (!searchInput || !tableBody) return;
|
||||||
if (!tableBody) { console.error('Table body not found:', tableBodyId); return; }
|
|
||||||
|
|
||||||
// Move suggestions to body to avoid z-index/overflow issues in modals
|
|
||||||
if (suggestions && suggestions.parentNode !== document.body) {
|
|
||||||
if (suggestions.parentNode) suggestions.parentNode.removeChild(suggestions);
|
|
||||||
document.body.appendChild(suggestions);
|
|
||||||
suggestions.style.position = 'absolute';
|
|
||||||
suggestions.style.zIndex = '10000'; // Ensure it is above everything
|
|
||||||
suggestions.style.width = Math.max(searchInput.offsetWidth, 300) + 'px'; // Initial width
|
|
||||||
suggestions.style.maxHeight = '300px';
|
|
||||||
suggestions.style.overflowY = 'auto';
|
|
||||||
suggestions.style.backgroundColor = 'white';
|
|
||||||
suggestions.style.border = '1px solid #ccc';
|
|
||||||
suggestions.style.borderRadius = '0.25rem';
|
|
||||||
suggestions.style.boxShadow = '0 0.5rem 1rem rgba(0, 0, 0, 0.15)';
|
|
||||||
}
|
|
||||||
|
|
||||||
const positionSuggestions = () => {
|
|
||||||
if (!suggestions || suggestions.style.display === 'none') return;
|
|
||||||
const rect = searchInput.getBoundingClientRect();
|
|
||||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
||||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
|
||||||
|
|
||||||
suggestions.style.top = (rect.bottom + scrollTop) + 'px';
|
|
||||||
suggestions.style.left = (rect.left + scrollLeft) + 'px';
|
|
||||||
suggestions.style.width = Math.max(rect.width, 300) + 'px';
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('resize', positionSuggestions);
|
|
||||||
window.addEventListener('scroll', positionSuggestions, true);
|
|
||||||
|
|
||||||
const escapeHtml = (unsafe) => {
|
|
||||||
return String(unsafe)
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
// Prevent form submission on Enter key - Aggressive Fix
|
|
||||||
const handleEnterKey = function(e) {
|
|
||||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
// If suggestions are visible, click the first one
|
|
||||||
if (suggestions && suggestions.style.display !== 'none') {
|
|
||||||
const firstBtn = suggestions.querySelector('button');
|
|
||||||
if (firstBtn) firstBtn.click();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Use capture phase to intercept before bubbling
|
|
||||||
searchInput.addEventListener('keydown', handleEnterKey, true);
|
|
||||||
searchInput.addEventListener('keypress', handleEnterKey, true);
|
|
||||||
|
|
||||||
searchInput.addEventListener('input', function() {
|
searchInput.addEventListener('input', function() {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
const q = this.value.trim();
|
const q = this.value.trim();
|
||||||
|
|
||||||
if (q.length < 1) {
|
if (q.length < 1) {
|
||||||
if (suggestions) suggestions.style.display = 'none';
|
suggestions.style.display = 'none';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
if (suggestions) {
|
|
||||||
suggestions.innerHTML = '<div class="list-group-item text-muted p-2 small">Searching...</div>';
|
|
||||||
suggestions.style.display = 'block';
|
|
||||||
positionSuggestions();
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
console.log(`Searching for: ${q}`);
|
|
||||||
fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`)
|
fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`)
|
||||||
.then(res => {
|
.then(res => res.json())
|
||||||
if (!res.ok) throw new Error('Network response: ' + res.statusText);
|
|
||||||
return res.text();
|
|
||||||
})
|
|
||||||
.then(text => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(text);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('JSON Parse Error:', text);
|
|
||||||
throw new Error('Invalid JSON response');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('Search results:', data);
|
|
||||||
if (!suggestions) return;
|
|
||||||
suggestions.innerHTML = '';
|
suggestions.innerHTML = '';
|
||||||
if (data && data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
btn.className = 'list-group-item list-group-item-action p-2';
|
btn.className = 'list-group-item list-group-item-action';
|
||||||
btn.innerHTML = `
|
btn.innerHTML = `
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between">
|
||||||
<div>
|
<span><strong>${item.sku}</strong> - ${item.name_en} / ${item.name_ar}</span>
|
||||||
<div class="fw-bold small">${escapeHtml(item.sku)}</div>
|
<span class="text-muted small">Stock: ${item.stock_quantity}</span>
|
||||||
<div class="small">${escapeHtml(item.name_en)}</div>
|
|
||||||
</div>
|
|
||||||
<span class="badge bg-secondary rounded-pill">${parseFloat(item.stock_quantity).toFixed(2)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
btn.addEventListener('click', (e) => {
|
btn.onclick = () => window.addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
window.addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
|
|
||||||
suggestions.style.display = 'none';
|
|
||||||
});
|
|
||||||
suggestions.appendChild(btn);
|
suggestions.appendChild(btn);
|
||||||
});
|
});
|
||||||
suggestions.style.display = 'block';
|
suggestions.style.display = 'block';
|
||||||
positionSuggestions();
|
|
||||||
} else {
|
} else {
|
||||||
suggestions.innerHTML = '<div class="list-group-item text-muted p-2 small">No items found</div>';
|
suggestions.style.display = 'none';
|
||||||
suggestions.style.display = 'block';
|
|
||||||
positionSuggestions();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('Search error:', err);
|
|
||||||
if (suggestions) {
|
|
||||||
suggestions.innerHTML = '<div class="list-group-item text-danger p-2 small">Error searching items</div>';
|
|
||||||
suggestions.style.display = 'block';
|
|
||||||
positionSuggestions();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
if (suggestions && !searchInput.contains(e.target) && !suggestions.contains(e.target)) {
|
if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) {
|
||||||
suggestions.style.display = 'none';
|
suggestions.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide on modal close
|
|
||||||
document.querySelectorAll('.modal').forEach(m => {
|
|
||||||
m.addEventListener('hide.bs.modal', () => {
|
|
||||||
if (suggestions) suggestions.style.display = 'none';
|
|
||||||
});
|
|
||||||
// Update position on modal scroll
|
|
||||||
m.addEventListener('scroll', positionSuggestions);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
|
window.invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : ($page === "purchases" ? "purchase" : "") ?>';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
|
||||||
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
|
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
|
||||||
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
|
|
||||||
|
|
||||||
// Quotation Form Logic
|
// Quotation Form Logic
|
||||||
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
|
window.initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
|
||||||
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
|
window.initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
|
||||||
|
|
||||||
// Global safeguard: Prevent form submission on Enter for search inputs
|
|
||||||
document.querySelectorAll('.modal form').forEach(form => {
|
|
||||||
// Prevent submission if triggered by search input
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
const active = document.activeElement;
|
|
||||||
if (active && active.tagName === 'INPUT' && active.id && active.id.toLowerCase().includes('searchinput')) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent Keydown/Keypress bubbling up to form submission
|
|
||||||
const preventEnter = function(e) {
|
|
||||||
if ((e.key === 'Enter' || e.keyCode === 13) && e.target.tagName === 'INPUT') {
|
|
||||||
if (e.target.id && e.target.id.toLowerCase().includes('searchinput')) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
form.addEventListener('keydown', preventEnter, true); // Capture phase
|
|
||||||
form.addEventListener('keypress', preventEnter, true); // Capture phase
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Global Actions Handler - Delegation for list buttons
|
// Global Actions Handler - Delegation for list buttons
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
@ -4288,7 +4091,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<input type="text" id="productSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
<input type="text" id="productSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
||||||
<div id="searchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 2000;"></div>
|
<div id="searchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4388,7 +4191,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<input type="text" id="editProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
<input type="text" id="editProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
||||||
<div id="editSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 2000;"></div>
|
<div id="editSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4470,7 +4273,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<input type="text" id="quotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
<input type="text" id="quotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
||||||
<div id="quotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 2000;"></div>
|
<div id="quotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4563,7 +4366,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<input type="text" id="editQuotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
<input type="text" id="editQuotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
|
||||||
<div id="editQuotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 2000;"></div>
|
<div id="editQuotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'index.php';
|
|
||||||
// We need to simulate the GET request
|
|
||||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
|
||||||
$_GET['action'] = 'search_items';
|
|
||||||
$_GET['q'] = 'a'; // Search for 'a'
|
|
||||||
|
|
||||||
// Capture output
|
|
||||||
ob_start();
|
|
||||||
// We can't include index.php again as it will execute global code.
|
|
||||||
// Instead, I'll just copy the relevant DB code or query directly.
|
|
||||||
// Actually, index.php has global execution code, so requiring it might trigger output.
|
|
||||||
// Let's just use the db config and query directly.
|
|
||||||
?>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
Loading…
x
Reference in New Issue
Block a user