document.addEventListener('DOMContentLoaded', () => { let cart = []; const cartItemsContainer = document.getElementById('cart-items'); const cartTotalPrice = document.getElementById('cart-total-price'); const cartSubtotal = document.getElementById('cart-subtotal'); const cartDiscountInput = document.getElementById('cart-discount-input'); // Updated Button References const quickOrderBtn = document.getElementById('quick-order-btn'); const placeOrderBtn = document.getElementById('place-order-btn'); // Loyalty State let isLoyaltyRedemption = false; const loyaltySection = document.getElementById('loyalty-section'); const loyaltyPointsDisplay = document.getElementById('loyalty-points-display'); const loyaltyMessage = document.getElementById('loyalty-message'); const redeemLoyaltyBtn = document.getElementById('redeem-loyalty-btn'); // Table Management let currentTableId = null; let currentTableName = null; const tableDisplay = document.getElementById('current-table-display'); const tableModalEl = document.getElementById('tableSelectionModal'); const tableSelectionModal = new bootstrap.Modal(tableModalEl); // Variant Management const variantModalEl = document.getElementById('variantSelectionModal'); const variantSelectionModal = new bootstrap.Modal(variantModalEl); 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'); const clearCustomerBtn = document.getElementById('clear-customer'); const customerInfo = document.getElementById('customer-info'); const customerNameDisplay = document.getElementById('customer-name-display'); let currentCustomer = null; // Payment Modal const paymentModalEl = document.getElementById('paymentSelectionModal'); const paymentSelectionModal = new bootstrap.Modal(paymentModalEl); const paymentMethodsContainer = document.getElementById('payment-methods-container'); // Product Search & Filter const productSearchInput = document.getElementById('product-search-input'); let currentCategory = 'all'; let currentSearchQuery = ''; // Helper for currency function formatCurrency(amount) { const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 }; 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); if (matchesCategory && matchesSearch) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); } window.filterCategory = function(categoryId, btnElement) { currentCategory = categoryId; // Update Active Button State if (btnElement) { document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active')); btnElement.classList.add('active'); } else if (typeof btnElement === 'undefined' && categoryId !== 'all') { // Try to find the button corresponding to this category ID } filterProducts(); }; if (productSearchInput) { productSearchInput.addEventListener('input', (e) => { currentSearchQuery = e.target.value.trim().toLowerCase(); filterProducts(); }); } // --- Customer Search --- let searchTimeout; if (customerSearchInput) { customerSearchInput.addEventListener('input', (e) => { const query = e.target.value.trim(); clearTimeout(searchTimeout); if (query.length < 2) { customerResults.style.display = 'none'; return; } searchTimeout = setTimeout(() => { fetch(`api/search_customers.php?q=${encodeURIComponent(query)}`) .then(res => res.json()) .then(data => { customerResults.innerHTML = ''; if (data.length > 0) { data.forEach(cust => { const a = document.createElement('a'); a.href = '#'; a.className = 'list-group-item list-group-item-action'; a.innerHTML = `
${cust.name}
${cust.phone || ''}
`; a.onclick = (ev) => { ev.preventDefault(); selectCustomer(cust); }; customerResults.appendChild(a); }); customerResults.style.display = 'block'; } else { customerResults.innerHTML = '
No results found
'; customerResults.style.display = 'block'; } }); }, 300); }); // Close search on click outside document.addEventListener('click', (e) => { if (!customerSearchInput.contains(e.target) && !customerResults.contains(e.target)) { customerResults.style.display = 'none'; } }); } 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'); // customerInfo.classList.remove('d-none'); // Loyalty Logic if (loyaltySection) { 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.`; } } // Reset redemption state when switching customer (though usually we clear first) isLoyaltyRedemption = false; } if (clearCustomerBtn) { clearCustomerBtn.addEventListener('click', () => { currentCustomer = null; selectedCustomerId.value = ''; customerSearchInput.value = ''; customerSearchInput.disabled = false; clearCustomerBtn.classList.add('d-none'); customerInfo.classList.add('d-none'); // Hide Loyalty if (loyaltySection) loyaltySection.classList.add('d-none'); isLoyaltyRedemption = false; cartDiscountInput.value = 0; updateCart(); customerSearchInput.focus(); }); } // Loyalty Redeem Click if (redeemLoyaltyBtn) { redeemLoyaltyBtn.addEventListener('click', () => { if (cart.length === 0) { showToast("Cart is empty!", "warning"); return; } if (!currentCustomer || !currentCustomer.eligible_for_free_meal) return; if (confirm("Redeem 70 points for a free meal? This will apply a full discount to the current order.")) { isLoyaltyRedemption = true; // Calculate total and apply as discount const subtotal = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0); cartDiscountInput.value = subtotal.toFixed(2); updateCart(); showToast("Loyalty Redemption Applied!", "success"); redeemLoyaltyBtn.disabled = true; // Prevent double click loyaltyMessage.innerHTML = ' Redeemed! Place order to finalize.'; } }); } // --- Table & Order Type Logic --- const orderTypeInputs = document.querySelectorAll('input[name="order_type"]'); function checkOrderType() { const checked = document.querySelector('input[name="order_type"]:checked'); if (!checked) return; const selected = checked.value; if (selected === 'dine-in') { if (!currentTableId) openTableSelectionModal(); if (tableDisplay) tableDisplay.style.display = 'inline-block'; } else { if (tableDisplay) tableDisplay.style.display = 'none'; } } orderTypeInputs.forEach(input => { input.addEventListener('change', checkOrderType); }); function openTableSelectionModal() { const container = document.getElementById('table-list-container'); 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.
`; }); } function renderTables(tables) { const container = document.getElementById('table-list-container'); 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'; } 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, 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) { pendingProduct = product; const variants = PRODUCT_VARIANTS[product.id] || []; const list = document.getElementById('variant-list'); const title = document.getElementById('variantModalTitle'); title.textContent = `Select option for ${product.name}`; 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 sign = adj > 0 ? '+' : ''; const finalPrice = product.base_price + adj; btn.innerHTML = ` ${v.name} ${formatCurrency(finalPrice)} `; btn.onclick = () => { pendingProduct.variant_id = v.id; pendingProduct.variant_name = v.name; pendingProduct.price = finalPrice; addToCart(pendingProduct); variantSelectionModal.hide(); }; list.appendChild(btn); }); variantSelectionModal.show(); } function addToCart(product) { const existing = cart.find(item => item.id === product.id && item.variant_id === product.variant_id); 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(); } } }; function updateCart() { if (cart.length === 0) { cartItemsContainer.innerHTML = `

Cart is empty

`; cartSubtotal.innerText = formatCurrency(0); cartTotalPrice.innerText = formatCurrency(0); if (quickOrderBtn) quickOrderBtn.disabled = true; if (placeOrderBtn) placeOrderBtn.disabled = true; return; } 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'; const variantLabel = item.variant_name ? `${item.variant_name}` : ''; row.innerHTML = `
${item.name}
${formatCurrency(item.price)} ${variantLabel}
${item.quantity}
${formatCurrency(itemTotal)}
`; cartItemsContainer.appendChild(row); }); cartSubtotal.innerText = formatCurrency(subtotal); let discount = parseFloat(cartDiscountInput.value) || 0; if (isLoyaltyRedemption) { discount = subtotal; cartDiscountInput.value = subtotal.toFixed(2); } let total = subtotal - discount; if (total < 0) total = 0; cartTotalPrice.innerText = formatCurrency(total); if (quickOrderBtn) quickOrderBtn.disabled = false; if (placeOrderBtn) placeOrderBtn.disabled = false; } if (cartDiscountInput) { cartDiscountInput.addEventListener('input', () => { updateCart(); }); } window.removeFromCart = function(index) { cart.splice(index, 1); updateCart(); }; // --- 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 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'; return 'bi-wallet2'; } 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 (quickOrderBtn) { quickOrderBtn.addEventListener('click', () => { if (validateOrder()) { paymentSelectionModal.show(); } }); } // --- Place Order (Pay Later) Flow --- if (placeOrderBtn) { placeOrderBtn.addEventListener('click', () => { if (validateOrder()) { if (confirm("Place order without immediate payment?")) { processOrder(null, 'Pay Later'); } } }); } 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 discount = parseFloat(cartDiscountInput.value) || 0; const totalAmount = Math.max(0, subtotal - discount); const custId = selectedCustomerId.value; const orderData = { 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, payment_type_id: paymentTypeId, total_amount: totalAmount, discount: discount, redeem_loyalty: isLoyaltyRedemption, 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) }) .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; paymentSelectionModal.hide(); if (data.success) { // Print Receipt printReceipt({ orderId: data.order_id, customer: currentCustomer, items: [...cart], total: totalAmount, discount: discount, orderType: orderType, tableNumber: (orderType === 'dine-in') ? currentTableName : null, date: new Date().toLocaleString(), paymentMethod: paymentTypeName, loyaltyRedeemed: isLoyaltyRedemption }); cart = []; cartDiscountInput.value = 0; isLoyaltyRedemption = false; // Reset 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; paymentSelectionModal.hide(); showToast('Network Error', 'danger'); }); }; function showToast(msg, type = 'primary') { const toastContainer = document.getElementById('toast-container'); const id = 'toast-' + Date.now(); const html = ` `; toastContainer.insertAdjacentHTML('beforeend', html); const el = document.getElementById(id); const t = new bootstrap.Toast(el, { delay: 3000 }); t.show(); el.addEventListener('hidden.bs.toast', () => el.remove()); } function printReceipt(data) { const width = 300; const height = 600; const left = (screen.width - width) / 2; const top = (screen.height - height) / 2; const win = window.open('', 'Receipt', `width=${width},height=${height},top=${top},left=${left}`); const itemsHtml = data.items.map(item => ` ${item.name}
${item.variant_name ? `(${item.variant_name})` : ''} ${item.quantity} x ${formatCurrency(item.price)} `).join(''); const customerHtml = data.customer ? `
Customer:
${data.customer.name}
${data.customer.phone || ''}
` : ''; const tableHtml = data.tableNumber ? `
Table: ${data.tableNumber}
` : ''; const paymentHtml = data.paymentMethod ? `
Payment: ${data.paymentMethod}
` : ''; const loyaltyHtml = data.loyaltyRedeemed ? `
* Free Meal Redeemed *
` : ''; const html = ` Receipt #${data.orderId}

FLATLOGIC POS

123 Main St, City
Tel: 123-456-7890
Order #${data.orderId}
${data.date}
Type: ${data.orderType.toUpperCase()}
${tableHtml} ${paymentHtml} ${loyaltyHtml}
${customerHtml} ${itemsHtml}
${data.discount > 0 ? ` ` : ''}
Subtotal ${formatCurrency(data.total + data.discount)}
Discount -${formatCurrency(data.discount)}
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 addCustomerBtn = document.getElementById('add-customer-btn'); const addCustomerModalEl = document.getElementById('addCustomerModal'); if (addCustomerBtn && addCustomerModalEl) { const addCustomerModal = new bootstrap.Modal(addCustomerModalEl); const saveCustomerBtn = document.getElementById('save-new-customer'); const newCustomerName = document.getElementById('new-customer-name'); const newCustomerPhone = document.getElementById('new-customer-phone'); const phoneError = document.getElementById('phone-error'); addCustomerBtn.addEventListener('click', () => { newCustomerName.value = ''; newCustomerPhone.value = ''; phoneError.classList.add('d-none'); addCustomerModal.show(); }); saveCustomerBtn.addEventListener('click', () => { const name = newCustomerName.value.trim(); const phone = newCustomerPhone.value.trim(); if (name === '') { showToast('Name is required', 'warning'); return; } // 8 digits validation if (!/^\d{8}$/.test(phone)) { phoneError.classList.remove('d-none'); return; } else { phoneError.classList.add('d-none'); } 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) { addCustomerModal.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'); }); }); } });