38929-vm/pos.js
2026-03-04 03:04:10 +00:00

421 lines
16 KiB
JavaScript

const itemsData = 1;
const lang = '1';
const currencyLabel = '1';
const decimalPrecision = 1;
const editOrderId = 1;
const loyaltyEnabled = 1;
const pointsPerCurrency = 1;
const currencyPerPoint = 1;
let cart = 1;
let selectionModal;
let paymentModal;
let customerLoyaltyPoints = 0;
let pointsToRedeem = 0;
// If not editing, try to load from local storage
if (!editOrderId) {
try {
const saved = localStorage.getItem('pos_cart');
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) cart = parsed;
}
} catch (e) { console.error('Cart parse error', e); }
}
document.addEventListener('DOMContentLoaded', () => {
if (typeof bootstrap !== 'undefined') {
const modalEl = document.getElementById('selectionModal');
if (modalEl) selectionModal = new bootstrap.Modal(modalEl);
const payModalEl = document.getElementById('paymentModal');
if (payModalEl) paymentModal = new bootstrap.Modal(payModalEl);
}
// Category filtering
document.querySelectorAll('.cat-filter').forEach(btn => {
btn.onclick = () => {
const cat = btn.getAttribute('data-cat');
document.querySelectorAll('.cat-filter').forEach(b => b.classList.remove('btn-primary'));
document.querySelectorAll('.cat-filter').forEach(b => b.classList.add('btn-white', 'border'));
btn.classList.add('btn-primary');
btn.classList.remove('btn-white', 'border');
document.querySelectorAll('.item-card-wrapper').forEach(card => {
if (cat === 'all' || card.getAttribute('data-cat') === cat) card.style.display = 'block';
else card.style.display = 'none';
});
};
});
// Search
const searchInput = document.getElementById('itemSearch');
if (searchInput) {
searchInput.addEventListener('input', function(e) {
const q = e.target.value.toLowerCase();
document.querySelectorAll('.item-card-wrapper').forEach(card => {
const en = card.getAttribute('data-name-en') || '';
const ar = card.getAttribute('data-name-ar') || '';
if (en.includes(q) || ar.includes(q)) card.style.display = 'block';
else card.style.display = 'none';
});
});
}
// Persistent Customer Search
const custSearchInput = document.getElementById('customerSearchInput');
const custResults = document.getElementById('customerResults');
const clearCustBtn = document.getElementById('clearCustomerBtn');
const custIdInput = document.getElementById('customerId');
// Handle Edit Customer Pre-fill
<?php if ($edit_order && $edit_order['customer_id']): ?>
const initialCustId = "1";
const initialCustBtn = document.querySelector(`.customer-result-item[data-id="${initialCustId}"]`);
if (initialCustBtn) {
selectCustomer(initialCustId, initialCustBtn.getAttribute('data-name'), initialCustBtn.getAttribute('data-points'));
}
<?php endif; ?>
if (custSearchInput) {
custSearchInput.addEventListener('focus', () => {
custResults.classList.remove('d-none');
});
custSearchInput.addEventListener('input', function(e) {
const q = e.target.value.toLowerCase();
custResults.classList.remove('d-none');
document.querySelectorAll('.customer-result-item').forEach(item => {
const search = item.getAttribute('data-search') || '';
if (search.includes(q)) {
item.parentElement.style.display = 'block';
} else {
item.parentElement.style.display = 'none';
}
});
});
document.addEventListener('click', function(e) {
if (!document.getElementById('customerSearchWrapper').contains(e.target)) {
custResults.classList.add('d-none');
}
});
}
if (clearCustBtn) {
clearCustBtn.addEventListener('click', () => {
resetCustomerSelection();
});
}
custResults.addEventListener('click', function(e) {
const btn = e.target.closest('.customer-result-item');
if (btn) {
const id = btn.getAttribute('data-id');
const name = btn.getAttribute('data-name');
const points = btn.getAttribute('data-points') || 0;
selectCustomer(id, name, points);
custResults.classList.add('d-none');
}
});
updateCart();
});
function selectCustomer(id, name, points) {
const custIdInput = document.getElementById('customerId');
const custSearchInput = document.getElementById('customerSearchInput');
const clearCustBtn = document.getElementById('clearCustomerBtn');
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
const customerPointsEl = document.getElementById('customerPoints');
const redeemHint = document.getElementById('redeemHint');
custIdInput.value = id;
customerLoyaltyPoints = parseFloat(points);
if (id) {
custSearchInput.value = name;
clearCustBtn.classList.remove('d-none');
if (loyaltyEnabled) {
loyaltyDisplay.classList.remove('d-none');
loyaltyRedeemUI.classList.remove('d-none');
customerPointsEl.innerText = customerLoyaltyPoints.toFixed(2);
if (redeemHint) {
redeemHint.innerText = lang === 'en'
? `1 Point = ${currencyPerPoint} ${currencyLabel}`
: `1 نقطة = ${currencyPerPoint} ${currencyLabel}`;
}
}
} else {
resetCustomerSelection();
}
}
function resetCustomerSelection() {
const custIdInput = document.getElementById('customerId');
const custSearchInput = document.getElementById('customerSearchInput');
const clearCustBtn = document.getElementById('clearCustomerBtn');
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
const loyaltyRedeemUI = document.getElementById('loyaltyRedeemUI');
custIdInput.value = '';
custSearchInput.value = '';
custSearchInput.placeholder = lang === 'en' ? 'Walk-in Customer' : 'عميل عابر';
clearCustBtn.classList.add('d-none');
loyaltyDisplay.classList.add('d-none');
loyaltyRedeemUI.classList.add('d-none');
customerLoyaltyPoints = 0;
pointsToRedeem = 0;
const ptsInput = document.getElementById('pointsToRedeem');
if (ptsInput) ptsInput.value = '';
updateCart();
}
function applyPoints() {
const ptsInput = document.getElementById('pointsToRedeem');
let val = parseFloat(ptsInput.value) || 0;
if (val > customerLoyaltyPoints) {
alert(lang === 'en' ? 'Insufficient points' : 'نقاط غير كافية');
val = customerLoyaltyPoints;
ptsInput.value = val;
}
pointsToRedeem = val;
updateCart();
}
function showOptions(itemId) {
const item = itemsData[itemId];
if (!item) return;
const itemNameEl = document.getElementById('selectionItemName');
if (itemNameEl) itemNameEl.innerText = lang === 'en' ? item.name_en : item.name_ar;
const list = document.getElementById('optionsList');
if (list) {
list.innerHTML = '';
item.services.forEach(s => {
const col = document.createElement('div');
col.className = 'col-6';
col.innerHTML = `
<button class="btn btn-outline-primary w-100 p-3 rounded-4 border-2 text-center h-100 transition-all" onclick="addToCart(${item.id}, ${s.id})">
<div class="fw-bold mb-1 small">${lang === 'en' ? s.name_en : s.name_ar}</div>
<div class="small opacity-75">${s.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
</button>
`;
list.appendChild(col);
});
}
if (selectionModal) selectionModal.show();
}
function addToCart(itemId, serviceId) {
const item = itemsData[itemId];
if (!item) return;
const service = item.services.find(s => s.id === serviceId);
if (!service) return;
const existing = cart.find(i => i.item_id === itemId && i.service_id === serviceId);
if (existing) {
existing.qty++;
} else {
cart.push({
item_id: itemId,
service_id: serviceId,
name: lang === 'en' ? item.name_en : item.name_ar,
service_name: lang === 'en' ? service.name_en : service.name_ar,
price: service.price,
qty: 1,
vat_percent: item.vat_percent
});
}
if (selectionModal) selectionModal.hide();
updateCart();
}
function changeQty(index, delta) {
cart[index].qty += delta;
if (cart[index].qty <= 0) cart.splice(index, 1);
updateCart();
}
function updateCart() {
if (!editOrderId) {
localStorage.setItem('pos_cart', JSON.stringify(cart));
}
const cartList = document.getElementById('cartItems');
const emptyCart = document.getElementById('emptyCart');
if (!cartList || !emptyCart) return;
if (cart.length === 0) {
cartList.innerHTML = '';
emptyCart.style.display = 'block';
} else {
emptyCart.style.display = 'none';
cartList.innerHTML = cart.map((item, index) => `
<div class="d-flex align-items-center mb-3 bg-light p-2 rounded-3">
<div class="flex-grow-1">
<div class="fw-bold small text-dark">${item.name}</div>
<div class="text-muted" style="font-size: 0.75rem;">${item.service_name}</div>
<div class="fw-bold text-primary">${item.price.toFixed(decimalPrecision)} ${currencyLabel}</div>
</div>
<div class="d-flex align-items-center bg-white rounded-3 p-1">
<button class="btn btn-sm p-0 px-2" onclick="changeQty(${index}, -1)"><i class="bi bi-dash"></i></button>
<span class="mx-2 fw-bold">${item.qty}</span>
<button class="btn btn-sm p-0 px-2" onclick="changeQty(${index}, 1)"><i class="bi bi-plus"></i></button>
</div>
</div>
`).join('');
}
let subtotal = 0;
let totalVat = 0;
cart.forEach(item => {
const itemSubtotal = item.price * item.qty;
subtotal += itemSubtotal;
totalVat += itemSubtotal * ((item.vat_percent || 15) / 100);
});
const subtotalEl = document.getElementById('cartSubtotal');
const vatEl = document.getElementById('cartVat');
const totalEl = document.getElementById('cartTotal');
const discountRow = document.getElementById('loyaltyDiscountRow');
const discountEl = document.getElementById('cartLoyaltyDiscount');
let totalBeforeDiscount = subtotal + totalVat;
let loyaltyDiscount = pointsToRedeem * currencyPerPoint;
if (loyaltyDiscount > totalBeforeDiscount) {
loyaltyDiscount = totalBeforeDiscount;
pointsToRedeem = loyaltyDiscount / currencyPerPoint;
}
if (loyaltyDiscount > 0) {
discountRow.classList.remove('d-none');
discountEl.innerText = '-' + loyaltyDiscount.toFixed(decimalPrecision) + ' ' + currencyLabel;
} else {
discountRow.classList.add('d-none');
}
const finalTotal = totalBeforeDiscount - loyaltyDiscount;
if (subtotalEl) subtotalEl.innerText = subtotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
if (vatEl) vatEl.innerText = totalVat.toFixed(decimalPrecision) + ' ' + currencyLabel;
if (totalEl) totalEl.innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
}
function clearCart() {
if (confirm(lang === 'en' ? 'Clear cart?' : 'مسح السلة؟')) {
cart = [];
pointsToRedeem = 0;
const ptsInput = document.getElementById('pointsToRedeem');
if (ptsInput) ptsInput.value = '';
updateCart();
}
}
function checkout() {
if (cart.length === 0) return;
let subtotal = 0;
let totalVat = 0;
cart.forEach(item => {
const itemSubtotal = item.price * item.qty;
subtotal += itemSubtotal;
totalVat += itemSubtotal * ((item.vat_percent || 15) / 100);
});
let finalTotal = (subtotal + totalVat) - (pointsToRedeem * currencyPerPoint);
if (finalTotal < 0) finalTotal = 0;
document.getElementById('paymentTotalAmount').innerText = finalTotal.toFixed(decimalPrecision) + ' ' + currencyLabel;
if (paymentModal) paymentModal.show();
}
async function completeCheckout(paymentMethod) {
const cid = document.getElementById('customerId').value;
let subtotal = 0;
let totalVat = 0;
const itemsToSubmit = cart.map(item => {
const itemSubtotal = item.price * item.qty;
const itemVat = itemSubtotal * ((item.vat_percent || 15) / 100);
subtotal += itemSubtotal;
totalVat += itemVat;
return {
itemId: item.item_id,
serviceId: item.service_id,
variantId: null,
quantity: item.qty,
price: item.price,
vatAmount: itemVat
};
});
const totalPrice = subtotal + totalVat;
try {
const response = await fetch('api/checkout.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_id: editOrderId,
customer_id: cid,
items: itemsToSubmit,
vat_total: totalVat,
total_price: totalPrice,
payment_method: paymentMethod,
points_to_redeem: pointsToRedeem
})
});
const res = await response.json();
if (res.success) {
const orderId = res.order_id || editOrderId;
if (!editOrderId) { cart = []; updateCart(); localStorage.removeItem("pos_cart"); } window.location.href = "receipt.php?id=" + orderId;
} else alert(res.error);
} catch (e) { alert('Error'); }
}
async function saveCustomer() {
const form = document.getElementById('addCustomerForm');
if (!form) return;
const data = new FormData(form);
try {
const response = await fetch('api/add_customer.php', { method: 'POST', body: data });
const res = await response.json();
if (res.success) {
const id = res.customer.id;
const nameEn = res.customer.name_en;
const nameAr = res.customer.name_ar || nameEn;
const phone = res.customer.phone;
const displayName = lang === 'en' ? nameEn : nameAr;
selectCustomer(id, displayName, 0);
const list = document.getElementById('customerResultsList');
if (list) {
const searchStr = `${nameEn} ${nameAr} ${phone}`.toLowerCase();
const div = document.createElement('div');
div.className = 'p-1';
div.innerHTML = `
<button class="btn btn-white btn-sm w-100 text-start rounded-2 customer-result-item p-2" type="button" data-id="${id}" data-name="${displayName}" data-phone="${phone}" data-search="${searchStr}" data-points="0">
<div class="fw-bold small text-dark">${displayName}</div>
<div class="text-muted small" style="font-size: 0.7rem;">${phone}</div>
</button>
`;
list.prepend(div);
}
if (typeof bootstrap !== 'undefined') {
const modalEl = document.getElementById('addCustomerModal');
if (modalEl) {
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
}
}
form.reset();
} else alert(res.error);
} catch (e) { alert('Error'); }
}