1014 lines
48 KiB
JavaScript
1014 lines
48 KiB
JavaScript
console.log('POS Script Loading...');
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Trigger crons silently (max once per hour)
|
|
setTimeout(() => {
|
|
const lastCron = localStorage.getItem('last_cron_trigger_pos');
|
|
const now = Date.now();
|
|
if (!lastCron || (now - parseInt(lastCron)) > 3600000) {
|
|
localStorage.setItem('last_cron_trigger_pos', now.toString());
|
|
}
|
|
}, 5000);
|
|
|
|
console.log('POS Script DOMContentLoaded');
|
|
|
|
// SweetAlert2 Toast Mixin
|
|
const Toast = (typeof Swal !== 'undefined') ? Swal.mixin({
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
timerProgressBar: true,
|
|
didOpen: (toast) => {
|
|
toast.addEventListener('mouseenter', Swal.stopTimer);
|
|
toast.addEventListener('mouseleave', Swal.resumeTimer);
|
|
}
|
|
}) : null;
|
|
|
|
window.showToast = showToast;
|
|
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 : {
|
|
currency_symbol: '$',
|
|
currency_decimals: 2,
|
|
currency_position: 'before',
|
|
vat_rate: 0
|
|
};
|
|
|
|
let cart = [];
|
|
let currentOrderId = null;
|
|
const cartItemsContainer = document.getElementById('cart-items');
|
|
const cartTotalPrice = document.getElementById('cart-total-price');
|
|
const cartSubtotal = document.getElementById('cart-subtotal');
|
|
const cartVatAmount = document.getElementById('cart-vat-amount');
|
|
const cartVatRow = document.getElementById('cart-vat-row');
|
|
const cartVatInput = document.getElementById('cart-vat-input');
|
|
|
|
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');
|
|
|
|
const recallModalEl = document.getElementById('recallOrderModal');
|
|
const recallModal = recallModalEl ? new bootstrap.Modal(recallModalEl) : null;
|
|
const recallList = document.getElementById('recall-orders-list');
|
|
|
|
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');
|
|
const viewPointsHistoryBtn = document.getElementById('view-points-history-btn');
|
|
|
|
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');
|
|
|
|
const addCustomerModalEl = document.getElementById('addCustomerModal');
|
|
const addCustomerModal = addCustomerModalEl ? new bootstrap.Modal(addCustomerModalEl) : null;
|
|
const saveNewCustomerBtn = document.getElementById('save-new-customer');
|
|
|
|
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;
|
|
|
|
const variantModalEl = document.getElementById('variantSelectionModal');
|
|
const variantSelectionModal = variantModalEl ? new bootstrap.Modal(variantModalEl) : null;
|
|
let pendingProduct = null;
|
|
|
|
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;
|
|
|
|
const paymentModalEl = document.getElementById('paymentSelectionModal');
|
|
const paymentSelectionModal = paymentModalEl ? new bootstrap.Modal(paymentModalEl) : null;
|
|
const paymentMethodsContainer = document.getElementById('payment-methods-container');
|
|
|
|
const productSearchInput = document.getElementById('product-search');
|
|
let currentCategory = 'all';
|
|
let currentSearchQuery = '';
|
|
|
|
// Translation helper check
|
|
const _t = (key) => (typeof t === 'function') ? t(key) : key;
|
|
|
|
function formatCurrency(amount) {
|
|
const symbol = settings.currency_symbol || '$';
|
|
const decimals = parseInt(settings.currency_decimals || 2);
|
|
const position = settings.currency_position || 'before';
|
|
const formatted = parseFloat(Math.abs(amount || 0)).toFixed(decimals);
|
|
|
|
if (position === 'after') {
|
|
return formatted + ' ' + symbol;
|
|
} else {
|
|
return symbol + formatted;
|
|
}
|
|
}
|
|
|
|
function filterProducts() {
|
|
const items = document.querySelectorAll('.product-item');
|
|
items.forEach(item => {
|
|
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);
|
|
|
|
item.style.display = (matchesCategory && matchesSearch) ? 'block' : 'none';
|
|
});
|
|
}
|
|
|
|
function filterCategory(categoryId, btnElement) {
|
|
currentCategory = categoryId;
|
|
document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active'));
|
|
if (btnElement) {
|
|
btnElement.classList.add('active');
|
|
} else {
|
|
const btn = document.querySelector(`.category-btn[data-category="${categoryId}"]`);
|
|
if (btn) btn.classList.add('active');
|
|
}
|
|
filterProducts();
|
|
}
|
|
window.filterCategory = filterCategory;
|
|
|
|
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();
|
|
filterProducts();
|
|
});
|
|
}
|
|
|
|
const productGrid = document.getElementById('product-grid');
|
|
if (productGrid) {
|
|
productGrid.addEventListener('click', (e) => {
|
|
const item = e.target.closest('.product-item');
|
|
if (item) {
|
|
try {
|
|
const product = JSON.parse(item.dataset.product);
|
|
const variants = JSON.parse(item.dataset.variants);
|
|
handleProductClick(product, variants);
|
|
} catch (err) {
|
|
console.error('Error parsing product data:', err);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function openRecallOrderModal() {
|
|
if (!recallModal) return;
|
|
fetchRecallOrders();
|
|
recallModal.show();
|
|
}
|
|
window.openRecallOrderModal = openRecallOrderModal;
|
|
|
|
function fetchRecallOrders() {
|
|
if (!recallList) return;
|
|
recallList.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary"></div></div>';
|
|
|
|
const outletId = CURRENT_OUTLET ? CURRENT_OUTLET.id : 1;
|
|
fetch(`api/recall_orders.php?action=list&outlet_id=${outletId}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
recallList.innerHTML = '';
|
|
if (data.success && data.orders.length > 0) {
|
|
data.orders.forEach(order => {
|
|
const item = document.createElement('button');
|
|
item.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
|
|
item.innerHTML = `
|
|
<div>
|
|
<div class="fw-bold">Order #${order.id} <span class="badge bg-secondary ms-1">${order.order_type}</span></div>
|
|
<small class="text-muted">${order.customer_name || 'Guest'} ${order.table_number ? ' • Table ' + order.table_number : ''} • ${order.time_formatted}</small>
|
|
</div>
|
|
<div class="text-end">
|
|
<div class="fw-bold text-primary">${formatCurrency(order.total_amount)}</div>
|
|
<small class="text-muted">${order.item_count} items</small>
|
|
</div>
|
|
`;
|
|
item.onclick = () => loadRecalledOrder(order.id);
|
|
recallList.appendChild(item);
|
|
});
|
|
} else {
|
|
recallList.innerHTML = `<div class="p-4 text-center text-muted">${_t('none')}</div>`;
|
|
}
|
|
})
|
|
.catch(() => {
|
|
recallList.innerHTML = `<div class="alert alert-danger">${_t('error')}</div>`;
|
|
});
|
|
}
|
|
|
|
function loadRecalledOrder(orderId) {
|
|
fetch(`api/recall_orders.php?action=details&id=${orderId}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
currentOrderId = data.order.id;
|
|
if (data.customer) selectCustomer(data.customer);
|
|
else if (clearCustomerBtn) clearCustomerBtn.click();
|
|
|
|
const otInput = document.querySelector(`input[name="order_type"][value="${data.order.order_type}"]`);
|
|
if (otInput) {
|
|
otInput.checked = true;
|
|
if (data.order.order_type === 'dine-in' && data.order.table_id) {
|
|
selectTable(data.order.table_id, data.order.table_number);
|
|
} else {
|
|
checkOrderType();
|
|
}
|
|
}
|
|
cart = data.items;
|
|
updateCart();
|
|
if (recallModal) recallModal.hide();
|
|
showToast(`Order #${orderId} loaded!`, 'success');
|
|
} else {
|
|
showToast(data.error || _t('error'), 'danger');
|
|
}
|
|
});
|
|
}
|
|
|
|
if (customerSearchInput) {
|
|
let searchTimeout;
|
|
customerSearchInput.addEventListener('input', (e) => {
|
|
const query = e.target.value.trim();
|
|
clearTimeout(searchTimeout);
|
|
if (query.length < 2) {
|
|
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 => {
|
|
const a = document.createElement('a');
|
|
a.href = '#';
|
|
a.className = 'list-group-item list-group-item-action';
|
|
a.innerHTML = `<div class="fw-bold">${cust.name}</div><div class="small text-muted">${cust.phone || ''}</div>`;
|
|
a.onclick = (ev) => {
|
|
ev.preventDefault();
|
|
selectCustomer(cust);
|
|
};
|
|
customerResults.appendChild(a);
|
|
});
|
|
customerResults.style.display = 'block';
|
|
} else {
|
|
customerResults.innerHTML = `<div class="list-group-item text-muted">${_t('none')}</div>`;
|
|
customerResults.style.display = 'block';
|
|
}
|
|
});
|
|
}, 300);
|
|
});
|
|
document.addEventListener('click', (e) => {
|
|
if (customerResults && !customerSearchInput.contains(e.target) && !customerResults.contains(e.target)) {
|
|
customerResults.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateLoyaltyUI() {
|
|
if (!loyaltySection || typeof LOYALTY_SETTINGS === 'undefined' || !LOYALTY_SETTINGS.is_enabled) return;
|
|
|
|
if (currentCustomer) {
|
|
loyaltySection.classList.remove('d-none');
|
|
if (loyaltyPointsDisplay) loyaltyPointsDisplay.textContent = currentCustomer.points + ' pts';
|
|
|
|
if (redeemLoyaltyBtn) {
|
|
const totalLoyaltyQuantity = cart.reduce((sum, item) => item.is_loyalty ? sum + item.quantity : sum, 0);
|
|
const hasLoyaltyItem = totalLoyaltyQuantity > 0;
|
|
const hasNonLoyaltyItem = cart.some(item => !item.is_loyalty);
|
|
const eligibleCount = currentCustomer.eligible_count || 1;
|
|
|
|
const isOverLimit = totalLoyaltyQuantity > eligibleCount;
|
|
|
|
redeemLoyaltyBtn.disabled = !currentCustomer.eligible_for_free_meal || !hasLoyaltyItem || hasNonLoyaltyItem || isOverLimit;
|
|
|
|
if (loyaltyMessage) {
|
|
if (currentCustomer.eligible_for_free_meal) {
|
|
loyaltyMessage.innerHTML = `<span class="text-success fw-bold">Eligible for ${eligibleCount} Free Product${eligibleCount > 1 ? 's' : ''}!</span>`;
|
|
|
|
if (hasNonLoyaltyItem) {
|
|
loyaltyMessage.innerHTML += '<div class="text-danger small" style="font-size: 0.7rem;">(Remove non-loyalty products to redeem)</div>';
|
|
} else if (!hasLoyaltyItem) {
|
|
loyaltyMessage.innerHTML += '<div class="text-warning small" style="font-size: 0.7rem;">(Add a loyalty product to redeem)</div>';
|
|
} else if (isOverLimit) {
|
|
loyaltyMessage.innerHTML += `<div class="text-danger small" style="font-size: 0.7rem;">(Maximum ${eligibleCount} free products allowed)</div>`;
|
|
}
|
|
} else {
|
|
loyaltyMessage.textContent = `${currentCustomer.points_needed || 0} pts away from a free product.`;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
loyaltySection.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function selectCustomer(cust) {
|
|
currentCustomer = cust;
|
|
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');
|
|
|
|
updateLoyaltyUI();
|
|
isLoyaltyRedemption = false;
|
|
}
|
|
|
|
if (clearCustomerBtn) {
|
|
clearCustomerBtn.addEventListener('click', () => {
|
|
currentCustomer = null;
|
|
if (selectedCustomerId) selectedCustomerId.value = '';
|
|
if (customerSearchInput) {
|
|
customerSearchInput.value = '';
|
|
customerSearchInput.disabled = false;
|
|
}
|
|
clearCustomerBtn.classList.add('d-none');
|
|
if (customerInfo) customerInfo.classList.add('d-none');
|
|
updateLoyaltyUI();
|
|
isLoyaltyRedemption = false;
|
|
updateCart();
|
|
});
|
|
}
|
|
|
|
if (redeemLoyaltyBtn) {
|
|
redeemLoyaltyBtn.addEventListener('click', () => {
|
|
if (!currentCustomer) return;
|
|
isLoyaltyRedemption = true;
|
|
processOrder(null, 'Loyalty Redeem');
|
|
});
|
|
}
|
|
|
|
if (viewPointsHistoryBtn) {
|
|
viewPointsHistoryBtn.addEventListener('click', () => {
|
|
if (!currentCustomer || !pointsHistoryModal) return;
|
|
pointsHistoryBody.innerHTML = '<div class="text-center py-3"><div class="spinner-border spinner-border-sm text-primary"></div></div>';
|
|
pointsHistoryEmpty.classList.add('d-none');
|
|
pointsHistoryModal.show();
|
|
|
|
fetch(`api/customer_loyalty_history.php?customer_id=${currentCustomer.id}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
pointsHistoryBody.innerHTML = '';
|
|
if (data.success && data.history.length > 0) {
|
|
data.history.forEach(h => {
|
|
const item = document.createElement('div');
|
|
item.className = 'list-group-item px-0 border-0 border-bottom';
|
|
const badgeClass = h.points_change > 0 ? 'bg-success' : 'bg-danger';
|
|
item.innerHTML = `
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="fw-bold text-capitalize">${h.reason}</div>
|
|
<span class="badge ${badgeClass}">${(h.points_change > 0 ? '+' : '') + h.points_change}</span>
|
|
</div>
|
|
<div class="text-muted small">${h.created_at}</div>
|
|
`;
|
|
pointsHistoryBody.appendChild(item);
|
|
});
|
|
} else {
|
|
pointsHistoryEmpty.classList.remove('d-none');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
pointsHistoryBody.innerHTML = `<div class="alert alert-danger p-2 small">${_t('error')}</div>`;
|
|
});
|
|
});
|
|
}
|
|
|
|
if (saveNewCustomerBtn) {
|
|
saveNewCustomerBtn.addEventListener('click', () => {
|
|
const name = document.getElementById('new-customer-name').value.trim();
|
|
const phone = document.getElementById('new-customer-phone').value.trim();
|
|
|
|
if (!name || !phone) {
|
|
showToast('Please fill in both name and phone', 'warning');
|
|
return;
|
|
}
|
|
|
|
saveNewCustomerBtn.disabled = true;
|
|
saveNewCustomerBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
|
|
|
fetch('api/create_customer.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, phone })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
saveNewCustomerBtn.disabled = false;
|
|
saveNewCustomerBtn.innerHTML = 'Add Customer';
|
|
|
|
if (data.success) {
|
|
showToast('Customer added successfully', 'success');
|
|
selectCustomer(data.customer);
|
|
if (addCustomerModal) addCustomerModal.hide();
|
|
|
|
// Clear inputs
|
|
document.getElementById('new-customer-name').value = '';
|
|
document.getElementById('new-customer-phone').value = '';
|
|
} else {
|
|
showToast(data.error || 'Failed to add customer', 'danger');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
saveNewCustomerBtn.disabled = false;
|
|
saveNewCustomerBtn.innerHTML = 'Add Customer';
|
|
showToast('An error occurred', 'danger');
|
|
});
|
|
});
|
|
}
|
|
|
|
function openTableSelectionModal() {
|
|
if (!tableSelectionModal) return;
|
|
fetchTables();
|
|
tableSelectionModal.show();
|
|
}
|
|
window.openTableSelectionModal = openTableSelectionModal;
|
|
|
|
function fetchTables() {
|
|
const grid = document.getElementById("tables-grid");
|
|
if (!grid) return;
|
|
grid.innerHTML = "<div class=\"text-center py-5 w-100\"><div class=\"spinner-border text-primary\"></div></div>";
|
|
|
|
const outletId = CURRENT_OUTLET ? CURRENT_OUTLET.id : 1;
|
|
fetch(`api/tables.php?outlet_id=${outletId}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
grid.innerHTML = "";
|
|
if (data.success && data.tables.length > 0) {
|
|
renderTables(data.tables);
|
|
} else {
|
|
grid.innerHTML = `<div class=\"p-4 text-center text-muted w-100">${_t("none")}</div>`;
|
|
}
|
|
})
|
|
.catch(() => {
|
|
grid.innerHTML = `<div class=\"alert alert-danger w-100">${_t("error")}</div>`;
|
|
});
|
|
}
|
|
|
|
function renderTables(tables) {
|
|
const grid = document.getElementById("tables-grid");
|
|
if (!grid) return;
|
|
grid.innerHTML = "";
|
|
|
|
const areas = {};
|
|
tables.forEach(t => {
|
|
const area = t.area_name || "General";
|
|
if (!areas[area]) areas[area] = [];
|
|
areas[area].push(t);
|
|
});
|
|
|
|
for (const area in areas) {
|
|
const areaHeader = document.createElement("div");
|
|
areaHeader.className = "col-12 mt-3";
|
|
areaHeader.innerHTML = `<h6 class=\"fw-bold text-muted text-uppercase small border-bottom pb-2">${area}</h6>`;
|
|
grid.appendChild(areaHeader);
|
|
|
|
areas[area].forEach(table => {
|
|
const col = document.createElement("div");
|
|
col.className = "col-3 col-sm-2";
|
|
const statusClass = table.is_occupied ? "btn-outline-danger" : "btn-outline-success";
|
|
col.innerHTML = `
|
|
<button class=\"btn ${statusClass} w-100 py-3 rounded-3 position-relative\" onclick=\"selectTable(${table.id}, '${table.name}')\">
|
|
<div class=\"fw-bold">${table.name}</div>
|
|
<div class=\"small\" style=\"font-size: 0.7rem;\">Cap: ${table.capacity}</div>
|
|
${table.is_occupied ? '<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger border border-light rounded-circle" title="Occupied"></span>' : ''}
|
|
</button>
|
|
`;
|
|
grid.appendChild(col);
|
|
});
|
|
}
|
|
}
|
|
|
|
function selectTable(id, name) {
|
|
currentTableId = id;
|
|
currentTableName = name;
|
|
const nameDisplay = document.getElementById("selected-table-name");
|
|
if (nameDisplay) nameDisplay.textContent = name;
|
|
if (tableSelectionModal) tableSelectionModal.hide();
|
|
|
|
const dineInInput = document.getElementById("ot-dine-in");
|
|
if (dineInInput) {
|
|
dineInInput.checked = true;
|
|
checkOrderType();
|
|
}
|
|
}
|
|
window.selectTable = selectTable;
|
|
|
|
function checkOrderType() {
|
|
const checked = document.querySelector('input[name="order_type"]:checked');
|
|
if (!checked) return;
|
|
const selected = checked.value;
|
|
if (selected === 'dine-in') {
|
|
if (!currentTableId && tableSelectionModal) openTableSelectionModal();
|
|
if (tableDisplay) tableDisplay.style.display = 'inline-block';
|
|
} else {
|
|
if (tableDisplay) tableDisplay.style.display = 'none';
|
|
}
|
|
}
|
|
window.checkOrderType = checkOrderType;
|
|
|
|
document.querySelectorAll('input[name="order_type"]').forEach(input => {
|
|
input.addEventListener('change', checkOrderType);
|
|
});
|
|
|
|
function handleProductClick(product, variants) {
|
|
console.log('Product Clicked:', product.name);
|
|
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 || 0), base_price: parseFloat(product.price || 0),
|
|
hasVariants: false, quantity: 1, variant_id: null, variant_name: null,
|
|
is_loyalty: parseInt(product.is_loyalty) === 1,
|
|
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
|
|
});
|
|
}
|
|
}
|
|
window.handleProductClick = handleProductClick;
|
|
|
|
function openVariantModal(product, variants) {
|
|
if (!variantSelectionModal) return;
|
|
const list = document.getElementById('variant-list');
|
|
const title = document.getElementById('variantModalTitle');
|
|
if (title) title.textContent = `${_t('variant')}: ${LANG === 'ar' && product.name_ar ? product.name_ar : 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 finalPrice = parseFloat(product.price || 0) + parseFloat(v.price_adjustment || 0);
|
|
const vName = (LANG === 'ar' && v.name_ar) ? v.name_ar : v.name;
|
|
btn.innerHTML = `<span>${vName}</span><span class="fw-bold">${formatCurrency(finalPrice)}</span>`;
|
|
btn.onclick = () => {
|
|
addToCart({
|
|
id: product.id, name: product.name, name_ar: product.name_ar || "",
|
|
price: finalPrice, base_price: parseFloat(product.price || 0),
|
|
hasVariants: true, quantity: 1, variant_id: v.id, variant_name: v.name,
|
|
is_loyalty: parseInt(product.is_loyalty) === 1,
|
|
vat_percent: parseFloat(product.vat_percent || settings.vat_rate || 0)
|
|
});
|
|
variantSelectionModal.hide();
|
|
};
|
|
list.appendChild(btn);
|
|
});
|
|
variantSelectionModal.show();
|
|
}
|
|
|
|
function addToCart(product) {
|
|
console.log('Adding to cart:', product);
|
|
const existing = cart.find(item => item.id === product.id && item.variant_id === product.variant_id);
|
|
if (existing) {
|
|
existing.quantity++;
|
|
console.log('Incremented quantity for:', product.name);
|
|
} else {
|
|
cart.push({...product});
|
|
console.log('Added new item to cart:', product.name);
|
|
}
|
|
updateCart();
|
|
}
|
|
window.addToCart = addToCart;
|
|
|
|
function changeQuantity(index, delta) {
|
|
if (cart[index]) {
|
|
cart[index].quantity += delta;
|
|
if (cart[index].quantity <= 0) cart.splice(index, 1);
|
|
updateCart();
|
|
}
|
|
}
|
|
window.changeQuantity = changeQuantity;
|
|
|
|
function removeFromCart(index) {
|
|
cart.splice(index, 1);
|
|
updateCart();
|
|
}
|
|
window.removeFromCart = removeFromCart;
|
|
|
|
function clearCart() {
|
|
if (cart.length === 0) return;
|
|
cart = [];
|
|
currentOrderId = null;
|
|
isLoyaltyRedemption = false;
|
|
updateCart();
|
|
showToast("Cart cleared", "success");
|
|
}
|
|
window.clearCart = clearCart;
|
|
|
|
function updateCart() {
|
|
console.log('Updating cart UI, item count:', cart.length);
|
|
if (!cartItemsContainer) {
|
|
console.error('Cart items container not found!');
|
|
return;
|
|
}
|
|
|
|
updateLoyaltyUI();
|
|
|
|
if (cart.length === 0) {
|
|
cartItemsContainer.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-basket3 fs-1 text-light"></i><p class="mt-2">${_t('cart_empty')}</p></div>`;
|
|
if (cartSubtotal) cartSubtotal.innerText = formatCurrency(0);
|
|
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(0);
|
|
// Always show VAT row even if 0
|
|
if (cartVatRow) cartVatRow.classList.remove('d-none');
|
|
if (cartVatInput) cartVatInput.value = '0';
|
|
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0);
|
|
if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); }
|
|
if (placeOrderBtn) { placeOrderBtn.disabled = true; placeOrderBtn.innerText = _t('save_bill'); }
|
|
return;
|
|
}
|
|
|
|
cartItemsContainer.innerHTML = '';
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
cart.forEach((item, index) => {
|
|
const itemTotal = (item.price || 0) * (item.quantity || 0);
|
|
const itemVat = itemTotal * ((item.vat_percent || 0) / 100);
|
|
subtotal += itemTotal;
|
|
totalVat += itemVat;
|
|
|
|
const row = document.createElement('div');
|
|
row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2';
|
|
const itemName = (LANG === 'ar' && item.name_ar) ? item.name_ar : item.name;
|
|
const otherName = (LANG === 'ar') ? item.name : item.name_ar;
|
|
|
|
row.innerHTML = `
|
|
<div class="flex-grow-1 me-2">
|
|
<div class="fw-bold text-truncate" style="max-width: 140px;">${itemName || 'Product'}</div>
|
|
${otherName ? `<div class="text-muted small" style="font-size: 0.75rem;">${otherName}</div>` : ''}
|
|
<div class="small text-muted">${formatCurrency(item.price)} ${((item.vat_percent || 0) > 0 ? `<span class="badge bg-light text-dark border ms-1" style="font-size: 0.6rem;">${item.vat_percent}% VAT</span>` : '')}</div>
|
|
</div>
|
|
<div class="d-flex align-items-center bg-light rounded px-1">
|
|
<button class="btn btn-sm text-secondary p-0" style="width: 24px;" onclick="changeQuantity(${index}, -1)"><i class="bi bi-dash"></i></button>
|
|
<span class="mx-1 fw-bold small" style="min-width: 20px; text-align: center;">${item.quantity}</span>
|
|
<button class="btn btn-sm text-secondary p-0" style="width: 24px;" onclick="changeQuantity(${index}, 1)"><i class="bi bi-plus"></i></button>
|
|
</div>
|
|
<div class="text-end ms-3" style="min-width: 60px;">
|
|
<div class="fw-bold">${formatCurrency(itemTotal)}</div>
|
|
<button class="btn btn-sm text-danger p-0 mt-1" style="font-size: 0.8rem;" onclick="removeFromCart(${index})">${_t('remove')}</button>
|
|
</div>
|
|
`;
|
|
cartItemsContainer.appendChild(row);
|
|
});
|
|
|
|
if (cartSubtotal) cartSubtotal.innerText = formatCurrency(subtotal);
|
|
if (cartVatAmount) cartVatAmount.innerText = formatCurrency(totalVat);
|
|
// Always show VAT row
|
|
if (cartVatRow) cartVatRow.classList.remove('d-none');
|
|
if (cartVatInput) cartVatInput.value = totalVat.toFixed(3);
|
|
|
|
const total = subtotal + totalVat;
|
|
if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(total);
|
|
|
|
if (quickOrderBtn) { quickOrderBtn.disabled = false; quickOrderBtn.innerText = _t('quick_pay'); }
|
|
if (placeOrderBtn) { placeOrderBtn.disabled = false; placeOrderBtn.innerText = _t('save_bill'); }
|
|
}
|
|
|
|
if (quickOrderBtn) {
|
|
quickOrderBtn.addEventListener('click', () => {
|
|
if (cart.length > 0 && paymentSelectionModal) {
|
|
renderPaymentMethods();
|
|
paymentSelectionModal.show();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (placeOrderBtn) {
|
|
placeOrderBtn.addEventListener('click', () => {
|
|
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-12';
|
|
col.innerHTML = `<button class="btn btn-outline-primary w-100 py-3 payment-btn" onclick="processOrder(${pt.id}, '${pt.name}')">${pt.name}</button>`;
|
|
paymentMethodsContainer.appendChild(col);
|
|
});
|
|
}
|
|
}
|
|
|
|
function processOrder(paymentTypeId, paymentTypeName) {
|
|
const orderTypeInput = document.querySelector('input[name="order_type"]:checked');
|
|
const orderType = orderTypeInput ? orderTypeInput.value : 'takeaway';
|
|
|
|
let subtotal = 0;
|
|
let totalVat = 0;
|
|
const itemsData = cart.map(item => {
|
|
const itemTotal = item.price * item.quantity;
|
|
const itemVat = itemTotal * (item.vat_percent / 100);
|
|
subtotal += itemTotal;
|
|
totalVat += itemVat;
|
|
return {
|
|
product_id: item.id,
|
|
quantity: item.quantity,
|
|
unit_price: item.price,
|
|
variant_id: item.variant_id,
|
|
vat_percent: item.vat_percent,
|
|
vat_amount: itemVat
|
|
};
|
|
});
|
|
|
|
const orderData = {
|
|
order_id: currentOrderId,
|
|
table_number: (orderType === 'dine-in') ? currentTableId : null,
|
|
order_type: orderType,
|
|
customer_id: selectedCustomerId ? selectedCustomerId.value : null,
|
|
outlet_id: CURRENT_OUTLET ? CURRENT_OUTLET.id : 1,
|
|
payment_type_id: paymentTypeId,
|
|
total_amount: isLoyaltyRedemption ? 0 : (subtotal + totalVat),
|
|
vat: isLoyaltyRedemption ? 0 : totalVat,
|
|
items: itemsData,
|
|
redeem_loyalty: isLoyaltyRedemption
|
|
};
|
|
|
|
const receiptData = {
|
|
orderId: null,
|
|
customer: currentCustomer ? { name: currentCustomer.name, phone: currentCustomer.phone, address: currentCustomer.address || '' } : null,
|
|
items: cart.map(item => ({
|
|
name: item.name,
|
|
variant_name: item.variant_name,
|
|
quantity: item.quantity,
|
|
price: isLoyaltyRedemption ? 0 : item.price,
|
|
vat_percent: isLoyaltyRedemption ? 0 : item.vat_percent,
|
|
vat_amount: isLoyaltyRedemption ? 0 : ((item.price * item.quantity) * (item.vat_percent / 100))
|
|
})),
|
|
subtotal: isLoyaltyRedemption ? 0 : subtotal,
|
|
total: isLoyaltyRedemption ? 0 : (subtotal + totalVat),
|
|
vat: isLoyaltyRedemption ? 0 : totalVat,
|
|
orderType: orderType,
|
|
tableNumber: (orderType === 'dine-in') ? currentTableName : null,
|
|
date: new Date().toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }),
|
|
paymentMethod: paymentTypeName || (isLoyaltyRedemption ? 'Loyalty Redeem' : 'Unpaid'),
|
|
loyaltyRedeemed: isLoyaltyRedemption
|
|
};
|
|
|
|
// Clear UI immediately for responsiveness
|
|
const tempOrderId = currentOrderId;
|
|
clearCart();
|
|
if (paymentSelectionModal) paymentSelectionModal.hide();
|
|
if (clearCustomerBtn) clearCustomerBtn.click();
|
|
|
|
fetch('api/order.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) })
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
receiptData.orderId = data.order_id;
|
|
|
|
// --- PRINTING LOGIC ---
|
|
const cashierPrinterIp = CURRENT_OUTLET.cashier_printer_ip;
|
|
const kitchenPrinterIp = CURRENT_OUTLET.kitchen_printer_ip;
|
|
|
|
const isLocalCashierIp = cashierPrinterIp && /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(cashierPrinterIp);
|
|
const isLocalKitchenIp = kitchenPrinterIp && /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(kitchenPrinterIp);
|
|
|
|
// 1. Browser Cashier Print (if needed)
|
|
if (!cashierPrinterIp || isLocalCashierIp) {
|
|
printThermalReceipt(receiptData);
|
|
} else {
|
|
triggerNetworkPrint(data.order_id, 'cashier');
|
|
}
|
|
|
|
// 2. Kitchen Network Print (ONLY if NOT local IP)
|
|
// If it's local, server can't reach it anyway, so skip the fetch to api/print.php
|
|
if (kitchenPrinterIp && !isLocalKitchenIp) {
|
|
triggerNetworkPrint(data.order_id, 'kitchen');
|
|
}
|
|
|
|
showToast(`${_t('order_placed')} #${data.order_id}`, 'success');
|
|
} else {
|
|
showToast(data.error, 'danger');
|
|
// Optional: should we restore the cart if it failed?
|
|
// For now, let's keep it cleared but show the error.
|
|
}
|
|
});
|
|
}
|
|
window.processOrder = processOrder;
|
|
|
|
function triggerNetworkPrint(orderId, type) {
|
|
if (!orderId) return;
|
|
|
|
const printerIp = (type === 'kitchen') ? CURRENT_OUTLET.kitchen_printer_ip : CURRENT_OUTLET.cashier_printer_ip;
|
|
if (!printerIp) return;
|
|
|
|
fetch('api/print.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ order_id: orderId, type: type })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (!data.success) {
|
|
const isLocalIp = /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(printerIp);
|
|
if (!isLocalIp) {
|
|
showToast(`Printer Error (${type}): ${data.error}`, 'warning');
|
|
}
|
|
}
|
|
})
|
|
.catch(err => console.error(`Network Print Fetch Error (${type}):`, err));
|
|
}
|
|
window.triggerNetworkPrint = triggerNetworkPrint;
|
|
|
|
function printThermalReceipt(data) {
|
|
let iframe = document.getElementById('print-iframe');
|
|
if (!iframe) {
|
|
iframe = document.createElement('iframe');
|
|
iframe.id = 'print-iframe';
|
|
iframe.style.display = 'none';
|
|
document.body.appendChild(iframe);
|
|
}
|
|
|
|
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 => `
|
|
<tr>
|
|
<td style="padding: 5px 0; border-bottom: 1px solid #eee;">
|
|
<div style="font-weight: bold;">${item.name}</div>
|
|
${item.variant_name ? `<div style="font-size: 10px; color: #555;">(${item.variant_name})</div>` : ''}
|
|
<div style="font-size: 11px;">${item.quantity} x ${formatCurrency(item.price)}</div>
|
|
</td>
|
|
<td style="text-align: center; vertical-align: middle; font-size: 11px;">${item.vat_percent > 0 ? item.vat_percent + '%' : '-'}</td>
|
|
<td style="text-align: right; vertical-align: middle; font-weight: bold;">${formatCurrency(item.quantity * item.price)}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
const customerHtml = data.customer ? `
|
|
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 8px; border-radius: 4px;">
|
|
<div style="font-weight: bold; text-transform: uppercase; font-size: 10px; color: #666; margin-bottom: 3px;">
|
|
<span style="float: left;">Customer Details</span>
|
|
<span style="float: right;">${tr['Customer Details']}</span>
|
|
<div style="clear: both;"></div>
|
|
</div>
|
|
<div style="font-weight: bold;">${data.customer.name}</div>
|
|
${data.customer.phone ? `<div>Tel: ${data.customer.phone}</div>` : ''}
|
|
${data.customer.address ? `<div style="font-size: 11px;">${data.customer.address}</div>` : ''}
|
|
</div>
|
|
` : '';
|
|
|
|
const tableHtml = data.tableNumber && data.orderType === 'dine-in' ? `
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<span><strong>Table:</strong> ${data.tableNumber}</span>
|
|
<span><strong>${tr['Table']}:</strong> ${data.tableNumber}</span>
|
|
</div>` : '';
|
|
|
|
const paymentHtml = data.paymentMethod ? `
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<span><strong>Payment:</strong> ${data.paymentMethod}</span>
|
|
<span><strong>${tr['Payment']}:</strong> ${data.paymentMethod}</span>
|
|
</div>` : '';
|
|
|
|
const loyaltyHtml = data.loyaltyRedeemed ? `<div style="color: #d63384; font-weight: bold; margin: 5px 0; text-align: center;">* Loyalty Reward Applied *</div>` : '';
|
|
|
|
// We skip logo in receipt for absolute speed unless it's already cached.
|
|
let logoHtml = '';
|
|
if (settings.logo_url) {
|
|
logoHtml = `<div style="text-align: center; margin-bottom: 10px;"><img src="${BASE_URL}${settings.logo_url}" style="max-height: 80px; max-width: 150px; filter: grayscale(100%);" alt="Logo"></div>`;
|
|
}
|
|
|
|
const vatRate = settings.vat_rate || 0;
|
|
|
|
const html = `
|
|
<html dir="ltr">
|
|
<head>
|
|
<title>Receipt #${data.orderId}</title>
|
|
<style>
|
|
body { font-family: 'Courier New', Courier, monospace; font-size: 13px; margin: 0; padding: 15px; color: #000; line-height: 1.4; }
|
|
.header { text-align: center; margin-bottom: 15px; }
|
|
.header h2 { margin: 0 0 5px 0; font-size: 20px; font-weight: bold; }
|
|
.header div { font-size: 12px; }
|
|
table { width: 100%; border-collapse: collapse; }
|
|
.divider { border-bottom: 2px dashed #000; margin: 10px 0; }
|
|
.thick-divider { border-bottom: 2px solid #000; margin: 10px 0; }
|
|
.totals td { padding: 3px 0; }
|
|
.footer { text-align: center; margin-top: 25px; font-size: 12px; }
|
|
.order-info { font-size: 11px; margin-bottom: 10px; }
|
|
.order-info-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
|
|
.rtl { direction: rtl; unicode-bidi: embed; }
|
|
@media print { body { width: 80mm; padding: 2mm; } @page { size: 80mm auto; margin: 0; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
${logoHtml}
|
|
<h2>${settings.company_name}</h2>
|
|
<div style="font-weight: bold;">${CURRENT_OUTLET.name}</div>
|
|
<div>${settings.address || ''}</div>
|
|
<div>Tel: ${settings.phone || ''}</div>
|
|
${settings.vat_number ? `<div style="margin-top: 4px;">VAT No / الرقم الضريبي: ${settings.vat_number}</div>` : ''}
|
|
${settings.ctr_number ? `<div>CTR No / رقم السجل: ${settings.ctr_number}</div>` : ''}
|
|
</div>
|
|
<div class="divider"></div>
|
|
<div class="order-info">
|
|
<div class="order-info-row"><span><strong>Order:</strong> #${data.orderId}</span><span><strong>${tr['Order']}:</strong> #${data.orderId}</span></div>
|
|
<div class="order-info-row"><span><strong>Type:</strong> ${data.orderType.toUpperCase()}</span><span><strong>${tr['Type']}:</strong> ${tr[data.orderType] || data.orderType}</span></div>
|
|
<div class="order-info-row"><span><strong>Date:</strong> ${data.date}</span><span><strong>${tr['Date']}:</strong> ${data.date}</span></div>
|
|
<div class="order-info-row"><span><strong>Staff:</strong> ${CURRENT_USER.name}</span><span><strong>${tr['Staff']}:</strong> ${CURRENT_USER.name}</span></div>
|
|
</div>
|
|
${tableHtml}${paymentHtml}${loyaltyHtml}
|
|
<div class="thick-divider"></div>
|
|
${customerHtml}
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align: left; padding-bottom: 5px;">ITEM / الصنف</th>
|
|
<th style="text-align: center; padding-bottom: 5px; font-size: 10px;">VAT / الضريبة</th>
|
|
<th style="text-align: right; padding-bottom: 5px;">TOTAL / المجموع</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>${itemsHtml}</tbody>
|
|
</table>
|
|
<div class="divider"></div>
|
|
<div class="totals">
|
|
<table style="width: 100%">
|
|
<tr><td>Subtotal / ${tr['Subtotal']}</td><td style="text-align: right">${formatCurrency(data.subtotal)}</td></tr>
|
|
<tr><td>VAT (${vatRate}%) / ${tr['VAT']}</td><td style="text-align: right">${formatCurrency(Math.abs(data.vat))}</td></tr>
|
|
<tr style="font-weight: bold; font-size: 18px;"><td style="padding-top: 10px;">TOTAL / ${tr['TOTAL']}</td><td style="text-align: right; padding-top: 10px;">${formatCurrency(data.total)}</td></tr>
|
|
</table>
|
|
</div>
|
|
<div class="thick-divider"></div>
|
|
<div class="footer">
|
|
<div style="font-weight: bold; font-size: 14px; margin-bottom: 2px;">THANK YOU FOR YOUR VISIT!</div>
|
|
<div style="font-weight: bold; font-size: 14px; margin-bottom: 5px;" class="rtl">${tr['THANK YOU FOR YOUR VISIT!']}</div>
|
|
<div>Please come again.</div><div class="rtl">${tr['Please come again.']}</div>
|
|
${settings.email ? `<div style="margin-top: 5px; font-size: 10px;">${settings.email}</div>` : ''}
|
|
<div style="margin-top: 15px; font-size: 10px; color: #888;">Powered by Abidarcafe</div>
|
|
</div>
|
|
</body></html>
|
|
`;
|
|
|
|
const doc = iframe.contentWindow.document;
|
|
doc.open();
|
|
doc.write(html);
|
|
doc.close();
|
|
|
|
let printed = false;
|
|
function triggerPrint() {
|
|
if (printed) return;
|
|
printed = true;
|
|
iframe.contentWindow.focus();
|
|
iframe.contentWindow.print();
|
|
}
|
|
|
|
const img = doc.querySelector('img');
|
|
if (img && !img.complete) {
|
|
img.onload = triggerPrint;
|
|
img.onerror = triggerPrint;
|
|
setTimeout(triggerPrint, 1500); // Fallback
|
|
} else {
|
|
triggerPrint();
|
|
}
|
|
}
|
|
window.printThermalReceipt = printThermalReceipt;
|
|
|
|
function openRatingQRModal() {
|
|
const qrContainer = document.getElementById('rating-qr-container');
|
|
const ratingUrl = BASE_URL + '/rate.php';
|
|
const qrCodeUrl = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" + encodeURIComponent(ratingUrl);
|
|
|
|
qrContainer.innerHTML = '<img src="' + qrCodeUrl + '" alt="Rating QR Code" class="img-fluid rounded-3 shadow-sm">';
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('qrRatingModal'));
|
|
modal.show();
|
|
}
|
|
window.openRatingQRModal = openRatingQRModal;
|
|
});
|