39728-vm/shop.php
2026-04-26 02:11:55 +00:00

624 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once __DIR__ . '/includes/app.php';
$forcePublic = true;
$pageTitle = tr('الطلب عبر الإنترنت', 'Online Ordering');
$shopPaymentStatus = trim((string) ($_GET['payment_status'] ?? ''));
$shopPaymentMessage = trim((string) ($_GET['message'] ?? ''));
$shopCanPayOnline = thawani_is_configured();
$shopShowPayOnline = thawani_is_enabled() || $shopCanPayOnline;
require __DIR__ . '/includes/header.php';
$db = db();
$stmt = $db->query("
SELECT i.*, c.name_ar as cat_ar, c.name_en as cat_en
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
WHERE i.in_catalog = 1
ORDER BY c.id, i.name
");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
$catalog = [];
foreach ($items as $item) {
$catName = current_lang() === 'ar' ? ($item['cat_ar'] ?? 'عام') : ($item['cat_en'] ?? 'General');
$catalog[$catName][] = $item;
}
?>
<style>
.shop-item-card {
transition: transform 0.2s, box-shadow 0.2s;
border-radius: 12px;
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
height: 100%;
}
.shop-item-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0,0,0,0.1);
}
.shop-item-img {
height: 200px;
object-fit: cover;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
body { background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%); background-attachment: fixed; min-height: 100vh; }
/* Hide scrollbar for category filter */
.category-filter-container::-webkit-scrollbar {
display: none;
}
.category-filter-container {
-ms-overflow-style: none;
scrollbar-width: none;
}
.shop-pagination-wrap {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.shop-pagination-status {
color: #6c757d;
font-weight: 600;
}
.shop-pagination .page-link {
border-radius: 999px !important;
margin: 0 4px;
border: 0;
box-shadow: 0 3px 10px rgba(0,0,0,0.06);
color: #0d6efd;
}
.shop-pagination .page-item.active .page-link {
background: #0d6efd;
color: #fff;
}
.shop-pagination .page-item.disabled .page-link {
color: #adb5bd;
box-shadow: none;
}
</style>
<div class="container py-5">
<!-- Sticky Header -->
<div class="sticky-top bg-white p-3 rounded-4 shadow-sm mb-4" style="top: 15px; z-index: 1020;">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3">
<div class="d-flex align-items-center gap-3">
<?php if (get_setting('company_logo')): ?>
<img src="<?= h(get_setting('company_logo')) ?>" alt="Logo" class="rounded shadow-sm bg-white" style="max-height: 55px; max-width: 120px; object-fit: contain; padding: 4px;">
<?php else: ?>
<div class="bg-primary bg-gradient text-white rounded shadow-sm d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="bi bi-shop fs-4"></i>
</div>
<?php endif; ?>
<div>
<h3 class="fw-bold text-primary mb-1" style="font-size: 1.5rem;"><?= h(get_setting('company_name_' . current_lang(), app_name())) ?></h3>
<p class="text-muted mb-0 d-none d-sm-block" style="font-size: 0.85rem;"><?= h(tr('اطلب الآن وسنقوم بتجهيز طلبك', 'Order now and we will prepare your request')) ?></p>
</div>
</div>
<div class="d-flex align-items-center gap-2 gap-md-3">
<button class="btn btn-primary rounded-pill px-3 px-md-4 py-2 position-relative shadow-sm fw-bold d-flex align-items-center gap-2" onclick="openCart()">
<i class="bi bi-cart3 fs-5"></i>
<span class="d-none d-md-inline"><?= h(tr('سلة المشتريات', 'Cart')) ?></span>
<span class="badge bg-danger rounded-pill fs-6" id="cartCount">0</span>
</button>
<div class="language-switcher bg-light border rounded-pill p-1 d-flex">
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-primary' : 'text-dark' ?> rounded-pill px-2 px-md-3 fw-bold" href="shop.php?lang=ar">AR</a>
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-primary' : 'text-dark' ?> rounded-pill px-2 px-md-3 fw-bold" href="shop.php?lang=en">EN</a>
</div>
</div>
</div>
</div>
<?php if ($shopPaymentStatus !== ''): ?>
<?php
$alertClass = 'alert-info';
if ($shopPaymentStatus === 'paid') {
$alertClass = 'alert-success';
} elseif (in_array($shopPaymentStatus, ['cancelled', 'failed'], true)) {
$alertClass = 'alert-warning';
}
$defaultMessage = match ($shopPaymentStatus) {
'paid' => tr('تم الدفع بنجاح وتم استلام طلبك.', 'Payment completed successfully and your order was received.'),
'cancelled' => tr('تم إلغاء عملية الدفع. يمكنك المحاولة مرة أخرى أو اختيار الدفع لاحقاً.', 'Payment was cancelled. You can try again or choose Pay Later.'),
'failed' => tr('تعذر تأكيد الدفع. إذا تم الخصم يرجى مراجعة الإدارة.', 'We could not confirm the payment. If you were charged, please contact the store.'),
default => tr('تم تحديث حالة الطلب.', 'The order status has been updated.'),
};
?>
<div class="alert <?= h($alertClass) ?> rounded-4 shadow-sm mb-4">
<?= h($shopPaymentMessage !== '' ? $shopPaymentMessage : $defaultMessage) ?>
</div>
<?php endif; ?>
<?php if (!empty($catalog)): ?>
<!-- Search and Filter -->
<div class="row mb-4">
<div class="col-md-6 mb-3 mb-md-0">
<div class="input-group input-group-lg shadow-sm rounded-pill overflow-hidden">
<span class="input-group-text bg-white border-0 ps-4"><i class="bi bi-search text-muted"></i></span>
<input type="text" id="searchInput" class="form-control border-0 px-3" placeholder="<?= h(tr('ابحث عن منتج...', 'Search for a product...')) ?>" onkeyup="filterProducts()">
</div>
</div>
<div class="col-md-6">
<div class="d-flex gap-2 overflow-auto py-2 px-1 category-filter-container" style="white-space: nowrap;" id="categoryFilters">
<button class="btn btn-primary rounded-pill px-4 active" data-filter="all" onclick="setCategoryFilter('all', this)"><?= h(tr('الكل', 'All')) ?></button>
<?php foreach (array_keys($catalog) as $catName): ?>
<button class="btn btn-light rounded-pill px-4 text-dark shadow-sm border" data-filter="<?= htmlspecialchars($catName) ?>" onclick="setCategoryFilter(this.getAttribute('data-filter'), this)"><?= h($catName) ?></button>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="shop-pagination-wrap bg-white p-3 rounded-4 shadow-sm mb-4">
<div class="shop-pagination-status" id="paginationStatus">
<?= h(tr('جارٍ تحميل المنتجات...', 'Loading products...')) ?>
</div>
<nav aria-label="<?= h(tr('تنقل الصفحات', 'Product pagination')) ?>">
<ul class="pagination pagination-sm mb-0 shop-pagination" id="paginationTop"></ul>
</nav>
</div>
<?php endif; ?>
<?php if (empty($catalog)): ?>
<div class="text-center py-5">
<i class="bi bi-box-seam display-1 text-muted opacity-50 mb-3 d-block"></i>
<h3 class="text-muted"><?= h(tr('لا توجد منتجات متاحة حالياً', 'No products available currently')) ?></h3>
</div>
<?php else: ?>
<?php foreach ($catalog as $category => $catItems): ?>
<h3 class="fw-bold mb-4 mt-5 border-bottom pb-2 category-title" data-category="<?= htmlspecialchars($category) ?>"><?= h($category) ?></h3>
<div class="row g-4 category-row" data-category="<?= htmlspecialchars($category) ?>">
<?php foreach ($catItems as $item): ?>
<div class="col-sm-6 col-md-4 col-lg-3 product-item" data-name="<?= htmlspecialchars(strtolower($item['name'])) ?>">
<div class="card shop-item-card">
<?php if (!empty($item['image_url'])): ?>
<img src="<?= h($item['image_url']) ?>" class="card-img-top shop-item-img" alt="<?= h($item['name']) ?>">
<?php else: ?>
<div class="bg-light d-flex align-items-center justify-content-center shop-item-img text-muted">
<i class="bi bi-image" style="font-size: 3rem;"></i>
</div>
<?php endif; ?>
<div class="card-body d-flex flex-column">
<h5 class="card-title fw-bold mb-1"><?= h($item['name']) ?></h5>
<p class="text-primary fw-bold fs-5 mb-3"><?= h(currency($item['price'])) ?></p>
<button class="btn btn-outline-primary mt-auto rounded-pill fw-bold" onclick="addToCart(<?= htmlspecialchars(json_encode([
'id' => $item['id'],
'sku' => $item['sku'],
'name' => $item['name'],
'price' => $item['price'], 'vat' => $item['vat'] ?? 0
]), ENT_QUOTES, 'UTF-8') ?>)">
<i class="bi bi-cart-plus me-1"></i> <?= h(tr('إضافة للسلة', 'Add to Cart')) ?>
</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<!-- No results message -->
<div id="noResultsMsg" class="text-center py-5" style="display: none;">
<i class="bi bi-search display-1 text-muted opacity-50 mb-3 d-block"></i>
<h3 class="text-muted"><?= h(tr('لم يتم العثور على نتائج', 'No results found')) ?></h3>
</div>
<div class="shop-pagination-wrap bg-white p-3 rounded-4 shadow-sm mt-4" id="paginationFooter">
<div class="shop-pagination-status" id="paginationStatusBottom"></div>
<nav aria-label="<?= h(tr('تنقل الصفحات', 'Product pagination')) ?>">
<ul class="pagination pagination-sm mb-0 shop-pagination" id="paginationBottom"></ul>
</nav>
</div>
<?php endif; ?>
</div>
<!-- Cart Modal -->
<div class="modal fade" id="cartModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header border-bottom-0 pb-0 pt-4 px-4">
<h5 class="modal-title fw-bold"><i class="bi bi-cart-check me-2 text-primary"></i><?= h(tr('سلة المشتريات', 'Shopping Cart')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body px-4 py-4">
<div id="cartItemsList" class="mb-4">
<!-- Items will be rendered here -->
</div>
<!-- cart summary -->
<div class="bg-light p-3 rounded-3 mb-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted"><?= h(tr("المجموع الفرعي", "Subtotal")) ?></span>
<span class="fw-bold" id="cartSubtotal">0.00</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted"><?= h(tr("الضريبة", "VAT")) ?></span>
<span class="fw-bold" id="cartVat">0.00</span>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold"><?= h(tr("المجموع الإجمالي", "Total Amount")) ?></h5>
<h4 class="mb-0 fw-bold text-primary" id="cartTotal">0.00</h4>
</div>
</div>
<h5 class="fw-bold mb-3 border-bottom pb-2"><?= h(tr('بيانات العميل', 'Customer Details')) ?></h5>
<form id="checkoutForm">
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('الاسم', 'Name')) ?> *</label>
<input type="text" class="form-control form-control-lg rounded-3" id="customerName" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('رقم الهاتف', 'Telephone')) ?> *</label>
<div class="input-group input-group-lg" dir="ltr">
<span class="input-group-text">968</span>
<input type="tel" class="form-control form-control-lg rounded-end-3" id="customerPhone" inputmode="numeric" maxlength="8" pattern="\d{8}" placeholder="91234567" required>
</div>
<div class="form-text"><?= h(tr('أدخل 8 أرقام فقط وسيتم إرسالها عبر واتساب مع المقدمة 968.', 'Enter 8 digits only; WhatsApp will send it with the 968 prefix.')) ?></div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('العنوان', 'Address')) ?> *</label>
<textarea class="form-control form-control-lg rounded-3" id="customerAddress" rows="2" required></textarea>
</div>
<div class="mb-3">
<label class="form-label fw-semibold d-block mb-3"><?= h(tr('طريقة الدفع', 'Payment Method')) ?> *</label>
<div class="row g-3">
<?php if ($shopShowPayOnline): ?>
<div class="col-md-6">
<label class="border rounded-4 p-3 w-100 h-100 bg-light-subtle <?= $shopCanPayOnline ? '' : 'opacity-75' ?>">
<input class="form-check-input me-2" type="radio" name="payment_method" value="pay_online" <?= $shopCanPayOnline ? '' : 'disabled' ?>>
<span class="fw-bold d-block"><?= h(tr('ادفع أونلاين', 'Pay Online')) ?></span>
<span class="small text-muted"><?= h(tr('سيتم تحويلك إلى بوابة ثواني لإتمام الدفع مباشرة.', 'You will be redirected to Thawani checkout to complete payment.')) ?></span>
</label>
</div>
<?php endif; ?>
<div class="col-md-6">
<label class="border rounded-4 p-3 w-100 h-100 bg-light-subtle">
<input class="form-check-input me-2" type="radio" name="payment_method" value="pay_later" checked>
<span class="fw-bold d-block"><?= h(tr('ادفع لاحقاً', 'Pay Later')) ?></span>
<span class="small text-muted"><?= h(tr('سيتم إنشاء الطلب الآن وسيتم تحصيل المبلغ لاحقاً.', 'Your order will be created now and payment will be collected later.')) ?></span>
</label>
</div>
</div>
<?php if (!$shopCanPayOnline && $shopShowPayOnline): ?>
<div class="form-text mt-2 text-warning"><?= h(tr('خيار الدفع أونلاين ظاهر لكن مفاتيح ثواني غير مكتملة في الإعدادات بعد.', 'Pay Online is visible, but the Thawani keys are not fully configured in Settings yet.')) ?></div>
<?php endif; ?>
</div>
<div class="mb-2">
<div class="form-check bg-light rounded-4 p-3 border">
<input class="form-check-input" type="checkbox" id="acceptPolicies" required>
<label class="form-check-label small" for="acceptPolicies">
<?= tr('أوافق على <a href="privacy-policy.php" target="_blank" rel="noopener">سياسة الخصوصية</a> و<a href="terms-conditions.php" target="_blank" rel="noopener">الشروط والأحكام</a>.', 'I agree to the <a href="privacy-policy.php" target="_blank" rel="noopener">Privacy Policy</a> and <a href="terms-conditions.php" target="_blank" rel="noopener">Terms &amp; Conditions</a>.') ?>
</label>
</div>
<div class="form-text"><?= h(tr('يجب قبول السياسة والشروط قبل إرسال الطلب.', 'You must accept the policy and terms before placing the order.')) ?></div>
</div>
</form>
</div>
<div class="modal-footer border-top-0 pt-0 pb-4 px-4 d-flex justify-content-between">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal"><?= h(tr('إكمال التسوق', 'Continue Shopping')) ?></button>
<button type="button" class="btn btn-success rounded-pill px-5 fw-bold shadow-sm" id="submitOrderBtn" onclick="submitOrder()">
<i class="bi bi-check2-circle me-1"></i> <?= h(tr('تأكيد الطلب', 'Confirm Order')) ?>
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
let cart = JSON.parse(localStorage.getItem('shop_cart')) || {};
let cartModalInstance = null;
function saveCart() {
localStorage.setItem('shop_cart', JSON.stringify(cart));
updateCartBadge();
}
function updateCartBadge() {
let count = 0;
for (let id in cart) {
count += cart[id].qty;
}
document.getElementById('cartCount').innerText = count;
}
function addToCart(item) {
if (cart[item.id]) {
cart[item.id].qty += 1;
} else {
cart[item.id] = { ...item, qty: 1 };
}
saveCart();
Swal.fire({
title: '<?= h(tr('تمت الإضافة', 'Added')) ?>',
text: item.name,
icon: 'success',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 1500
});
}
function changeQty(id, delta) {
if (cart[id]) {
cart[id].qty += delta;
if (cart[id].qty <= 0) {
delete cart[id];
}
saveCart();
renderCart();
}
}
function renderCart() {
const list = document.getElementById('cartItemsList');
let html = '';
let total = 0;
let totalVat = 0;
if (Object.keys(cart).length === 0) {
html = '<div class="text-center text-muted py-4"><?= h(tr('السلة فارغة', 'Cart is empty')) ?></div>';
} else {
html = '<div class="list-group list-group-flush">';
for (let id in cart) {
const item = cart[id];
const subtotal = item.price * item.qty;
const itemVat = subtotal * ((item.vat || 0) / 100);
totalVat += itemVat;
total += subtotal;
html += `
<div class="list-group-item d-flex justify-content-between align-items-center py-3 px-0 border-bottom-dashed">
<div>
<h6 class="mb-1 fw-bold">${item.name}</h6>
<small class="text-muted">${Number(item.price).toFixed(2)}</small>
</div>
<div class="d-flex align-items-center">
<button class="btn btn-sm btn-outline-secondary rounded-circle px-2 me-2" onclick="changeQty(${id}, -1)"><i class="bi bi-dash"></i></button>
<span class="fw-bold px-2">${item.qty}</span>
<button class="btn btn-sm btn-outline-secondary rounded-circle px-2 ms-2 me-4" onclick="changeQty(${id}, 1)"><i class="bi bi-plus"></i></button>
<span class="fw-bold text-primary" style="width: 70px; text-align:right;">${subtotal.toFixed(2)}</span>
</div>
</div>`;
}
html += '</div>';
}
list.innerHTML = html;
document.getElementById('cartSubtotal').innerText = total.toFixed(2);
document.getElementById('cartVat').innerText = totalVat.toFixed(2);
document.getElementById('cartTotal').innerText = (total + totalVat).toFixed(2);
}
function openCart() {
if (!cartModalInstance && typeof bootstrap !== 'undefined') {
cartModalInstance = new bootstrap.Modal(document.getElementById('cartModal'));
}
if (cartModalInstance) {
renderCart();
cartModalInstance.show();
} else {
console.error("Bootstrap is not loaded yet.");
}
}
async function submitOrder() {
if (Object.keys(cart).length === 0) {
Swal.fire('<?= h(tr('تنبيه', 'Warning')) ?>', '<?= h(tr('السلة فارغة', 'Cart is empty')) ?>', 'warning');
return;
}
const form = document.getElementById('checkoutForm');
if (!form.reportValidity()) return;
const btn = document.getElementById('submitOrderBtn');
const origText = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
btn.disabled = true;
const paymentMethodField = document.querySelector('input[name="payment_method"]:checked');
if (!paymentMethodField) {
Swal.fire('<?= h(tr('تنبيه', 'Warning')) ?>', '<?= h(tr('اختر طريقة الدفع.', 'Please choose a payment method.')) ?>', 'warning');
btn.innerHTML = origText;
btn.disabled = false;
return;
}
const data = {
name: document.getElementById('customerName').value,
phone: document.getElementById('customerPhone').value,
address: document.getElementById('customerAddress').value,
payment_method: paymentMethodField.value,
accept_policies: document.getElementById('acceptPolicies').checked,
items: cart
};
try {
const res = await fetch('api/place_order.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.success) {
cart = {};
saveCart();
if (cartModalInstance) cartModalInstance.hide();
if (json.redirect_url) {
window.location.href = json.redirect_url;
return;
}
Swal.fire({
title: '<?= h(tr('تم إرسال الطلب بنجاح!', 'Order submitted successfully!')) ?>',
text: '<?= h(tr('سنتواصل معك قريباً لتأكيد الطلب.', 'We will contact you shortly to confirm the order.')) ?>',
icon: 'success',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
form.reset();
} else {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل إرسال الطلب', 'Failed to submit order')) ?>', 'error');
}
} catch (e) {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
} finally {
btn.innerHTML = origText;
btn.disabled = false;
}
}
// Filtering + Pagination Logic
let currentCategory = 'all';
let currentPage = 1;
const itemsPerPage = 24;
function setCategoryFilter(cat, btn) {
currentCategory = cat;
currentPage = 1;
document.querySelectorAll('#categoryFilters button').forEach(b => {
b.classList.remove('btn-primary', 'active');
b.classList.add('btn-light', 'text-dark');
});
btn.classList.remove('btn-light', 'text-dark');
btn.classList.add('btn-primary', 'active');
filterProducts();
}
function getFilteredItems() {
const searchInput = document.getElementById('searchInput');
const searchVal = searchInput ? searchInput.value.toLowerCase().trim() : '';
return Array.from(document.querySelectorAll('.product-item')).filter(item => {
const itemName = (item.getAttribute('data-name') || '').toLowerCase();
const itemCategory = item.closest('.category-row')?.getAttribute('data-category') || '';
const matchesSearch = itemName.includes(searchVal);
const matchesCategory = currentCategory === 'all' || currentCategory === itemCategory;
return matchesSearch && matchesCategory;
});
}
function renderPagination(totalItems) {
const top = document.getElementById('paginationTop');
const bottom = document.getElementById('paginationBottom');
const statusTop = document.getElementById('paginationStatus');
const statusBottom = document.getElementById('paginationStatusBottom');
const footerWrap = document.getElementById('paginationFooter');
if (!top || !bottom || !statusTop || !statusBottom) return;
const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
if (currentPage > totalPages) currentPage = totalPages;
if (totalItems === 0) {
statusTop.textContent = <?= json_encode(tr('0 منتج', '0 products')) ?>;
statusBottom.textContent = statusTop.textContent;
top.innerHTML = '';
bottom.innerHTML = '';
if (footerWrap) footerWrap.style.display = 'none';
return;
}
const start = ((currentPage - 1) * itemsPerPage) + 1;
const end = Math.min(totalItems, currentPage * itemsPerPage);
const statusText = `<?= h(tr('عرض', 'Showing')) ?> ${start}-${end} <?= h(tr('من', 'of')) ?> ${totalItems}`;
statusTop.textContent = statusText;
statusBottom.textContent = statusText;
if (footerWrap) footerWrap.style.display = '';
if (totalPages <= 1) {
top.innerHTML = '';
bottom.innerHTML = '';
return;
}
const pages = [];
pages.push(1);
for (let p = Math.max(2, currentPage - 1); p <= Math.min(totalPages - 1, currentPage + 1); p++) {
pages.push(p);
}
if (totalPages > 1) pages.push(totalPages);
const uniquePages = [...new Set(pages)].sort((a, b) => a - b);
const parts = [];
const addControl = (label, page, disabled = false, active = false) => {
parts.push(`<li class="page-item${disabled ? ' disabled' : ''}${active ? ' active' : ''}"><button class="page-link" type="button" ${disabled ? 'disabled' : ''} onclick="goToPage(${page})">${label}</button></li>`);
};
addControl('', Math.max(1, currentPage - 1), currentPage === 1);
let lastPage = 0;
uniquePages.forEach(page => {
if (lastPage && page > lastPage + 1) {
parts.push('<li class="page-item disabled"><span class="page-link">…</span></li>');
}
addControl(page, page, false, page === currentPage);
lastPage = page;
});
addControl('', Math.min(totalPages, currentPage + 1), currentPage === totalPages);
const html = parts.join('');
top.innerHTML = html;
bottom.innerHTML = html;
}
function goToPage(page) {
currentPage = page;
filterProducts(false);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function filterProducts(resetPage = true) {
if (resetPage) currentPage = 1;
const filteredItems = getFilteredItems();
const totalVisible = filteredItems.length;
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const visibleOnPage = new Set(filteredItems.slice(startIndex, endIndex));
document.querySelectorAll('.category-row').forEach(row => {
let hasVisibleItems = false;
row.querySelectorAll('.product-item').forEach(item => {
if (visibleOnPage.has(item)) {
item.style.display = '';
hasVisibleItems = true;
} else {
item.style.display = 'none';
}
});
const title = row.previousElementSibling;
if (title && title.classList.contains('category-title')) {
title.style.display = hasVisibleItems ? '' : 'none';
row.style.display = hasVisibleItems ? '' : 'none';
}
});
const noResultsMsg = document.getElementById('noResultsMsg');
if (noResultsMsg) {
noResultsMsg.style.display = totalVisible === 0 ? 'block' : 'none';
}
renderPagination(totalVisible);
}
// init
updateCartBadge();
filterProducts();
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>