diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..9cebde3 --- /dev/null +++ b/admin/index.php @@ -0,0 +1,166 @@ +prepare("SELECT SUM(total_amount) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'"); +$stmt->execute([$today]); +$revenueToday = $stmt->fetchColumn() ?: 0; + +// Total Orders Today +$stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = ?"); +$stmt->execute([$today]); +$ordersToday = $stmt->fetchColumn(); + +// Active Outlets +$outletsCount = $pdo->query("SELECT COUNT(*) FROM outlets")->fetchColumn(); + +// Total Products +$productsCount = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn(); + +// Recent Orders +$recentOrders = $pdo->query("SELECT o.*, + (SELECT GROUP_CONCAT(p.name SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items + FROM orders o ORDER BY created_at DESC LIMIT 5")->fetchAll(); + +include 'includes/header.php'; +?> + +
+
+

Dashboard

+

Welcome back, Admin!

+
+
+ New Order +
+
+ +
+ +
+
+
+
+ +
+
+
Today's Revenue
+

$

+
+
+
+
+ + +
+
+
+
+ +
+
+
Orders Today
+

+
+
+
+
+ + +
+
+
+
+ +
+
+
Active Outlets
+

+
+
+
+
+ + +
+
+
+
+ +
+
+
Total Products
+

+
+
+
+
+
+ + +
+
+
Recent Orders
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IDTypeTable/CustomerTotalStatusDate
# + 'bg-info', + 'delivery' => 'bg-warning', + 'drive-thru' => 'bg-purple', // custom class or just use primary + default => 'bg-secondary' + }; + ?> + + + + Table + + + + $ + + + +
No recent orders found.
+
+
+ +
+ + diff --git a/admin/loyalty.php b/admin/loyalty.php new file mode 100644 index 0000000..9f0557f --- /dev/null +++ b/admin/loyalty.php @@ -0,0 +1,55 @@ +query("SELECT * FROM loyalty_customers ORDER BY points DESC")->fetchAll(); + +include 'includes/header.php'; +?> + +
+

Loyalty Program

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNameEmailPoints BalanceJoined
# + + pts + +
No loyalty members yet.
+
+
+
+ + diff --git a/admin/orders.php b/admin/orders.php new file mode 100644 index 0000000..b8ca633 --- /dev/null +++ b/admin/orders.php @@ -0,0 +1,106 @@ +prepare("UPDATE orders SET status = ? WHERE id = ?"); + $stmt->execute([$new_status, $order_id]); + header("Location: orders.php"); + exit; +} + +$orders = $pdo->query("SELECT o.*, + (SELECT GROUP_CONCAT(CONCAT(p.name, ' x', oi.quantity) SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items_summary + FROM orders o + ORDER BY o.created_at DESC")->fetchAll(); + +include 'includes/header.php'; +?> + +
+

Order Management

+ + Live + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDSourceItemsTotalStatusTimeAction
# + + Table + + + + + + + + +
+ + + + + + + + + + + + - + +
+
+ + No active orders. +
+
+
+
+ + \ No newline at end of file diff --git a/admin/outlet_edit.php b/admin/outlet_edit.php new file mode 100644 index 0000000..f315a90 --- /dev/null +++ b/admin/outlet_edit.php @@ -0,0 +1,75 @@ +prepare("SELECT * FROM outlets WHERE id = ?"); +$stmt->execute([$id]); +$outlet = $stmt->fetch(); + +if (!$outlet) { + header("Location: outlets.php"); + exit; +} + +// Handle Update +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name']; + $address = $_POST['address']; + + if (empty($name)) { + $message = '
Name is required.
'; + } else { + $stmt = $pdo->prepare("UPDATE outlets SET name = ?, address = ? WHERE id = ?"); + if ($stmt->execute([$name, $address, $id])) { + $message = '
Outlet updated successfully!
'; + // Refresh outlet data + $stmt = $pdo->prepare("SELECT * FROM outlets WHERE id = ?"); + $stmt->execute([$id]); + $outlet = $stmt->fetch(); + } else { + $message = '
Error updating outlet.
'; + } + } +} + +include 'includes/header.php'; +?> + +
+ Back to Outlets +

Edit Outlet:

+
+ + + +
+
+
+
+ + +
+
+ + +
+ +
+
+ Cancel + +
+
+
+
+ + diff --git a/admin/outlets.php b/admin/outlets.php new file mode 100644 index 0000000..a63fd6f --- /dev/null +++ b/admin/outlets.php @@ -0,0 +1,89 @@ +prepare("INSERT INTO outlets (name, address) VALUES (?, ?)"); + $stmt->execute([$_POST['name'], $_POST['address']]); + header("Location: outlets.php"); + exit; +} + +if (isset($_GET['delete'])) { + $pdo->prepare("DELETE FROM outlets WHERE id = ?")->execute([$_GET['delete']]); + header("Location: outlets.php"); + exit; +} + +$outlets = $pdo->query("SELECT * FROM outlets ORDER BY id DESC")->fetchAll(); + +include 'includes/header.php'; +?> + +
+

Outlets

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + +
IDNameAddressActions
# + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/admin/product_edit.php b/admin/product_edit.php new file mode 100644 index 0000000..aae8968 --- /dev/null +++ b/admin/product_edit.php @@ -0,0 +1,136 @@ +prepare("SELECT * FROM products WHERE id = ?"); +$stmt->execute([$id]); +$product = $stmt->fetch(); + +if (!$product) { + header("Location: products.php"); + exit; +} + +// Handle Update +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name']; + $category_id = $_POST['category_id']; + $price = $_POST['price']; + $description = $_POST['description']; + $image_url = $product['image_url']; // Default to existing + + // Image handling + if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { + $uploadDir = __DIR__ . '/../assets/images/products/'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + $fileInfo = pathinfo($_FILES['image']['name']); + $fileExt = strtolower($fileInfo['extension']); + $allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + + if (in_array($fileExt, $allowedExts)) { + $fileName = uniqid('prod_') . '.' . $fileExt; + $targetFile = $uploadDir . $fileName; + + if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) { + $image_url = 'assets/images/products/' . $fileName; + } else { + $message = '
Failed to upload image.
'; + } + } else { + $message = '
Invalid file type. Allowed: jpg, png, gif, webp.
'; + } + } + + if (empty($message)) { + $stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, description = ?, image_url = ? WHERE id = ?"); + if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $id])) { + $message = '
Product updated successfully!
'; + // Refresh product data + $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?"); + $stmt->execute([$id]); + $product = $stmt->fetch(); + } else { + $message = '
Error updating product.
'; + } + } +} + +// Fetch Categories +$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll(); + +include 'includes/header.php'; +?> + +
+ Back to Products +

Edit Product:

+
+ + + +
+
+
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ +
+ Product Image +
+
+
+ + +
Leave empty to keep current image.
+
+
+
+
+
+ Cancel + +
+
+
+
+ + \ No newline at end of file diff --git a/admin/product_variants.php b/admin/product_variants.php new file mode 100644 index 0000000..21656ea --- /dev/null +++ b/admin/product_variants.php @@ -0,0 +1,137 @@ +prepare("SELECT * FROM products WHERE id = ?"); +$stmt->execute([$product_id]); +$product = $stmt->fetch(); + +if (!$product) { + header("Location: products.php"); + exit; +} + +// Handle Add Variant +if (isset($_POST['action']) && $_POST['action'] === 'add_variant') { + $name = $_POST['name']; + $price_adj = $_POST['price_adjustment']; + + $stmt = $pdo->prepare("INSERT INTO product_variants (product_id, name, price_adjustment) VALUES (?, ?, ?)"); + $stmt->execute([$product_id, $name, $price_adj]); + header("Location: product_variants.php?product_id=$product_id"); + exit; +} + +// Handle Delete +if (isset($_GET['delete'])) { + $pdo->prepare("DELETE FROM product_variants WHERE id = ?")->execute([$_GET['delete']]); + header("Location: product_variants.php?product_id=$product_id"); + exit; +} + +$variants = $pdo->prepare("SELECT * FROM product_variants WHERE product_id = ? ORDER BY price_adjustment ASC"); +$variants->execute([$product_id]); +$variants = $variants->fetchAll(); + +include 'includes/header.php'; +?> + +
+ Back to Products +
+
+

Variants:

+

Manage sizes, extras, or options.

+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Variant NamePrice AdjustmentFinal Price (Est.)Actions
+ 0): ?> + + + + - + + No change + + + + + +
No variants defined (e.g., Small, Large, Spicy).
+
+
+
+ + + + + \ No newline at end of file diff --git a/admin/products.php b/admin/products.php new file mode 100644 index 0000000..396fea4 --- /dev/null +++ b/admin/products.php @@ -0,0 +1,160 @@ +prepare("INSERT INTO products (name, category_id, price, description, image_url) VALUES (?, ?, ?, ?, ?)"); + if ($stmt->execute([$name, $category_id, $price, $description, $image_url])) { + $message = '
Product added successfully!
'; + } else { + $message = '
Error adding product.
'; + } +} + +// Handle Delete +if (isset($_GET['delete'])) { + $id = $_GET['delete']; + $pdo->prepare("DELETE FROM products WHERE id = ?")->execute([$id]); + header("Location: products.php"); + exit; +} + +// Fetch Products with Category Name +$products = $pdo->query("SELECT p.*, c.name as category_name + FROM products p + LEFT JOIN categories c ON p.category_id = c.id + ORDER BY p.id DESC")->fetchAll(); + +// Fetch Categories for Dropdown +$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll(); + +include 'includes/header.php'; +?> + +
+

Products

+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ImageNameCategoryPriceActions
+ + +
+ ... +
+
+ + + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/admin/tables.php b/admin/tables.php new file mode 100644 index 0000000..b9dc2e6 --- /dev/null +++ b/admin/tables.php @@ -0,0 +1,113 @@ +prepare("INSERT INTO tables (area_id, name, capacity) VALUES (?, ?, ?)"); + $stmt->execute([$_POST['area_id'], $_POST['name'], $_POST['capacity']]); + header("Location: tables.php"); + exit; +} + +if (isset($_GET['delete'])) { + $pdo->prepare("DELETE FROM tables WHERE id = ?")->execute([$_GET['delete']]); + header("Location: tables.php"); + exit; +} + +// Fetch tables with area names +$tables = $pdo->query(" + SELECT tables.*, areas.name as area_name + FROM tables + LEFT JOIN areas ON tables.area_id = areas.id + ORDER BY tables.id DESC +")->fetchAll(); + +// Fetch areas for dropdown +$areas = $pdo->query("SELECT id, name FROM areas ORDER BY name ASC")->fetchAll(); + +include 'includes/header.php'; +?> + +
+

Tables

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNameAreaCapacityActions
# pax + +
No tables found. Add one to get started.
+
+
+
+ + + + + diff --git a/api/order.php b/api/order.php new file mode 100644 index 0000000..8b6ddaa --- /dev/null +++ b/api/order.php @@ -0,0 +1,32 @@ + false, 'error' => 'No data provided']); + exit; +} + +try { + $pdo = db(); + $pdo->beginTransaction(); + + $stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_number, total_amount, status) VALUES (?, ?, ?, 'pending')"); + $stmt->execute([1, $data['table_number'] ?? '1', $data['total_amount']]); + $order_id = $pdo->lastInsertId(); + + $item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)"); + foreach ($data['items'] as $item) { + $item_stmt->execute([$order_id, $item['id'], $item['quantity'], $item['price']]); + } + + $pdo->commit(); + echo json_encode(['success' => true, 'order_id' => $order_id]); +} catch (Exception $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/assets/css/custom.css b/assets/css/custom.css index 50e0502..75a86e2 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,302 +1,165 @@ +:root { + --primary-color: #1A1A1A; + --accent-color: #E63946; + --secondary-bg: #F5F5F5; + --text-primary: #1A1A1A; + --text-secondary: #666666; + --white: #FFFFFF; + --border-radius: 8px; + --shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: var(--secondary-bg); + color: var(--text-primary); + line-height: 1.5; + margin: 0; + padding: 0; } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; +.navbar { + background-color: var(--white); + border-bottom: 1px solid #EAEAEA; + padding: 1rem 0; } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +.brand-logo { + font-weight: 700; + font-size: 1.5rem; + color: var(--primary-color); + text-decoration: none; + letter-spacing: -0.5px; } -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; +.menu-category-title { + font-weight: 700; + margin: 2rem 0 1rem; + font-size: 1.25rem; + border-bottom: 2px solid var(--accent-color); + display: inline-block; } -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; +.product-card { + background: var(--white); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: var(--shadow); + transition: transform 0.2s ease; + border: 1px solid #EAEAEA; + height: 100%; } -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; +.product-card:hover { + transform: translateY(-4px); } -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; +.product-image { + width: 100%; + height: 180px; + background-color: #EEEEEE; + object-fit: cover; } -::-webkit-scrollbar-track { - background: transparent; +.product-info { + padding: 1.25rem; } -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; +.product-name { + font-weight: 600; + font-size: 1.1rem; + margin-bottom: 0.5rem; } -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); +.product-desc { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 1rem; + min-height: 3rem; } -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +.product-price { + font-weight: 700; + color: var(--accent-color); + font-size: 1.1rem; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } +.btn-add { + background-color: var(--primary-color); + color: var(--white); + border-radius: var(--border-radius); + padding: 0.5rem 1rem; + font-weight: 600; + border: none; } -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; +.btn-add:hover { + background-color: #333333; } -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; +.cart-sidebar { + background: var(--white); + height: 100vh; + position: sticky; + top: 0; + border-left: 1px solid #EAEAEA; + padding: 2rem; + display: flex; + flex-direction: column; } -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); +.cart-item { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid #EEEEEE; } -.chat-input-area form { - display: flex; - gap: 0.75rem; +.cart-item-name { + font-weight: 600; + font-size: 0.9rem; } -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; +.cart-item-price { + font-weight: 700; + font-size: 0.9rem; } -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); +.cart-total { + margin-top: auto; + padding-top: 1rem; + border-top: 2px solid var(--primary-color); + font-weight: 700; + font-size: 1.25rem; + display: flex; + justify-content: space-between; } -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.admin-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.admin-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; +.order-badge { + background: var(--accent-color); + color: white; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 700; } /* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; +.admin-table-container { + background: var(--white); + border-radius: var(--border-radius); + box-shadow: var(--shadow); + padding: 1.5rem; } -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; +.status-pending { color: #FFA500; font-weight: 600; } +.status-preparing { color: #007BFF; font-weight: 600; } +.status-ready { color: #28A745; font-weight: 600; } + +.toast-container { + position: fixed; + bottom: 2rem; + right: 2rem; + z-index: 1050; } - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; -} - -.table td { - background: #fff; - padding: 1rem; - border: none; -} - -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index d349598..5e238e6 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,131 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); + let cart = []; + const cartItemsContainer = document.getElementById('cart-items'); + const cartTotalPrice = document.getElementById('cart-total-price'); + const checkoutBtn = document.getElementById('checkout-btn'); + const mobileCartBtn = document.getElementById('mobile-cart-btn'); - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - }; + // Helper for currency formatting + function formatCurrency(amount) { + // Fallback if settings not defined + const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 }; + const symbol = settings.currency_symbol || '$'; + const decimals = parseInt(settings.currency_decimals || 2); + return symbol + parseFloat(amount).toFixed(decimals); + } - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; + // Add to cart + document.querySelectorAll('.add-to-cart').forEach(button => { + button.addEventListener('click', (e) => { + const product = { + id: e.target.dataset.id, + name: e.target.dataset.name, + price: parseFloat(e.target.dataset.price), + quantity: 1 + }; - appendMessage(message, 'visitor'); - chatInput.value = ''; + const existing = cart.find(item => item.id === product.id); + if (existing) { + existing.quantity++; + } else { + cart.push(product); + } - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); - } + updateCart(); + showToast(`${product.name} added to cart!`); + }); }); -}); + + function updateCart() { + if (cart.length === 0) { + cartItemsContainer.innerHTML = '

Your cart is empty.

'; + cartTotalPrice.innerText = formatCurrency(0); + checkoutBtn.disabled = true; + if (mobileCartBtn) mobileCartBtn.innerText = `View Cart (${formatCurrency(0)})`; + return; + } + + cartItemsContainer.innerHTML = ''; + let total = 0; + cart.forEach(item => { + const itemTotal = item.price * item.quantity; + total += itemTotal; + const div = document.createElement('div'); + div.className = 'cart-item'; + div.innerHTML = ` +
+
${item.name} x${item.quantity}
+
${formatCurrency(item.price)} ea
+
+
${formatCurrency(itemTotal)}
+ `; + cartItemsContainer.appendChild(div); + }); + + cartTotalPrice.innerText = formatCurrency(total); + checkoutBtn.disabled = false; + if (mobileCartBtn) mobileCartBtn.innerText = `View Cart (${formatCurrency(total)})`; + } + + // Checkout + checkoutBtn.addEventListener('click', () => { + if (cart.length === 0) return; + + const table_id = new URLSearchParams(window.location.search).get('table') || '1'; + + // Calculate total + const totalAmount = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0); + + const orderData = { + table_number: table_id, + total_amount: totalAmount, + // Only send necessary fields + items: cart.map(item => ({ + product_id: item.id, + quantity: item.quantity, + unit_price: item.price + })) + }; + + // Use existing api/order.php + fetch('api/order.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(orderData) + }) + .then(res => res.json()) + .then(data => { + if (data.success) { + cart = []; + updateCart(); + showToast('Order placed successfully! Please wait for preparation.', 'success'); + } else { + showToast('Error: ' + (data.error || 'Unknown error'), 'danger'); + } + }) + .catch(err => { + console.error(err); + showToast('Failed to place order.', 'danger'); + }); + }); + + function showToast(message, type = 'dark') { + const toastContainer = document.getElementById('toast-container'); + if (!toastContainer) return; + + const toastId = 'toast-' + Date.now(); + const toastHtml = ` + + `; + toastContainer.insertAdjacentHTML('beforeend', toastHtml); + const toastElement = document.getElementById(toastId); + const toast = new bootstrap.Toast(toastElement); + toast.show(); + toastElement.addEventListener('hidden.bs.toast', () => toastElement.remove()); + } +}); \ No newline at end of file diff --git a/db/company_settings.sql b/db/company_settings.sql new file mode 100644 index 0000000..6c0a5b9 --- /dev/null +++ b/db/company_settings.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS company_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + company_name VARCHAR(255) NOT NULL DEFAULT 'My Restaurant', + address TEXT, + phone VARCHAR(50), + email VARCHAR(255), + vat_rate DECIMAL(5, 2) DEFAULT 0.00, + currency_symbol VARCHAR(10) DEFAULT '$', + currency_decimals INT DEFAULT 2, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +INSERT INTO company_settings (company_name, address, phone, vat_rate, currency_symbol, currency_decimals) +SELECT 'My Restaurant', '123 Food Street', '555-0199', 10.00, '$', 2 +WHERE NOT EXISTS (SELECT 1 FROM company_settings); diff --git a/db/config.php b/db/config.php index a5ac9dd..827298c 100644 --- a/db/config.php +++ b/db/config.php @@ -15,3 +15,5 @@ function db() { } return $pdo; } + +require_once __DIR__ . '/../includes/functions.php'; \ No newline at end of file diff --git a/db/init.php b/db/init.php new file mode 100644 index 0000000..9375db9 --- /dev/null +++ b/db/init.php @@ -0,0 +1,39 @@ +exec($sql); + + // Check if data exists + $stmt = $pdo->query("SELECT COUNT(*) FROM outlets"); + if ($stmt->fetchColumn() == 0) { + // Seed Outlets + $pdo->exec("INSERT INTO outlets (name, address) VALUES ('Main Downtown', '123 Main St'), ('Westside Hub', '456 West Blvd')"); + + // Seed Categories + $pdo->exec("INSERT INTO categories (name, sort_order) VALUES ('Burgers', 1), ('Sides', 2), ('Drinks', 3)"); + + // Seed Products + $pdo->exec("INSERT INTO products (category_id, name, description, price) VALUES + (1, 'Signature Burger', 'Juicy beef patty with special sauce', 12.99), + (1, 'Veggie Delight', 'Plant-based patty with fresh avocado', 11.50), + (2, 'Truffle Fries', 'Crispy fries with truffle oil and parmesan', 5.99), + (3, 'Craft Cola', 'House-made sparkling cola', 3.50)"); + + // Seed Variants + $pdo->exec("INSERT INTO product_variants (product_id, name, price_adjustment) VALUES + (1, 'Double Patty', 4.00), + (1, 'Extra Cheese', 1.00), + (3, 'Large Portion', 2.00)"); + + echo "Database initialized and seeded successfully."; + } else { + echo "Database already initialized."; + } +} catch (Exception $e) { + echo "Error: " . $e->getMessage(); +} diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..78d2335 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,82 @@ +CREATE TABLE IF NOT EXISTS outlets ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + address TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + sort_order INT DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS products ( + id INT AUTO_INCREMENT PRIMARY KEY, + category_id INT, + name VARCHAR(255) NOT NULL, + description TEXT, + price DECIMAL(10, 2) NOT NULL, + image_url VARCHAR(255), + FOREIGN KEY (category_id) REFERENCES categories(id) +); + +CREATE TABLE IF NOT EXISTS product_variants ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT, + name VARCHAR(255) NOT NULL, + price_adjustment DECIMAL(10, 2) DEFAULT 0.00, + FOREIGN KEY (product_id) REFERENCES products(id) +); + +CREATE TABLE IF NOT EXISTS areas ( + id INT AUTO_INCREMENT PRIMARY KEY, + outlet_id INT, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (outlet_id) REFERENCES outlets(id) +); + +CREATE TABLE IF NOT EXISTS tables ( + id INT AUTO_INCREMENT PRIMARY KEY, + area_id INT, + name VARCHAR(50) NOT NULL, + capacity INT DEFAULT 4, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (area_id) REFERENCES areas(id) +); + +CREATE TABLE IF NOT EXISTS orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + outlet_id INT, + table_id INT, + table_number VARCHAR(50), + order_type ENUM('dine-in', 'delivery', 'drive-thru') DEFAULT 'dine-in', + status ENUM('pending', 'preparing', 'ready', 'completed', 'cancelled') DEFAULT 'pending', + total_amount DECIMAL(10, 2) NOT NULL, + customer_name VARCHAR(255), + customer_phone VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (outlet_id) REFERENCES outlets(id), + FOREIGN KEY (table_id) REFERENCES tables(id) +); + +CREATE TABLE IF NOT EXISTS order_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + order_id INT, + product_id INT, + variant_id INT, + quantity INT NOT NULL, + unit_price DECIMAL(10, 2) NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) +); + +-- Loyalty +CREATE TABLE IF NOT EXISTS loyalty_customers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255), + email VARCHAR(255) UNIQUE, + points INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..81309a2 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,34 @@ +query("SELECT * FROM company_settings LIMIT 1"); + $settings = $stmt->fetch(PDO::FETCH_ASSOC); + } catch (Exception $e) { + // Log error or ignore if table doesn't exist yet + } + + // Default values if no settings found + if (!$settings) { + $settings = [ + 'company_name' => 'My Restaurant', + 'address' => '123 Food Street', + 'phone' => '555-0199', + 'email' => 'info@restaurant.com', + 'vat_rate' => 0.00, + 'currency_symbol' => '$', + 'currency_decimals' => 2 + ]; + } + } + return $settings; +} + +// Function to format currency using settings +function format_currency($amount) { + $settings = get_company_settings(); + return $settings['currency_symbol'] . number_format((float)$amount, (int)$settings['currency_decimals']); +} diff --git a/index.php b/index.php index 7205f3d..8cfa83b 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,101 @@ query("SELECT * FROM categories ORDER BY sort_order")->fetchAll(); +$all_products = $pdo->query("SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll(); + +$table_id = $_GET['table'] ?? '1'; // Default table +$outlet_id = 1; // Main outlet +$settings = get_company_settings(); ?> - New Style - - - - - - - - - - - - - - - + Menu - <?= htmlspecialchars($settings['company_name']) ?> + - - + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… +
-
- Page updated: (UTC) -
+ + +
+
+ +
+

Order Now

+ + + +
+ +
+
+ <?= htmlspecialchars($product['name']) ?> +
+

+

+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+

Your Order

+
+

Your cart is empty.

+
+
+ Total + +
+ +
+
+
+
+ + +
+ +
+ +
+ + + + - + \ No newline at end of file