diff --git a/assets/images/products/prod_699dab0588305.jpg b/assets/images/products/prod_699dab0588305.jpg new file mode 100644 index 0000000..1da33d3 Binary files /dev/null and b/assets/images/products/prod_699dab0588305.jpg differ diff --git a/assets/js/main.js b/assets/js/main.js index 62f7201..915c9a7 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,6 +1,6 @@ document.addEventListener('DOMContentLoaded', () => { // SweetAlert2 Toast Mixin - const Toast = Swal.mixin({ + const Toast = (typeof Swal !== 'undefined') ? Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, @@ -10,7 +10,23 @@ document.addEventListener('DOMContentLoaded', () => { toast.addEventListener('mouseenter', Swal.stopTimer); toast.addEventListener('mouseleave', Swal.resumeTimer); } - }); + }) : null; + + function showToast(msg, type = 'primary') { + if (!Toast) { + console.log(`Toast: ${msg} (${type})`); + return; + } + let icon = 'info'; + if (type === 'success') icon = 'success'; + if (type === 'danger') icon = 'error'; + if (type === 'warning') icon = 'warning'; + + Toast.fire({ + icon: icon, + title: msg + }); + } // Global settings with fallbacks const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { @@ -20,24 +36,21 @@ document.addEventListener('DOMContentLoaded', () => { }; let cart = []; - let currentOrderId = null; // Track order ID for updates + let currentOrderId = null; const cartItemsContainer = document.getElementById('cart-items'); const cartTotalPrice = document.getElementById('cart-total-price'); const cartSubtotal = document.getElementById('cart-subtotal'); const cartVatInput = document.getElementById('cart-vat-input'); - // Updated Button References const quickOrderBtn = document.getElementById('quick-order-btn'); const placeOrderBtn = document.getElementById('place-order-btn'); const recallBtn = document.getElementById('recall-bill-btn'); const reprintReceiptBtn = document.getElementById('reprint-receipt-btn'); - // Recall Modal const recallModalEl = document.getElementById('recallOrderModal'); const recallModal = recallModalEl ? new bootstrap.Modal(recallModalEl) : null; const recallList = document.getElementById('recall-orders-list'); - // Loyalty State let isLoyaltyRedemption = false; const loyaltySection = document.getElementById('loyalty-section'); const loyaltyPointsDisplay = document.getElementById('loyalty-points-display'); @@ -45,25 +58,21 @@ document.addEventListener('DOMContentLoaded', () => { const redeemLoyaltyBtn = document.getElementById('redeem-loyalty-btn'); const viewPointsHistoryBtn = document.getElementById('view-points-history-btn'); - // Points History Modal const pointsHistoryModalEl = document.getElementById('pointsHistoryModal'); const pointsHistoryModal = pointsHistoryModalEl ? new bootstrap.Modal(pointsHistoryModalEl) : null; const pointsHistoryBody = document.getElementById('points-history-body'); const pointsHistoryEmpty = document.getElementById('points-history-empty'); - // Table Management let currentTableId = null; let currentTableName = null; const tableDisplay = document.getElementById('current-table-display'); const tableModalEl = document.getElementById('tableSelectionModal'); const tableSelectionModal = tableModalEl ? new bootstrap.Modal(tableModalEl) : null; - // Variant Management const variantModalEl = document.getElementById('variantSelectionModal'); const variantSelectionModal = variantModalEl ? new bootstrap.Modal(variantModalEl) : null; let pendingProduct = null; - // Customer Search Elements const customerSearchInput = document.getElementById('customer-search'); const customerResults = document.getElementById('customer-results'); const selectedCustomerId = document.getElementById('selected-customer-id'); @@ -72,51 +81,50 @@ document.addEventListener('DOMContentLoaded', () => { const customerNameDisplay = document.getElementById('customer-name-display'); let currentCustomer = null; - // Payment Modal const paymentModalEl = document.getElementById('paymentSelectionModal'); const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentModalEl) : null; const paymentMethodsContainer = document.getElementById('payment-methods-container'); - // Product Search & Filter - const productSearchInput = document.getElementById('product-search-input'); + const productSearchInput = document.getElementById('product-search'); let currentCategory = 'all'; let currentSearchQuery = ''; - // Helper for currency function formatCurrency(amount) { const symbol = settings.currency_symbol || '$'; const decimals = parseInt(settings.currency_decimals || 2); return symbol + parseFloat(amount).toFixed(decimals); } - // --- Product Filtering (Category + Search) --- function filterProducts() { const items = document.querySelectorAll('.product-item'); items.forEach(item => { - const matchesCategory = (currentCategory == 'all' || item.dataset.categoryId == currentCategory); - const name = item.querySelector('.card-title').textContent.toLowerCase(); - const matchesSearch = name.includes(currentSearchQuery); + const matchesCategory = (currentCategory == 'all' || item.dataset.category == currentCategory); + const name = (item.dataset.name || '').toLowerCase(); + const sku = (item.dataset.sku || '').toLowerCase(); + const matchesSearch = name.includes(currentSearchQuery) || sku.includes(currentSearchQuery); - if (matchesCategory && matchesSearch) { - item.style.display = 'block'; - } else { - item.style.display = 'none'; - } + item.style.display = (matchesCategory && matchesSearch) ? 'block' : 'none'; }); } window.filterCategory = function(categoryId, btnElement) { currentCategory = categoryId; - - // Update Active Button State + document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active')); if (btnElement) { - document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active')); btnElement.classList.add('active'); + } else { + const btn = document.querySelector(`.category-btn[data-category="${categoryId}"]`); + if (btn) btn.classList.add('active'); } - filterProducts(); }; + document.querySelectorAll('.category-btn').forEach(btn => { + btn.addEventListener('click', () => { + filterCategory(btn.dataset.category, btn); + }); + }); + if (productSearchInput) { productSearchInput.addEventListener('input', (e) => { currentSearchQuery = e.target.value.trim().toLowerCase(); @@ -124,19 +132,17 @@ document.addEventListener('DOMContentLoaded', () => { }); } - // --- Recall Order Logic --- - if (recallBtn) { - recallBtn.addEventListener('click', () => { - fetchRecallOrders(); - }); - } + window.openRecallOrderModal = function() { + if (!recallModal) return; + fetchRecallOrders(); + recallModal.show(); + }; function fetchRecallOrders() { if (!recallList) return; - recallList.innerHTML = '
'; - if (recallModal) recallModal.show(); + recallList.innerHTML = '
'; - const outletId = new URLSearchParams(window.location.search).get('outlet_id') || 1; + const outletId = CURRENT_OUTLET ? CURRENT_OUTLET.id : 1; fetch(`api/recall_orders.php?action=list&outlet_id=${outletId}`) .then(res => res.json()) .then(data => { @@ -148,11 +154,7 @@ document.addEventListener('DOMContentLoaded', () => { item.innerHTML = `
Order #${order.id} ${order.order_type}
- - ${order.customer_name || 'Guest'} - ${order.table_number ? ' • Table ' + order.table_number : ''} - • ${order.time_formatted} - + ${order.customer_name || 'Guest'} ${order.table_number ? ' • Table ' + order.table_number : ''} • ${order.time_formatted}
${formatCurrency(order.total_amount)}
@@ -166,7 +168,7 @@ document.addEventListener('DOMContentLoaded', () => { recallList.innerHTML = '
No unpaid bills found.
'; } }) - .catch(err => { + .catch(() => { recallList.innerHTML = '
Error fetching orders.
'; }); } @@ -176,17 +178,10 @@ document.addEventListener('DOMContentLoaded', () => { .then(res => res.json()) .then(data => { if (data.success) { - // Set Order ID currentOrderId = data.order.id; + if (data.customer) selectCustomer(data.customer); + else if (clearCustomerBtn) clearCustomerBtn.click(); - // Set Customer - if (data.customer) { - selectCustomer(data.customer); - } else { - if (clearCustomerBtn) clearCustomerBtn.click(); - } - - // Set Table/Order Type const otInput = document.querySelector(`input[name="order_type"][value="${data.order.order_type}"]`); if (otInput) { otInput.checked = true; @@ -196,36 +191,30 @@ document.addEventListener('DOMContentLoaded', () => { checkOrderType(); } } - - // Populate Cart cart = data.items; - updateCart(); if (recallModal) recallModal.hide(); showToast(`Order #${orderId} loaded!`, 'success'); } else { showToast(data.error || 'Failed to load order', 'danger'); } - }) - .catch(err => showToast('Error loading order details', 'danger')); + }); } - // --- Customer Search --- - let searchTimeout; if (customerSearchInput) { + let searchTimeout; customerSearchInput.addEventListener('input', (e) => { const query = e.target.value.trim(); clearTimeout(searchTimeout); - if (query.length < 2) { - customerResults.style.display = 'none'; + if (customerResults) customerResults.style.display = 'none'; return; } - searchTimeout = setTimeout(() => { fetch(`api/search_customers.php?q=${encodeURIComponent(query)}`) .then(res => res.json()) .then(data => { + if (!customerResults) return; customerResults.innerHTML = ''; if (data.length > 0) { data.forEach(cust => { @@ -247,10 +236,8 @@ document.addEventListener('DOMContentLoaded', () => { }); }, 300); }); - - // Close search on click outside document.addEventListener('click', (e) => { - if (!customerSearchInput.contains(e.target) && !customerResults.contains(e.target)) { + if (customerResults && !customerSearchInput.contains(e.target) && !customerResults.contains(e.target)) { customerResults.style.display = 'none'; } }); @@ -258,137 +245,48 @@ document.addEventListener('DOMContentLoaded', () => { function selectCustomer(cust) { currentCustomer = cust; - selectedCustomerId.value = cust.id; - customerNameDisplay.textContent = cust.name; - customerSearchInput.value = cust.name; // Show name in input - customerSearchInput.disabled = true; // Lock input - customerResults.style.display = 'none'; - clearCustomerBtn.classList.remove('d-none'); + if (selectedCustomerId) selectedCustomerId.value = cust.id; + if (customerNameDisplay) customerNameDisplay.textContent = cust.name; + if (customerSearchInput) { + customerSearchInput.value = cust.name; + customerSearchInput.disabled = true; + } + if (customerResults) customerResults.style.display = 'none'; + if (clearCustomerBtn) clearCustomerBtn.classList.remove('d-none'); - // Loyalty Logic if (loyaltySection && typeof LOYALTY_SETTINGS !== 'undefined' && LOYALTY_SETTINGS.is_enabled) { loyaltySection.classList.remove('d-none'); - loyaltyPointsDisplay.textContent = cust.points + ' pts'; - - if (cust.eligible_for_free_meal) { - redeemLoyaltyBtn.disabled = false; - loyaltyMessage.innerHTML = 'Eligible for Free Meal!'; - } else { - redeemLoyaltyBtn.disabled = true; - const needed = cust.points_needed || 0; - loyaltyMessage.textContent = `${needed} pts away from a free meal.`; + if (loyaltyPointsDisplay) loyaltyPointsDisplay.textContent = cust.points + ' pts'; + if (redeemLoyaltyBtn) { + redeemLoyaltyBtn.disabled = !cust.eligible_for_free_meal; + if (loyaltyMessage) { + if (cust.eligible_for_free_meal) loyaltyMessage.innerHTML = 'Eligible for Free Meal!'; + else loyaltyMessage.textContent = `${cust.points_needed || 0} pts away from a free meal.`; + } } } - isLoyaltyRedemption = false; } if (clearCustomerBtn) { clearCustomerBtn.addEventListener('click', () => { currentCustomer = null; - selectedCustomerId.value = ''; - customerSearchInput.value = ''; - customerSearchInput.disabled = false; + if (selectedCustomerId) selectedCustomerId.value = ''; + if (customerSearchInput) { + customerSearchInput.value = ''; + customerSearchInput.disabled = false; + } clearCustomerBtn.classList.add('d-none'); if (customerInfo) customerInfo.classList.add('d-none'); - if (loyaltySection) loyaltySection.classList.add('d-none'); isLoyaltyRedemption = false; updateCart(); - - customerSearchInput.focus(); - }); - } - - // Loyalty Redeem Click - if (redeemLoyaltyBtn) { - redeemLoyaltyBtn.addEventListener('click', () => { - if (typeof LOYALTY_SETTINGS === 'undefined' || !LOYALTY_SETTINGS.is_enabled) return; - - if (cart.length === 0) { - showToast("Cart is empty!", "warning"); - return; - } - - if (cart.length > 1) { - showToast("Can only redeem a free meal with exactly one item in cart!", "warning"); - return; - } - - if (!currentCustomer || !currentCustomer.eligible_for_free_meal) return; - - Swal.fire({ - title: 'Redeem Loyalty?', - text: "Redeem points for a free meal? This will finalize the order immediately.", - icon: 'question', - showCancelButton: true, - confirmButtonColor: '#198754', - cancelButtonColor: '#6c757d', - confirmButtonText: 'Yes, redeem and finish!' - }).then((result) => { - if (result.isConfirmed) { - isLoyaltyRedemption = true; - updateCart(); - processOrder(null, 'Loyalty Redeem'); - } - }); }); } - // Points History View - if (viewPointsHistoryBtn) { - viewPointsHistoryBtn.addEventListener('click', () => { - if (!currentCustomer) return; - fetchPointsHistory(currentCustomer.id); - }); - } - - function fetchPointsHistory(customerId) { - if (!pointsHistoryBody || !pointsHistoryModal) return; - pointsHistoryBody.innerHTML = '
Loading...'; - pointsHistoryEmpty.classList.add('d-none'); - pointsHistoryModal.show(); - - fetch(`api/customer_loyalty_history.php?customer_id=${customerId}`) - .then(res => res.json()) - .then(data => { - pointsHistoryBody.innerHTML = ''; - if (data.success && data.history.length > 0) { - data.history.forEach(item => { - const tr = document.createElement('tr'); - const dateObj = new Date(item.created_at); - const date = dateObj.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }) + ' ' + - dateObj.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }); - - const pointsClass = item.points_change > 0 ? 'text-success' : 'text-danger'; - const pointsPrefix = item.points_change > 0 ? '+' : ''; - - tr.innerHTML = ` - ${date} - -
${item.reason}
- ${item.order_id ? `
Order #${item.order_id}
` : ''} - - ${pointsPrefix}${item.points_change} - `; - pointsHistoryBody.appendChild(tr); - }); - } else { - pointsHistoryEmpty.classList.remove('d-none'); - } - }) - .catch(err => { - pointsHistoryBody.innerHTML = 'Error loading history'; - }); - } - - // --- Table & Order Type Logic --- - const orderTypeInputs = document.querySelectorAll('input[name="order_type"]'); - - function checkOrderType() { + window.checkOrderType = function() { const checked = document.querySelector('input[name="order_type"]:checked'); if (!checked) return; - const selected = checked.value; if (selected === 'dine-in') { if (!currentTableId && tableSelectionModal) openTableSelectionModal(); @@ -396,240 +294,64 @@ document.addEventListener('DOMContentLoaded', () => { } else { if (tableDisplay) tableDisplay.style.display = 'none'; } - } + }; - orderTypeInputs.forEach(input => { + document.querySelectorAll('input[name="order_type"]').forEach(input => { input.addEventListener('change', checkOrderType); }); - function openTableSelectionModal() { - if (!tableSelectionModal) return; - const container = document.getElementById('table-list-container'); - if (!container) return; - container.innerHTML = '
'; - - // Update Modal Title - const modalTitle = document.querySelector('#tableSelectionModal .modal-title'); - if (modalTitle && typeof CURRENT_OUTLET !== 'undefined') { - modalTitle.textContent = `Select Table - ${CURRENT_OUTLET.name}`; - } - - tableSelectionModal.show(); - - const outletId = new URLSearchParams(window.location.search).get('outlet_id') || 1; - fetch(`api/tables.php?outlet_id=${outletId}`) - .then(res => res.json()) - .then(data => { - if (data.success) { - renderTables(data.tables); - } else { - container.innerHTML = `
${data.error}
`; - } - }) - .catch(() => { - container.innerHTML = `
Error loading tables.
`; + window.handleProductClick = function(product, variants) { + if (variants && variants.length > 0) { + openVariantModal(product, variants); + } else { + addToCart({ + id: product.id, name: product.name, name_ar: product.name_ar || "", + price: parseFloat(product.price), base_price: parseFloat(product.price), + hasVariants: false, quantity: 1, variant_id: null, variant_name: null }); - } - - function renderTables(tables) { - const container = document.getElementById('table-list-container'); - if (!container) return; - container.innerHTML = ''; - - if (tables.length === 0) { - const outletName = (typeof CURRENT_OUTLET !== 'undefined') ? CURRENT_OUTLET.name : 'this outlet'; - container.innerHTML = `
- - No tables found for ${outletName}. -
`; - return; } - - tables.forEach(table => { - const isOccupied = table.is_occupied; - const col = document.createElement('div'); - col.className = 'col-6 col-md-4 col-lg-3'; - col.innerHTML = ` -
-
-
${table.name}
- ${table.capacity} Pax -
${isOccupied ? 'Occupied' : 'Available'}
-
-
- `; - container.appendChild(col); - }); - } - - window.selectTable = function(id, name) { - currentTableId = id; - currentTableName = name; - if (tableDisplay) { - tableDisplay.innerHTML = `Table: ${name}`; - tableDisplay.style.display = 'block'; - } - if (tableSelectionModal) tableSelectionModal.hide(); - showToast(`Selected Table: ${name}`, 'success'); }; - // --- Cart Logic --- - document.querySelectorAll('.add-to-cart').forEach(card => { - card.addEventListener('click', (e) => { - const target = e.currentTarget; - const product = { - id: target.dataset.id, - name: target.dataset.name, - name_ar: target.dataset.nameAr || '', - price: parseFloat(target.dataset.price), - base_price: parseFloat(target.dataset.price), - hasVariants: target.dataset.hasVariants === 'true', - quantity: 1, - variant_id: null, - variant_name: null - }; - - if (product.hasVariants) { - openVariantModal(product); - } else { - addToCart(product); - } - }); - }); - - function openVariantModal(product) { + function openVariantModal(product, variants) { if (!variantSelectionModal) return; - pendingProduct = product; - const variants = (typeof PRODUCT_VARIANTS !== 'undefined') ? (PRODUCT_VARIANTS[product.id] || []) : []; const list = document.getElementById('variant-list'); const title = document.getElementById('variantModalTitle'); - if (title) title.textContent = `Select option for ${product.name}`; - + if (title) title.textContent = `Option: ${product.name}`; if (!list) return; list.innerHTML = ''; - variants.forEach(v => { const btn = document.createElement('button'); btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center'; - const adj = parseFloat(v.price_adjustment); - const finalPrice = product.base_price + adj; - - btn.innerHTML = ` - ${v.name} - ${formatCurrency(finalPrice)} - `; + const finalPrice = parseFloat(product.price) + parseFloat(v.price_adjustment); + btn.innerHTML = `${v.name}${formatCurrency(finalPrice)}`; btn.onclick = () => { - pendingProduct.variant_id = v.id; - pendingProduct.variant_name = v.name; - pendingProduct.price = finalPrice; - addToCart(pendingProduct); + addToCart({ + id: product.id, name: product.name, name_ar: product.name_ar || "", + price: finalPrice, base_price: parseFloat(product.price), + hasVariants: true, quantity: 1, variant_id: v.id, variant_name: v.name + }); variantSelectionModal.hide(); }; list.appendChild(btn); }); - variantSelectionModal.show(); } - function addToCart(product) { + window.addToCart = function(product) { const existing = cart.find(item => item.id === product.id && item.variant_id === product.variant_id); - if (existing) { - existing.quantity++; - } else { - cart.push({...product}); - } + if (existing) existing.quantity++; + else cart.push({...product}); updateCart(); - } + }; window.changeQuantity = function(index, delta) { if (cart[index]) { cart[index].quantity += delta; - if (cart[index].quantity <= 0) { - removeFromCart(index); - } else { - updateCart(); - } + if (cart[index].quantity <= 0) cart.splice(index, 1); + updateCart(); } }; - function updateCart() { - if (reprintReceiptBtn) { - if (currentOrderId) reprintReceiptBtn.classList.remove('d-none'); - else reprintReceiptBtn.classList.add('d-none'); - } - - if (cart.length === 0) { - if (cartItemsContainer) { - cartItemsContainer.innerHTML = ` -
- -

Cart is empty

-
`; - } - if (cartSubtotal) cartSubtotal.innerText = formatCurrency(0); - if (cartVatInput) cartVatInput.value = 0; - if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0); - if (quickOrderBtn) quickOrderBtn.disabled = true; - if (placeOrderBtn) placeOrderBtn.disabled = true; - return; - } - - if (cartItemsContainer) cartItemsContainer.innerHTML = ''; - let subtotal = 0; - - cart.forEach((item, index) => { - const itemTotal = item.price * item.quantity; - subtotal += itemTotal; - - if (cartItemsContainer) { - const row = document.createElement('div'); - row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2'; - - const variantLabel = item.variant_name ? `${item.variant_name}` : ''; - const arabicNameDisplay = item.name_ar ? `
${item.name_ar}
` : ''; - - row.innerHTML = ` -
-
${item.name}
- ${arabicNameDisplay} -
${formatCurrency(item.price)} ${variantLabel}
-
-
- - ${item.quantity} - -
-
-
${formatCurrency(itemTotal)}
- -
- `; - cartItemsContainer.appendChild(row); - } - }); - - if (cartSubtotal) cartSubtotal.innerText = formatCurrency(subtotal); - - let vat = 0; - if (isLoyaltyRedemption) { - vat = -subtotal; - } else { - const vatRate = parseFloat(settings.vat_rate) || 0; - vat = subtotal * (vatRate / 100); - } - - if (cartVatInput) cartVatInput.value = vat.toFixed(2); - - let total = subtotal + vat; - if (total < 0) total = 0; - - if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(total); - if (quickOrderBtn) quickOrderBtn.disabled = false; - if (placeOrderBtn) placeOrderBtn.disabled = false; - } - window.removeFromCart = function(index) { cart.splice(index, 1); updateCart(); @@ -637,494 +359,115 @@ document.addEventListener('DOMContentLoaded', () => { window.clearCart = function() { if (cart.length === 0) return; - Swal.fire({ - title: 'Clear Cart?', - text: "Are you sure you want to remove all items from the cart?", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#dc3545', - cancelButtonColor: '#6c757d', - confirmButtonText: 'Yes, clear it!' - }).then((result) => { - if (result.isConfirmed) { - cart = []; - if (cartVatInput) cartVatInput.value = 0; - currentOrderId = null; - isLoyaltyRedemption = false; - updateCart(); - showToast("Cart cleared", "success"); - } - }); + cart = []; + currentOrderId = null; + isLoyaltyRedemption = false; + updateCart(); + showToast("Cart cleared", "success"); }; - // --- Payment Selection Logic --- - function renderPaymentMethods() { - if (!paymentMethodsContainer) return; - paymentMethodsContainer.innerHTML = ''; - - if (typeof PAYMENT_TYPES !== 'undefined' && PAYMENT_TYPES.length > 0) { - PAYMENT_TYPES.forEach(pt => { - const col = document.createElement('div'); - col.className = 'col-6'; - col.innerHTML = ` - - `; - paymentMethodsContainer.appendChild(col); - }); - } else { - paymentMethodsContainer.innerHTML = '
No payment methods configured.
'; + function updateCart() { + if (!cartItemsContainer) return; + if (cart.length === 0) { + cartItemsContainer.innerHTML = '

Cart is empty

'; + if (cartSubtotal) cartSubtotal.innerText = formatCurrency(0); + if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0); + if (quickOrderBtn) quickOrderBtn.disabled = true; + if (placeOrderBtn) placeOrderBtn.disabled = true; + return; } - } - function getPaymentIcon(name) { - const n = name.toLowerCase(); - if (n.includes('cash')) return 'bi-cash-coin'; - if (n.includes('card') || n.includes('visa') || n.includes('master')) return 'bi-credit-card'; - if (n.includes('qr') || n.includes('scan')) return 'bi-qr-code'; - if (n.includes('bank') || n.includes('transfer')) return 'bi-building-columns'; - return 'bi-wallet2'; - } + cartItemsContainer.innerHTML = ''; + let subtotal = 0; + cart.forEach((item, index) => { + const itemTotal = item.price * item.quantity; + subtotal += itemTotal; + const row = document.createElement('div'); + row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2'; + row.innerHTML = ` +
+
${item.name}
+ ${item.name_ar ? `
${item.name_ar}
` : ''} +
${formatCurrency(item.price)}
+
+
+ + ${item.quantity} + +
+
+
${formatCurrency(itemTotal)}
+ +
+ `; + cartItemsContainer.appendChild(row); + }); - renderPaymentMethods(); - - // --- Checkout Flow (Quick Order) --- - function validateOrder() { - if (cart.length === 0) return false; - - const orderTypeInput = document.querySelector('input[name="order_type"]:checked'); - const orderType = orderTypeInput ? orderTypeInput.value : 'takeaway'; - - if (orderType === 'dine-in' && !currentTableId) { - showToast('Please select a table first', 'warning'); - openTableSelectionModal(); - return false; - } - return true; + if (cartSubtotal) cartSubtotal.innerText = formatCurrency(subtotal); + const vatRate = parseFloat(settings.vat_rate) || 0; + const vat = subtotal * (vatRate / 100); + if (cartVatInput) cartVatInput.value = vat.toFixed(2); + const total = subtotal + vat; + if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(total); + if (quickOrderBtn) quickOrderBtn.disabled = false; + if (placeOrderBtn) placeOrderBtn.disabled = false; } if (quickOrderBtn) { quickOrderBtn.addEventListener('click', () => { - if (validateOrder() && paymentSelectionModal) { + if (cart.length > 0 && paymentSelectionModal) { + renderPaymentMethods(); paymentSelectionModal.show(); } }); } - // --- Place Order (Pay Later) Flow --- if (placeOrderBtn) { placeOrderBtn.addEventListener('click', () => { - if (validateOrder()) { - Swal.fire({ - title: 'Place Order?', - text: "Place order without immediate payment?", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#ffc107', - cancelButtonColor: '#6c757d', - confirmButtonText: 'Yes, place order' - }).then((result) => { - if (result.isConfirmed) { - processOrder(null, 'Pay Later'); - } - }); - } + if (cart.length > 0) processOrder(null, 'Pay Later'); }); } + function renderPaymentMethods() { + if (!paymentMethodsContainer) return; + paymentMethodsContainer.innerHTML = ''; + if (typeof PAYMENT_TYPES !== 'undefined') { + PAYMENT_TYPES.forEach(pt => { + const col = document.createElement('div'); + col.className = 'col-6'; + col.innerHTML = ``; + paymentMethodsContainer.appendChild(col); + }); + } + } + window.processOrder = function(paymentTypeId, paymentTypeName) { const orderTypeInput = document.querySelector('input[name="order_type"]:checked'); const orderType = orderTypeInput ? orderTypeInput.value : 'takeaway'; const subtotal = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0); - const vat = parseFloat(cartVatInput ? cartVatInput.value : 0) || 0; - const totalAmount = Math.max(0, subtotal + vat); - const custId = selectedCustomerId ? selectedCustomerId.value : null; - + const vatRate = parseFloat(settings.vat_rate) || 0; + const vat = subtotal * (vatRate / 100); + const orderData = { order_id: currentOrderId, table_number: (orderType === 'dine-in') ? currentTableId : null, order_type: orderType, - customer_id: custId || null, - outlet_id: new URLSearchParams(window.location.search).get('outlet_id') || 1, + customer_id: selectedCustomerId ? selectedCustomerId.value : null, + outlet_id: CURRENT_OUTLET ? CURRENT_OUTLET.id : 1, payment_type_id: paymentTypeId, - total_amount: totalAmount, - vat: vat, - redeem_loyalty: isLoyaltyRedemption, - items: cart.map(item => ({ - product_id: item.id, - quantity: item.quantity, - unit_price: item.price, - variant_id: item.variant_id - })) + total_amount: subtotal + vat, + vat: vat, items: cart.map(item => ({ product_id: item.id, quantity: item.quantity, unit_price: item.price, variant_id: item.variant_id })) }; - - // Disable buttons - if (paymentMethodsContainer) { - const btns = paymentMethodsContainer.querySelectorAll('button'); - btns.forEach(b => b.disabled = true); - } - if (quickOrderBtn) quickOrderBtn.disabled = true; - if (placeOrderBtn) placeOrderBtn.disabled = true; - fetch('api/order.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(orderData) - }) + fetch('api/order.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) }) .then(res => res.json()) .then(data => { - if (paymentMethodsContainer) { - const btns = paymentMethodsContainer.querySelectorAll('button'); - btns.forEach(b => b.disabled = false); - } - if (quickOrderBtn) quickOrderBtn.disabled = false; - if (placeOrderBtn) placeOrderBtn.disabled = false; - - if (paymentSelectionModal) paymentSelectionModal.hide(); - if (data.success) { - // Print Receipt - printReceipt({ - orderId: data.order_id, - customer: currentCustomer, - items: [...cart], - total: totalAmount, - vat: vat, - orderType: orderType, - tableNumber: (orderType === 'dine-in') ? currentTableName : null, - date: new Date().toLocaleString(), - paymentMethod: paymentTypeName, - loyaltyRedeemed: isLoyaltyRedemption - }); - - cart = []; - if (cartVatInput) cartVatInput.value = 0; - currentOrderId = null; - isLoyaltyRedemption = false; - updateCart(); - if (clearCustomerBtn) clearCustomerBtn.click(); showToast(`Order #${data.order_id} placed!`, 'success'); - } else { - showToast(`Error: ${data.error}`, 'danger'); - } - }) - .catch(err => { - if (paymentMethodsContainer) { - const btns = paymentMethodsContainer.querySelectorAll('button'); - btns.forEach(b => b.disabled = false); - } - if (quickOrderBtn) quickOrderBtn.disabled = false; - if (placeOrderBtn) placeOrderBtn.disabled = false; - - if (paymentSelectionModal) paymentSelectionModal.hide(); - showToast('Network Error', 'danger'); + clearCart(); + if (paymentSelectionModal) paymentSelectionModal.hide(); + if (clearCustomerBtn) clearCustomerBtn.click(); + } else showToast(data.error, 'danger'); }); }; - - function showToast(msg, type = 'primary') { - let icon = 'info'; - if (type === 'success') icon = 'success'; - if (type === 'danger') icon = 'error'; - if (type === 'warning') icon = 'warning'; - - Toast.fire({ - icon: icon, - title: msg - }); - } - - window.reprintCurrentReceipt = function() { - if (!currentOrderId) return; - - const subtotal = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0); - const vat = parseFloat(cartVatInput ? cartVatInput.value : 0) || 0; - const totalAmount = Math.max(0, subtotal + vat); - const orderType = document.querySelector('input[name="order_type"]:checked').value; - - printReceipt({ - orderId: currentOrderId, - customer: currentCustomer, - items: [...cart], - total: totalAmount, - vat: vat, - orderType: orderType, - tableNumber: (orderType === 'dine-in') ? currentTableName : null, - date: new Date().toLocaleString() + ' (Reprint)', - paymentMethod: 'Previously Settled', - loyaltyRedeemed: isLoyaltyRedemption - }); - }; - - function printReceipt(data) { - const width = 400; - const height = 800; - const left = (screen.width - width) / 2; - const top = (screen.height - height) / 2; - - const win = window.open('', '_blank', `width=${width},height=${height},top=${top},left=${left}`); - - if (!win) { - alert('Please allow popups for this website to print receipts.'); - return; - } - - const tr = { - 'Order': 'الطلب', - 'Type': 'النوع', - 'Date': 'التاريخ', - 'Staff': 'الموظف', - 'Table': 'طاولة', - 'Payment': 'الدفع', - 'ITEM': 'الصنف', - 'TOTAL': 'المجموع', - 'Subtotal': 'المجموع الفرعي', - 'VAT': 'ضريبة القيمة المضافة', - 'Tax Included': 'شامل الضريبة', - 'THANK YOU FOR YOUR VISIT!': 'شكراً لزيارتكم!', - 'Please come again.': 'يرجى زيارتنا مرة أخرى.', - 'Customer Details': 'تفاصيل العميل', - 'Tel': 'هاتف', - 'takeaway': 'سفري', - 'dine-in': 'محلي', - 'delivery': 'توصيل', - 'VAT No': 'الرقم الضريبي', - 'CTR No': 'رقم السجل التجاري' - }; - - const itemsHtml = data.items.map(item => ` - - -
${item.name}
- ${item.name_ar ? `
${item.name_ar}
` : ''} - ${item.variant_name ? `
(${item.variant_name})
` : ''} -
${item.quantity} x ${formatCurrency(item.price)}
- - ${formatCurrency(item.quantity * item.price)} - - `).join(''); - - const customerHtml = data.customer ? ` -
-
- Customer Details - ${tr['Customer Details']} -
-
-
${data.customer.name}
- ${data.customer.phone ? `
Tel: ${data.customer.phone}
` : ''} - ${data.customer.address ? `
${data.customer.address}
` : ''} -
- ` : ''; - - const tableHtml = data.tableNumber ? ` -
- Table: ${data.tableNumber} - ${tr['Table']}: ${data.tableNumber} -
` : ''; - - const paymentHtml = data.paymentMethod ? ` -
- Payment: ${data.paymentMethod} - ${tr['Payment']}: ${data.paymentMethod} -
` : ''; - - const loyaltyHtml = data.loyaltyRedeemed ? `
* Loyalty Reward Applied *
` : ''; - - const subtotal = data.total - data.vat; - const baseUrl = (typeof BASE_URL !== 'undefined') ? BASE_URL : ''; - const logoHtml = settings.logo_url ? `` : ''; - const outletName = (typeof CURRENT_OUTLET !== 'undefined') ? CURRENT_OUTLET.name : ''; - const staffName = (typeof CURRENT_USER !== 'undefined') ? CURRENT_USER.name : ''; - - const html = ` - - - Receipt #${data.orderId} - - - - -
- ${logoHtml} -

${settings.company_name}

-
${outletName}
-
${settings.address}
-
Tel: ${settings.phone}
- ${settings.vat_number ? `
VAT No / الرقم الضريبي: ${settings.vat_number}
` : ''} - ${settings.ctr_number ? `
CTR No / رقم السجل: ${settings.ctr_number}
` : ''} -
- -
- -
-
- Order: #${data.orderId} - ${tr['Order']}: #${data.orderId} -
-
- Type: ${data.orderType.toUpperCase()} - ${tr['Type']}: ${tr[data.orderType] || data.orderType} -
-
- Date: ${data.date} - ${tr['Date']}: ${data.date} -
-
- Staff: ${staffName} - ${tr['Staff']}: ${staffName} -
-
- - ${tableHtml} - ${paymentHtml} - ${loyaltyHtml} - -
- ${customerHtml} - - - - - - - - - - ${itemsHtml} - -
- ITEM / الصنف - - TOTAL / المجموع -
- -
- -
- - - - - - ${Math.abs(data.vat) > 0 ? ` - - - - ` : ''} - - - - -
Subtotal / ${tr['Subtotal']}${formatCurrency(subtotal)}
${data.vat < 0 ? 'Discount' : 'VAT'} / ${tr['VAT']}${data.vat < 0 ? '-' : '+'}${formatCurrency(Math.abs(data.vat))}
TOTAL / ${tr['TOTAL']}${formatCurrency(data.total)}
-
- -
- - - - - - - `; - - win.document.write(html); - win.document.close(); - } - - // Initialize logic - const urlParams = new URLSearchParams(window.location.search); - if (!urlParams.has('order_type')) { - const otTakeaway = document.getElementById('ot-takeaway'); - if (otTakeaway) { - otTakeaway.checked = true; - } - } - checkOrderType(); - - // --- Add Customer Logic --- - const addCustomerModalEl = document.getElementById('addCustomerModal'); - const saveCustomerBtn = document.getElementById('save-new-customer'); - const newCustomerName = document.getElementById('new-customer-name'); - const newCustomerPhone = document.getElementById('new-customer-phone'); - - if (saveCustomerBtn && newCustomerName && newCustomerPhone) { - saveCustomerBtn.addEventListener('click', () => { - const name = newCustomerName.value.trim(); - const phone = newCustomerPhone.value.trim(); - - if (name === '') { - showToast('Name is required', 'warning'); - return; - } - - saveCustomerBtn.disabled = true; - saveCustomerBtn.textContent = 'Saving...'; - - fetch('api/create_customer.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: name, phone: phone }) - }) - .then(res => res.json()) - .then(data => { - saveCustomerBtn.disabled = false; - saveCustomerBtn.textContent = 'Save Customer'; - - if (data.success) { - if (addCustomerModalEl) { - const modal = bootstrap.Modal.getInstance(addCustomerModalEl); - if (modal) modal.hide(); - } - selectCustomer(data.customer); - showToast('Customer created successfully', 'success'); - } else { - showToast(data.error || 'Error creating customer', 'danger'); - } - }) - .catch(err => { - saveCustomerBtn.disabled = false; - saveCustomerBtn.textContent = 'Save Customer'; - showToast('Network error', 'danger'); - }); - }); - } -}); +}); \ No newline at end of file diff --git a/pos.php b/pos.php index be6cc9a..e9dc924 100644 --- a/pos.php +++ b/pos.php @@ -85,21 +85,47 @@ if (!$loyalty_settings) { -
-