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 = '
${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 = `
-
`;
- }
- 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 = '
';
+ 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}
-
-
-
-
-
-
-
-
-
-
- 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}
-
-
-
-
- |
- ITEM / الصنف
- |
-
- TOTAL / المجموع
- |
-
-
-
- ${itemsHtml}
-
-
-
-
-
-
-
-
- | Subtotal / ${tr['Subtotal']} |
- ${formatCurrency(subtotal)} |
-
- ${Math.abs(data.vat) > 0 ? `
-
- | ${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) {
-