Autosave: 20260227-062846

This commit is contained in:
Flatlogic Bot 2026-02-27 06:28:46 +00:00
parent f1e599bc52
commit fa60b1d6db

View File

@ -17,7 +17,6 @@ if ($table_id <= 0) {
// Fetch table and outlet info // Fetch table and outlet info
try { try {
// Using standard aliases without backticks for better compatibility
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT t.id, t.table_number AS table_name, a.outlet_id, o.name AS outlet_name SELECT t.id, t.table_number AS table_name, a.outlet_id, o.name AS outlet_name
FROM `tables` t FROM `tables` t
@ -37,10 +36,10 @@ try {
$outlet_id = (int)$table_info['outlet_id']; $outlet_id = (int)$table_info['outlet_id'];
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll(); $categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll();
$all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll(); $all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id WHERE p.deleted_at IS NULL")->fetchAll();
// Fetch variants // Fetch variants
$variants_raw = $pdo->query("SELECT * FROM product_variants ORDER BY price_adjustment ASC")->fetchAll(); $variants_raw = $pdo->query("SELECT * FROM product_variants WHERE deleted_at IS NULL ORDER BY price_adjustment ASC")->fetchAll();
$variants_by_product = []; $variants_by_product = [];
foreach ($variants_raw as $v) { foreach ($variants_raw as $v) {
$variants_by_product[$v['product_id']][] = $v; $variants_by_product[$v['product_id']][] = $v;
@ -58,68 +57,237 @@ foreach ($variants_raw as $v) {
<?php endif; ?> <?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+Arabic:wght@400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;600;700&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--primary-font: 'Inter', sans-serif; --primary-color: #2563eb;
--secondary-color: #64748b;
--bg-light: #f8fafc;
--card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--primary-font: 'Plus Jakarta Sans', sans-serif;
--arabic-font: 'Noto Sans Arabic', sans-serif; --arabic-font: 'Noto Sans Arabic', sans-serif;
} }
body { font-family: var(--primary-font); background-color: #f8f9fa; padding-bottom: 80px; } body {
font-family: var(--primary-font);
background-color: var(--bg-light);
color: #1e293b;
padding-bottom: 100px;
}
body.lang-ar { font-family: var(--arabic-font); direction: rtl; text-align: right; } body.lang-ar { font-family: var(--arabic-font); direction: rtl; text-align: right; }
.category-nav { overflow-x: auto; white-space: nowrap; background: #fff; padding: 12px 10px; position: sticky; top: 0; z-index: 1020; border-bottom: 1px solid #eee; -webkit-overflow-scrolling: touch; } /* Header & Hero */
.hero-section {
background: white;
padding: 2rem 0;
border-bottom: 1px solid #e2e8f0;
margin-bottom: 1.5rem;
}
.company-logo {
height: 48px;
width: auto;
object-fit: contain;
}
/* Category Navigation */
.category-nav-wrapper {
position: sticky;
top: 0;
z-index: 1020;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(8px);
border-bottom: 1px solid #e2e8f0;
padding: 0.75rem 0;
}
.category-nav {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
padding: 0 1rem;
}
.category-nav::-webkit-scrollbar { display: none; } .category-nav::-webkit-scrollbar { display: none; }
.category-item { display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 20px; background: #f1f3f5; margin-right: 8px; font-weight: 600; font-size: 0.95rem; cursor: pointer; border: 1px solid transparent; transition: all 0.2s; vertical-align: middle; } .category-item {
.lang-ar .category-item { margin-right: 0; margin-left: 8px; } display: inline-flex;
.category-item.active { background: #0d6efd; color: #fff; border-color: #0d6efd; } align-items: center;
gap: 0.5rem;
padding: 0.6rem 1.25rem;
border-radius: 9999px;
background: white;
margin-right: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
border: 1px solid #e2e8f0;
transition: all 0.2s ease;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.lang-ar .category-item { margin-right: 0; margin-left: 0.5rem; }
.category-item:hover { border-color: var(--primary-color); color: var(--primary-color); }
.category-item.active {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.2);
}
.product-card { border: none; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); transition: transform 0.2s; position: relative; } /* Product Grid - 5 columns on desktop */
.product-card:active { transform: scale(0.98); } .product-grid {
.product-img { height: 140px; object-fit: cover; } display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
@media (min-width: 576px) { .product-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .product-grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1200px) { .product-grid { grid-template-columns: repeat(5, 1fr); } }
.cart-footer { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 15px; border-top: 1px solid #eee; z-index: 1030; display: none; } .product-card {
.badge-price { position: absolute; bottom: 10px; right: 10px; background: rgba(255,255,255,0.9); padding: 2px 8px; border-radius: 12px; font-weight: bold; font-size: 0.85rem; } background: white;
.lang-ar .badge-price { right: auto; left: 10px; } border: 1px solid #f1f5f9;
border-radius: 1rem;
overflow: hidden;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
cursor: pointer;
height: 100%;
display: flex;
flex-direction: column;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
border-color: #cbd5e1;
}
.product-card:active { transform: scale(0.97); }
.quantity-controls { display: flex; align-items: center; gap: 10px; } .product-img-container {
.quantity-btn { width: 32px; height: 32px; border-radius: 50%; border: 1px solid #dee2e6; background: #fff; display: flex; align-items: center; justify-content: center; } position: relative;
width: 100%;
padding-top: 100%; /* 1:1 Aspect Ratio */
background: #f1f5f9;
overflow: hidden;
}
.product-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.lang-toggle { font-size: 0.8rem; font-weight: bold; cursor: pointer; padding: 4px 8px; border-radius: 4px; border: 1px solid #dee2e6; background: #f8f9fa; } .product-info { padding: 0.75rem; flex-grow: 1; display: flex; flex-direction: column; }
.product-title {
font-weight: 700;
font-size: 0.95rem;
margin-bottom: 0.25rem;
color: #0f172a;
line-height: 1.2;
}
.product-price { font-weight: 800; color: var(--primary-color); font-size: 1rem; margin-top: auto; }
.name-en { display: inline-block; } .sale-badge {
position: absolute;
top: 0.5rem;
left: 0.5rem;
background: #ef4444;
color: white;
font-size: 0.7rem;
font-weight: 800;
padding: 0.2rem 0.6rem;
border-radius: 9999px;
z-index: 10;
}
.lang-ar .sale-badge { left: auto; right: 0.5rem; }
/* Cart Footer */
.cart-footer {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 500px;
background: #1e293b;
padding: 0.75rem 1.25rem;
border-radius: 1.25rem;
z-index: 1030;
display: none;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.2);
color: white;
}
/* Footer */
.site-footer {
padding: 3rem 0;
background: #f1f5f9;
border-top: 1px solid #e2e8f0;
margin-top: 3rem;
text-align: center;
}
.lang-toggle {
font-size: 0.85rem;
font-weight: 700;
cursor: pointer;
padding: 0.5rem 1rem;
border-radius: 0.75rem;
border: 1px solid #e2e8f0;
background: white;
transition: all 0.2s;
}
.lang-toggle:hover { background: #f1f5f9; }
.name-en { display: block; }
.name-ar { display: none; } .name-ar { display: none; }
.lang-ar .name-en { display: none; } .lang-ar .name-en { display: none; }
.lang-ar .name-ar { display: inline-block; } .lang-ar .name-ar { display: block; }
.both-names .name-en { display: block; } .both-names .name-en { display: block; }
.both-names .name-ar { display: block; font-size: 0.85em; opacity: 0.8; color: inherit; margin-top: 1px; } .both-names .name-ar { display: block; font-size: 0.8em; opacity: 0.8; margin-top: 2px; }
.category-item.active .both-names .name-ar { color: #fff; }
.modal-header .btn-close { margin: 0; } .cart-item-img { width: 60px; height: 60px; object-fit: cover; border-radius: 0.75rem; }
.lang-ar .modal-header .btn-close { margin-right: auto; margin-left: 0; } .quantity-btn {
width: 36px;
height: 36px;
border-radius: 0.75rem;
border: 1px solid #e2e8f0;
background: white;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.quantity-btn:active { background: #f1f5f9; transform: scale(0.9); }
</style> </style>
</head> </head>
<body class="both-names"> <body class="both-names">
<!-- Header --> <!-- Hero Section -->
<header class="bg-white p-3 border-bottom d-flex align-items-center justify-content-between"> <div class="hero-section">
<div class="d-flex align-items-center gap-2"> <div class="container text-center">
<div class="d-flex flex-column align-items-center mb-3">
<?php if (!empty($settings['logo_url'])): ?> <?php if (!empty($settings['logo_url'])): ?>
<img src="<?= get_base_url() . htmlspecialchars($settings['logo_url']) ?>?v=<?= time() ?>" alt="Logo" style="height: 32px;"> <img src="<?= get_base_url() . htmlspecialchars($settings['logo_url']) ?>?v=<?= time() ?>" alt="Logo" class="company-logo mb-3">
<?php endif; ?> <?php endif; ?>
<span class="fw-bold"><?= htmlspecialchars($settings['company_name']) ?></span> <h1 class="h4 fw-bold mb-1"><?= htmlspecialchars($settings['company_name']) ?></h1>
<p class="text-muted small mb-0">
<i class="bi bi-geo-alt-fill me-1"></i> <?= htmlspecialchars($table_info['outlet_name']) ?>
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">
<span data-t="table">Table</span> <?= htmlspecialchars((string)$table_info['table_name']) ?>
</span>
</p>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex justify-content-center gap-2 mt-3">
<div class="lang-toggle" onclick="toggleLanguage()" id="lang-btn">AR</div> <div class="lang-toggle" onclick="toggleLanguage()" id="lang-btn">AR</div>
<div class="badge bg-light text-dark border"><span data-t="table">Table</span> <?= htmlspecialchars((string)$table_info['table_name']) ?></div>
</div> </div>
</header> </div>
</div>
<!-- Category Nav --> <!-- Category Nav -->
<div class="category-nav shadow-sm"> <div class="category-nav-wrapper">
<div class="category-nav">
<div class="category-item active" onclick="filterCategory('all', this)"> <div class="category-item active" onclick="filterCategory('all', this)">
<i class="bi bi-grid"></i> <i class="bi bi-grid-fill"></i>
<div class="d-inline-block"> <div>
<span class="name-en">All</span> <span class="name-en">All</span>
<span class="name-ar">الكل</span> <span class="name-ar">الكل</span>
</div> </div>
@ -127,69 +295,64 @@ foreach ($variants_raw as $v) {
<?php foreach ($categories as $cat): ?> <?php foreach ($categories as $cat): ?>
<div class="category-item" onclick="filterCategory(<?= $cat['id'] ?>, this)"> <div class="category-item" onclick="filterCategory(<?= $cat['id'] ?>, this)">
<?php if (!empty($cat['image_url'])): ?> <?php if (!empty($cat['image_url'])): ?>
<img src="<?= htmlspecialchars(strpos($cat['image_url'], 'http') === 0 ? $cat['image_url'] : get_base_url() . $cat['image_url']) ?>" alt="" style="width: 22px; height: 22px; object-fit: cover; border-radius: 50%;"> <img src="<?= htmlspecialchars(strpos($cat['image_url'], 'http') === 0 ? $cat['image_url'] : get_base_url() . $cat['image_url']) ?>" alt="" style="width: 20px; height: 20px; object-fit: cover; border-radius: 4px;">
<?php else: ?> <?php else: ?>
<i class="bi bi-tag"></i> <i class="bi bi-tag-fill"></i>
<?php endif; ?> <?php endif; ?>
<div class="d-inline-block"> <div>
<span class="name-en"><?= htmlspecialchars($cat['name']) ?></span> <span class="name-en"><?= htmlspecialchars($cat['name']) ?></span>
<?php if (!empty($cat['name_ar'])): ?> <span class="name-ar"><?= htmlspecialchars($cat['name_ar'] ?: $cat['name']) ?></span>
<span class="name-ar"><?= htmlspecialchars($cat['name_ar']) ?></span>
<?php else: ?>
<span class="name-ar"><?= htmlspecialchars($cat['name']) ?></span>
<?php endif; ?>
</div> </div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div>
<div class="container py-3"> <div class="container mt-4">
<div class="row g-3" id="products-container"> <div class="product-grid" id="products-container">
<?php foreach ($all_products as $product): <?php foreach ($all_products as $product):
$has_variants = !empty($variants_by_product[$product['id']]); $has_variants = !empty($variants_by_product[$product['id']]);
$effective_price = get_product_price($product); $effective_price = get_product_price($product);
$is_promo = $effective_price < (float)$product['price']; $is_promo = $effective_price < (float)$product['price'];
?> ?>
<div class="col-6 col-md-4 product-item" data-category-id="<?= $product['category_id'] ?>"> <div class="product-item" data-category-id="<?= $product['category_id'] ?>">
<div class="card h-100 product-card" <div class="product-card"
onclick="handleProductClick(<?= htmlspecialchars((string)json_encode([ onclick="handleProductClick(<?= htmlspecialchars((string)json_encode([
'id' => $product['id'], 'id' => $product['id'],
'name' => $product['name'], 'name' => $product['name'],
'name_ar' => $product['name_ar'] ?? $product['name'], 'name_ar' => $product['name_ar'] ?: $product['name'],
'price' => (float)$effective_price, 'price' => (float)$effective_price,
'has_variants' => $has_variants 'has_variants' => $has_variants,
'image' => !empty($product['image_url']) ? (strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : get_base_url() . $product['image_url']) : null
])) ?>)"> ])) ?>)">
<div class="position-relative">
<div class="product-img-container">
<?php if ($is_promo): ?>
<span class="sale-badge">SALE</span>
<?php endif; ?>
<?php if (!empty($product['image_url'])): ?> <?php if (!empty($product['image_url'])): ?>
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : get_base_url() . $product['image_url']) ?>?v=<?= time() ?>" class="card-img-top product-img" alt="<?= htmlspecialchars($product['name']) ?>"> <img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : get_base_url() . $product['image_url']) ?>?v=<?= time() ?>" class="product-img" alt="<?= htmlspecialchars($product['name']) ?>" loading="lazy">
<?php else: ?> <?php else: ?>
<div class="card-img-top product-img d-flex align-items-center justify-content-center bg-light"> <div class="product-img d-flex align-items-center justify-content-center bg-light text-muted opacity-25">
<i class="bi bi-image text-muted opacity-50 fs-1"></i> <i class="bi bi-image fs-1"></i>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="badge-price text-primary"> </div>
<div class="product-info">
<h3 class="product-title">
<span class="name-en"><?= htmlspecialchars($product['name']) ?></span>
<span class="name-ar" dir="rtl"><?= htmlspecialchars($product['name_ar'] ?: $product['name']) ?></span>
</h3>
<div class="product-price">
<?php if ($is_promo): ?> <?php if ($is_promo): ?>
<span class="text-danger fw-bold"><?= format_currency($effective_price) ?></span> <span class="text-danger"><?= format_currency($effective_price) ?></span>
<small class="text-muted text-decoration-line-through ms-1" style="font-size: 0.7rem;"><?= format_currency((float)$product['price']) ?></small> <small class="text-muted text-decoration-line-through ms-1 opacity-50" style="font-size: 0.75rem;"><?= format_currency((float)$product['price']) ?></small>
<?php else: ?> <?php else: ?>
<?= format_currency((float)$product['price']) ?> <?= format_currency((float)$product['price']) ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php if ($is_promo): ?>
<div class="position-absolute top-0 start-0 m-2">
<span class="badge bg-warning text-dark fw-bold rounded-pill" style="font-size: 0.6rem;">SALE</span>
</div>
<?php endif; ?>
</div>
<div class="card-body p-2">
<h6 class="card-title mb-1 small fw-bold text-truncate">
<span class="name-en"><?= htmlspecialchars($product['name']) ?></span>
<span class="name-ar" dir="rtl"><?= htmlspecialchars($product['name_ar'] ?? $product['name']) ?></span>
</h6>
<p class="card-text small text-muted mb-0 text-truncate" style="font-size: 0.75rem;">
<span class="name-en"><?= htmlspecialchars($product['category_name']) ?></span>
<span class="name-ar" dir="rtl"><?= htmlspecialchars($product['category_name_ar'] ?? $product['category_name']) ?></span>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -197,66 +360,87 @@ foreach ($variants_raw as $v) {
</div> </div>
</div> </div>
<!-- Cart Footer --> <!-- Site Footer -->
<div class="cart-footer shadow-lg" id="cart-footer"> <footer class="site-footer">
<div class="d-flex align-items-center justify-content-between"> <div class="container">
<div> <p class="mb-2 fw-bold"><?= htmlspecialchars($settings['company_name']) ?></p>
<div class="small text-muted"><span id="cart-items-count">0</span> <span data-t="items">Items</span></div> <p class="text-muted small mb-3"><?= htmlspecialchars($settings['address'] ?? '') ?></p>
<div class="fw-bold fs-5 text-primary" id="cart-total-display"><?= format_currency(0) ?></div> <div class="d-flex justify-content-center gap-3 mb-4">
<a href="#" class="text-secondary"><i class="bi bi-facebook fs-5"></i></a>
<a href="#" class="text-secondary"><i class="bi bi-instagram fs-5"></i></a>
<a href="#" class="text-secondary"><i class="bi bi-whatsapp fs-5"></i></a>
</div>
<hr class="w-25 mx-auto mb-4">
<p class="text-muted small">&copy; <?= date('Y') ?> All Rights Reserved. Powered by Gemini POS</p>
</div>
</footer>
<!-- Cart Bar -->
<div class="cart-footer" id="cart-footer">
<div class="d-flex align-items-center justify-content-between" onclick="showCart()" style="cursor: pointer;">
<div class="d-flex align-items-center gap-3">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-bag-check-fill"></i>
</div>
<div>
<div class="small opacity-75"><span id="cart-items-count">0</span> <span data-t="items">Items</span></div>
<div class="fw-bold fs-5" id="cart-total-display"><?= format_currency(0) ?></div>
</div>
</div>
<div class="fw-bold">
<span data-t="view_cart">View Cart</span> <i class="bi bi-chevron-right ms-1"></i>
</div> </div>
<button class="btn btn-primary px-4 fw-bold" onclick="showCart()">
<span data-t="view_cart">View Cart</span> <i class="bi bi-cart-fill ms-1"></i>
</button>
</div> </div>
</div> </div>
<!-- Cart Modal --> <!-- Cart Modal -->
<div class="modal fade" id="cartModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="cartModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down"> <div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
<div class="modal-content"> <div class="modal-content border-0 shadow-lg">
<div class="modal-header"> <div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold" data-t="your_order">Your Order</h5> <h5 class="modal-title fw-bold" data-t="your_order">Your Order</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body p-0"> <div class="modal-body">
<div id="cart-list" class="list-group list-group-flush"> <div id="cart-list" class="mb-4">
<!-- Cart items here --> <!-- Cart items -->
</div> </div>
<div class="p-3 bg-light border-top">
<div class="bg-light p-3 rounded-4 mb-4">
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<span data-t="subtotal">Subtotal</span> <span class="text-muted" data-t="subtotal">Subtotal</span>
<span id="modal-subtotal"><?= format_currency(0) ?></span> <span id="modal-subtotal" class="fw-bold"><?= format_currency(0) ?></span>
</div> </div>
<div class="d-flex justify-content-between fw-bold fs-5"> <div class="d-flex justify-content-between border-top pt-2 mt-2">
<span data-t="total">Total</span> <span class="fw-bold fs-5" data-t="total">Total</span>
<span id="modal-total"><?= format_currency(0) ?></span> <span id="modal-total" class="fw-bold fs-5 text-primary"><?= format_currency(0) ?></span>
</div> </div>
</div> </div>
<div class="p-3">
<div class="mb-3"> <div class="mb-4">
<label class="form-label small text-muted" data-t="cust_name_label">Your Name (Optional)</label> <label class="form-label small fw-bold text-secondary" data-t="cust_name_label">Your Name (Optional)</label>
<input type="text" id="cust-name" class="form-control" data-t-placeholder="cust_name_placeholder" placeholder="To identify your order"> <input type="text" id="cust-name" class="form-control form-control-lg border-0 bg-light rounded-3" data-t-placeholder="cust_name_placeholder" placeholder="To identify your order">
</div> </div>
<button class="btn btn-primary btn-lg w-100 fw-bold" id="btn-place-order" onclick="placeOrder()">
<span data-t="place_order">Place Order</span> <i class="bi bi-send-fill ms-1"></i> <button class="btn btn-primary btn-lg w-100 py-3 rounded-4 fw-bold shadow-sm" id="btn-place-order" onclick="placeOrder()">
<span data-t="place_order">Place Order</span> <i class="bi bi-arrow-right-short ms-1"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Variant Selection Modal --> <!-- Variant Modal -->
<div class="modal fade" id="variantModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="variantModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content border-0 shadow-lg">
<div class="modal-header"> <div class="modal-header border-0">
<h5 class="modal-title" id="variantTitle" data-t="select_option">Select Option</h5> <h5 class="modal-title fw-bold" id="variantTitle" data-t="select_option">Select Option</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body p-0">
<div id="variant-list" class="list-group"> <div id="variant-list" class="list-group list-group-flush">
<!-- Variants injected by JS --> <!-- Variants -->
</div> </div>
</div> </div>
</div> </div>
@ -316,35 +500,20 @@ foreach ($variants_raw as $v) {
const decimals = parseInt(COMPANY_SETTINGS.currency_decimals || 2); const decimals = parseInt(COMPANY_SETTINGS.currency_decimals || 2);
const position = COMPANY_SETTINGS.currency_position || 'before'; const position = COMPANY_SETTINGS.currency_position || 'before';
const formatted = parseFloat(Math.abs(amount)).toFixed(decimals); const formatted = parseFloat(Math.abs(amount)).toFixed(decimals);
return position === 'after' ? formatted + ' ' + symbol : symbol + formatted;
if (position === 'after') {
return formatted + ' ' + symbol;
} else {
return symbol + formatted;
}
} }
function updateTranslations() { function updateTranslations() {
document.querySelectorAll('[data-t]').forEach(el => { document.querySelectorAll('[data-t]').forEach(el => {
const key = el.getAttribute('data-t'); const key = el.getAttribute('data-t');
if (translations[currentLang][key]) { if (translations[currentLang][key]) el.textContent = translations[currentLang][key];
el.textContent = translations[currentLang][key];
}
}); });
document.querySelectorAll('[data-t-placeholder]').forEach(el => { document.querySelectorAll('[data-t-placeholder]').forEach(el => {
const key = el.getAttribute('data-t-placeholder'); const key = el.getAttribute('data-t-placeholder');
if (translations[currentLang][key]) { if (translations[currentLang][key]) el.placeholder = translations[currentLang][key];
el.placeholder = translations[currentLang][key];
}
}); });
document.getElementById('lang-btn').textContent = currentLang === 'en' ? 'AR' : 'EN'; document.getElementById('lang-btn').textContent = currentLang === 'en' ? 'AR' : 'EN';
if (currentLang === 'ar') { document.body.classList.toggle('lang-ar', currentLang === 'ar');
document.body.classList.add('lang-ar');
} else {
document.body.classList.remove('lang-ar');
}
// Update currency in displays
updateCartUI(); updateCartUI();
} }
@ -354,19 +523,13 @@ foreach ($variants_raw as $v) {
updateTranslations(); updateTranslations();
} }
// Initialize translations
updateTranslations(); updateTranslations();
function filterCategory(catId, el) { function filterCategory(catId, el) {
document.querySelectorAll('.category-item').forEach(i => i.classList.remove('active')); document.querySelectorAll('.category-item').forEach(i => i.classList.remove('active'));
el.classList.add('active'); el.classList.add('active');
document.querySelectorAll('.product-item').forEach(item => { document.querySelectorAll('.product-item').forEach(item => {
if (catId === 'all' || item.dataset.categoryId == catId) { item.style.display = (catId === 'all' || item.dataset.categoryId == catId) ? 'block' : 'none';
item.style.display = 'block';
} else {
item.style.display = 'none';
}
}); });
} }
@ -374,7 +537,7 @@ foreach ($variants_raw as $v) {
if (product.has_variants) { if (product.has_variants) {
showVariants(product); showVariants(product);
} else { } else {
addToCart(product.id, currentLang === 'ar' ? product.name_ar : product.name, product.price); addToCart(product.id, currentLang === 'ar' ? product.name_ar : product.name, product.price, null, product.image);
} }
} }
@ -386,7 +549,7 @@ foreach ($variants_raw as $v) {
variants.forEach(v => { variants.forEach(v => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center py-3'; btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center py-3 border-0';
const adj = parseFloat(v.price_adjustment); const adj = parseFloat(v.price_adjustment);
const finalPrice = product.price + adj; const finalPrice = product.price + adj;
const vName = currentLang === 'ar' && v.name_ar ? v.name_ar : v.name; const vName = currentLang === 'ar' && v.name_ar ? v.name_ar : v.name;
@ -400,7 +563,7 @@ foreach ($variants_raw as $v) {
<div class="fw-bold text-primary">${formatCurrency(finalPrice)}</div> <div class="fw-bold text-primary">${formatCurrency(finalPrice)}</div>
`; `;
btn.onclick = () => { btn.onclick = () => {
addToCart(product.id, `${pName} (${vName})`, finalPrice, v.id); addToCart(product.id, `${pName} (${vName})`, finalPrice, v.id, product.image);
variantModal.hide(); variantModal.hide();
}; };
container.appendChild(btn); container.appendChild(btn);
@ -408,12 +571,12 @@ foreach ($variants_raw as $v) {
variantModal.show(); variantModal.show();
} }
function addToCart(pid, name, price, vid = null) { function addToCart(pid, name, price, vid = null, image = null) {
const existing = cart.find(i => i.product_id === pid && i.variant_id === vid); const existing = cart.find(i => i.product_id === pid && i.variant_id === vid);
if (existing) { if (existing) {
existing.quantity++; existing.quantity++;
} else { } else {
cart.push({ product_id: pid, name, unit_price: price, variant_id: vid, quantity: 1 }); cart.push({ product_id: pid, name, unit_price: price, variant_id: vid, quantity: 1, image });
} }
updateCartUI(); updateCartUI();
showToast(name + ' ' + translations[currentLang].added_to_cart); showToast(name + ' ' + translations[currentLang].added_to_cart);
@ -422,16 +585,9 @@ foreach ($variants_raw as $v) {
function updateCartUI() { function updateCartUI() {
const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0); const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const count = cart.reduce((sum, item) => sum + item.quantity, 0); const count = cart.reduce((sum, item) => sum + item.quantity, 0);
document.getElementById('cart-items-count').textContent = count; document.getElementById('cart-items-count').textContent = count;
document.getElementById('cart-total-display').textContent = formatCurrency(total); document.getElementById('cart-total-display').textContent = formatCurrency(total);
document.getElementById('cart-footer').style.display = count > 0 ? 'block' : 'none';
const footer = document.getElementById('cart-footer');
if (count > 0) {
footer.style.display = 'block';
} else {
footer.style.display = 'none';
}
} }
function showCart() { function showCart() {
@ -440,20 +596,18 @@ foreach ($variants_raw as $v) {
cart.forEach((item, index) => { cart.forEach((item, index) => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'list-group-item p-3'; div.className = 'd-flex align-items-center gap-3 mb-4';
div.innerHTML = ` div.innerHTML = `
<div class="d-flex justify-content-between align-items-start mb-2"> ${item.image ? `<img src="${item.image}" class="cart-item-img">` : `<div class="cart-item-img bg-light d-flex align-items-center justify-content-center text-muted"><i class="bi bi-image"></i></div>`}
<div class="fw-bold text-truncate me-2">${item.name}</div> <div class="flex-grow-1">
<div class="fw-bold">${formatCurrency(item.unit_price * item.quantity)}</div> <div class="fw-bold text-truncate" style="max-width: 150px;">${item.name}</div>
<div class="text-primary fw-bold">${formatCurrency(item.unit_price * item.quantity)}</div>
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex align-items-center gap-2">
<div class="text-muted small">${formatCurrency(item.unit_price)} / unit</div>
<div class="quantity-controls">
<button class="quantity-btn" onclick="updateQty(${index}, -1)"><i class="bi bi-dash"></i></button> <button class="quantity-btn" onclick="updateQty(${index}, -1)"><i class="bi bi-dash"></i></button>
<span class="fw-bold">${item.quantity}</span> <span class="fw-bold px-1">${item.quantity}</span>
<button class="quantity-btn" onclick="updateQty(${index}, 1)"><i class="bi bi-plus"></i></button> <button class="quantity-btn" onclick="updateQty(${index}, 1)"><i class="bi bi-plus"></i></button>
</div> </div>
</div>
`; `;
list.appendChild(div); list.appendChild(div);
}); });
@ -461,26 +615,18 @@ foreach ($variants_raw as $v) {
const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0); const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
document.getElementById('modal-subtotal').textContent = formatCurrency(total); document.getElementById('modal-subtotal').textContent = formatCurrency(total);
document.getElementById('modal-total').textContent = formatCurrency(total); document.getElementById('modal-total').textContent = formatCurrency(total);
cartModal.show(); cartModal.show();
} }
function updateQty(index, delta) { function updateQty(index, delta) {
cart[index].quantity += delta; cart[index].quantity += delta;
if (cart[index].quantity <= 0) { if (cart[index].quantity <= 0) cart.splice(index, 1);
cart.splice(index, 1); if (cart.length === 0) cartModal.hide(); else showCart();
}
if (cart.length === 0) {
cartModal.hide();
} else {
showCart();
}
updateCartUI(); updateCartUI();
} }
function placeOrder() { function placeOrder() {
if (cart.length === 0) return; if (cart.length === 0) return;
const btn = document.getElementById('btn-place-order'); const btn = document.getElementById('btn-place-order');
btn.disabled = true; btn.disabled = true;
btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${translations[currentLang].placing_order}`; btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${translations[currentLang].placing_order}`;
@ -488,20 +634,17 @@ foreach ($variants_raw as $v) {
const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0); const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const customerName = document.getElementById('cust-name').value; const customerName = document.getElementById('cust-name').value;
const payload = {
outlet_id: OUTLET_ID,
table_id: TABLE_ID, // Use table_id instead of table_number
order_type: 'dine-in',
customer_name: customerName,
items: cart,
total_amount: total,
payment_type_id: null
};
fetch('api/order.php', { fetch('api/order.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload) body: JSON.stringify({
outlet_id: OUTLET_ID,
table_id: TABLE_ID,
order_type: 'dine-in',
customer_name: customerName,
items: cart,
total_amount: total
})
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
@ -510,23 +653,20 @@ foreach ($variants_raw as $v) {
title: translations[currentLang].order_placed, title: translations[currentLang].order_placed,
text: translations[currentLang].order_success_msg, text: translations[currentLang].order_success_msg,
icon: 'success', icon: 'success',
confirmButtonText: currentLang === 'ar' ? 'ممتاز' : 'Great!' confirmButtonText: currentLang === 'ar' ? 'ممتاز' : 'Great!',
confirmButtonColor: '#2563eb'
}).then(() => { }).then(() => {
cart = []; cart = [];
updateCartUI(); updateCartUI();
cartModal.hide(); cartModal.hide();
document.getElementById('cust-name').value = ''; document.getElementById('cust-name').value = '';
}); });
} else { } else throw new Error(data.error || 'Failed to place order');
throw new Error(data.error || 'Failed to place order');
}
})
.catch(err => {
Swal.fire('Error', err.message, 'error');
}) })
.catch(err => Swal.fire('Error', err.message, 'error'))
.finally(() => { .finally(() => {
btn.disabled = false; btn.disabled = false;
btn.innerHTML = `${translations[currentLang].place_order} <i class="bi bi-send-fill ms-1"></i>`; btn.innerHTML = `<span>${translations[currentLang].place_order}</span> <i class="bi bi-arrow-right-short ms-1"></i>`;
}); });
} }
@ -538,10 +678,7 @@ foreach ($variants_raw as $v) {
timer: 2000, timer: 2000,
timerProgressBar: true timerProgressBar: true
}); });
Toast.fire({ Toast.fire({ icon: 'success', title: msg });
icon: 'success',
title: msg
});
} }
</script> </script>
</body> </body>