Autosave: 20260222-101338
This commit is contained in:
parent
38e82cb192
commit
3ce9ce30e6
107
admin/areas.php
Normal file
107
admin/areas.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_area') {
|
||||
$stmt = $pdo->prepare("INSERT INTO areas (outlet_id, name) VALUES (?, ?)");
|
||||
$stmt->execute([$_POST['outlet_id'], $_POST['name']]);
|
||||
header("Location: areas.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
$pdo->prepare("DELETE FROM areas WHERE id = ?")->execute([$_GET['delete']]);
|
||||
header("Location: areas.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fetch areas with outlet names
|
||||
$areas = $pdo->query("
|
||||
SELECT areas.*, outlets.name as outlet_name
|
||||
FROM areas
|
||||
LEFT JOIN outlets ON areas.outlet_id = outlets.id
|
||||
ORDER BY areas.id DESC
|
||||
")->fetchAll();
|
||||
|
||||
// Fetch outlets for dropdown
|
||||
$outlets = $pdo->query("SELECT id, name FROM outlets ORDER BY name ASC")->fetchAll();
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Areas</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addAreaModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Area
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Outlet</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($areas as $area): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $area['id'] ?></td>
|
||||
<td class="fw-bold"><?= htmlspecialchars($area['name']) ?></td>
|
||||
<td><span class="badge bg-info text-dark"><?= htmlspecialchars($area['outlet_name'] ?? 'N/A') ?></span></td>
|
||||
<td>
|
||||
<a href="?delete=<?= $area['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this area?')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($areas)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">No areas found. Add one to get started.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Area Modal -->
|
||||
<div class="modal fade" id="addAreaModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Area</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_area">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="e.g. Main Hall, Patio" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Outlet</label>
|
||||
<select name="outlet_id" class="form-select" required>
|
||||
<option value="">Select Outlet</option>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<option value="<?= $outlet['id'] ?>"><?= htmlspecialchars($outlet['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Area</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
82
admin/categories.php
Normal file
82
admin/categories.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'add_category') {
|
||||
$stmt = $pdo->prepare("INSERT INTO categories (name) VALUES (?)");
|
||||
$stmt->execute([$_POST['name']]);
|
||||
header("Location: categories.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['delete'])) {
|
||||
$pdo->prepare("DELETE FROM categories WHERE id = ?")->execute([$_GET['delete']]);
|
||||
header("Location: categories.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll();
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Categories</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Category
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $cat['id'] ?></td>
|
||||
<td><?= htmlspecialchars($cat['name']) ?></td>
|
||||
<td>
|
||||
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure? This might break products linked to this category.')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Category Modal -->
|
||||
<div class="modal fade" id="addCategoryModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Category</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_category">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Category</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
107
admin/company.php
Normal file
107
admin/company.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$message = '';
|
||||
$settings = get_company_settings();
|
||||
|
||||
// Handle Update
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$company_name = $_POST['company_name'] ?? '';
|
||||
$address = $_POST['address'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$vat_rate = $_POST['vat_rate'] ?? 0;
|
||||
$currency_symbol = $_POST['currency_symbol'] ?? '$';
|
||||
$currency_decimals = $_POST['currency_decimals'] ?? 2;
|
||||
|
||||
try {
|
||||
// Check if row exists (it should, from our functions.php logic or migration)
|
||||
$exists = $pdo->query("SELECT COUNT(*) FROM company_settings")->fetchColumn();
|
||||
|
||||
if ($exists) {
|
||||
$stmt = $pdo->prepare("UPDATE company_settings SET company_name=?, address=?, phone=?, email=?, vat_rate=?, currency_symbol=?, currency_decimals=?, updated_at=NOW()");
|
||||
$stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO company_settings (company_name, address, phone, email, vat_rate, currency_symbol, currency_decimals) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals]);
|
||||
}
|
||||
|
||||
$message = '<div class="alert alert-success">Company settings updated successfully!</div>';
|
||||
// Refresh settings
|
||||
$settings = [
|
||||
'company_name' => $company_name,
|
||||
'address' => $address,
|
||||
'phone' => $phone,
|
||||
'email' => $email,
|
||||
'vat_rate' => $vat_rate,
|
||||
'currency_symbol' => $currency_symbol,
|
||||
'currency_decimals' => $currency_decimals
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$message = '<div class="alert alert-danger">Error updating settings: ' . htmlspecialchars($e->getMessage()) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Company Profile</h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Company Name</label>
|
||||
<input type="text" name="company_name" class="form-control" value="<?= htmlspecialchars($settings['company_name'] ?? '') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($settings['email'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars($settings['phone'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($settings['address'] ?? '') ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
<h5 class="mb-3">Financial Settings</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">VAT Rate (%)</label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" name="vat_rate" class="form-control" value="<?= htmlspecialchars($settings['vat_rate'] ?? 0) ?>">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Currency Symbol</label>
|
||||
<input type="text" name="currency_symbol" class="form-control" value="<?= htmlspecialchars($settings['currency_symbol'] ?? '$') ?>" placeholder="e.g. $, €, £">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Decimal Places</label>
|
||||
<input type="number" name="currency_decimals" class="form-control" value="<?= htmlspecialchars($settings['currency_decimals'] ?? 2) ?>" min="0" max="4">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
5
admin/includes/footer.php
Normal file
5
admin/includes/footer.php
Normal file
@ -0,0 +1,5 @@
|
||||
</div> <!-- End Main Content -->
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
116
admin/includes/header.php
Normal file
116
admin/includes/header.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
// admin/includes/header.php
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
// Simple active link helper
|
||||
function isActive($page) {
|
||||
return basename($_SERVER['PHP_SELF']) === $page ? 'active' : '';
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Foody Admin Panel</title>
|
||||
<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.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../assets/css/custom.css?v=<?= time() ?>">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; background-color: #f8f9fa; }
|
||||
.sidebar { height: 100vh; position: fixed; top: 0; left: 0; width: 250px; background: #fff; border-right: 1px solid #eee; padding-top: 1rem; z-index: 1000; }
|
||||
.sidebar .nav-link { color: #555; padding: 0.75rem 1.5rem; font-weight: 500; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { color: #FF6B6B; background: #FFF0F0; border-right: 3px solid #FF6B6B; }
|
||||
.main-content { margin-left: 250px; padding: 2rem; }
|
||||
@media (max-width: 768px) {
|
||||
.sidebar { transform: translateX(-100%); transition: transform 0.3s ease; }
|
||||
.sidebar.show { transform: translateX(0); }
|
||||
.main-content { margin-left: 0; }
|
||||
}
|
||||
.stat-card { border: none; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.04); transition: transform 0.2s; }
|
||||
.stat-card:hover { transform: translateY(-3px); }
|
||||
.icon-box { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Mobile Header -->
|
||||
<nav class="navbar navbar-light bg-white border-bottom d-md-none fixed-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold text-dark" href="index.php">Foody<span class="text-primary">Admin</span></a>
|
||||
<button class="navbar-toggler border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="offcanvas-md offcanvas-start sidebar" tabindex="-1" id="sidebarMenu">
|
||||
<div class="d-flex align-items-center justify-content-between px-4 pb-3 border-bottom d-none d-md-flex">
|
||||
<a href="index.php" class="text-decoration-none">
|
||||
<h4 class="fw-bold m-0 text-dark">Foody<span style="color: #FF6B6B;">.</span></h4>
|
||||
</a>
|
||||
</div>
|
||||
<div class="px-4 py-3 d-md-none">
|
||||
<h5 class="fw-bold">Menu</h5>
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column mt-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('index.php') ?>" href="index.php">
|
||||
<i class="bi bi-grid me-2"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('orders.php') ?>" href="orders.php">
|
||||
<i class="bi bi-receipt me-2"></i> Orders (POS)
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('products.php') ?>" href="products.php">
|
||||
<i class="bi bi-box-seam me-2"></i> Products
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('categories.php') ?>" href="categories.php">
|
||||
<i class="bi bi-tags me-2"></i> Categories
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('outlets.php') ?>" href="outlets.php">
|
||||
<i class="bi bi-shop me-2"></i> Outlets
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('areas.php') ?>" href="areas.php">
|
||||
<i class="bi bi-geo-alt me-2"></i> Areas
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('tables.php') ?>" href="tables.php">
|
||||
<i class="bi bi-ui-checks-grid me-2"></i> Tables
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('loyalty.php') ?>" href="loyalty.php">
|
||||
<i class="bi bi-people me-2"></i> Loyalty
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= isActive('company.php') ?>" href="company.php">
|
||||
<i class="bi bi-building me-2"></i> Company
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-4 border-top pt-2">
|
||||
<a class="nav-link text-muted" href="../index.php" target="_blank">
|
||||
<i class="bi bi-box-arrow-up-right me-2"></i> View Site
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content pt-5 pt-md-4">
|
||||
166
admin/index.php
Normal file
166
admin/index.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch Dashboard Stats
|
||||
$today = date('Y-m-d');
|
||||
|
||||
// Total Revenue Today
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1">Dashboard</h2>
|
||||
<p class="text-muted">Welcome back, Admin!</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="orders.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Revenue Card -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card h-100 p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-box bg-success bg-opacity-10 text-success me-3">
|
||||
<i class="bi bi-currency-dollar"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Today's Revenue</h6>
|
||||
<h3 class="fw-bold mb-0">$<?= number_format($revenueToday, 2) ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders Card -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card h-100 p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-box bg-primary bg-opacity-10 text-primary me-3">
|
||||
<i class="bi bi-receipt"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Orders Today</h6>
|
||||
<h3 class="fw-bold mb-0"><?= $ordersToday ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outlets Card -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card h-100 p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-box bg-warning bg-opacity-10 text-warning me-3">
|
||||
<i class="bi bi-shop"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Active Outlets</h6>
|
||||
<h3 class="fw-bold mb-0"><?= $outletsCount ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Card -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card h-100 p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-box bg-info bg-opacity-10 text-info me-3">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Total Products</h6>
|
||||
<h3 class="fw-bold mb-0"><?= $productsCount ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Orders Table -->
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<h5 class="mb-0 fw-bold">Recent Orders</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Type</th>
|
||||
<th>Table/Customer</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $order['id'] ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$badge = match($order['order_type']) {
|
||||
'dine-in' => 'bg-info',
|
||||
'delivery' => 'bg-warning',
|
||||
'drive-thru' => 'bg-purple', // custom class or just use primary
|
||||
default => 'bg-secondary'
|
||||
};
|
||||
?>
|
||||
<span class="badge <?= $badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $badge) ?>"><?= ucfirst($order['order_type']) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($order['table_number']): ?>
|
||||
Table <?= htmlspecialchars($order['table_number']) ?>
|
||||
<?php else: ?>
|
||||
<?= htmlspecialchars($order['customer_name'] ?? 'Guest') ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="fw-bold">$<?= number_format((float)$order['total_amount'], 2) ?></td>
|
||||
<td>
|
||||
<span class="status-badge status-<?= $order['status'] ?> badge rounded-pill">
|
||||
<?= ucfirst($order['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('M d, H:i', strtotime($order['created_at'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted">No recent orders found.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white text-center py-3">
|
||||
<a href="orders.php" class="text-decoration-none fw-medium">View All Orders</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
55
admin/loyalty.php
Normal file
55
admin/loyalty.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$customers = $pdo->query("SELECT * FROM loyalty_customers ORDER BY points DESC")->fetchAll();
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Loyalty Program</h2>
|
||||
<button class="btn btn-outline-primary" disabled>
|
||||
<i class="bi bi-gear"></i> Settings (Coming Soon)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Points Balance</th>
|
||||
<th>Joined</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($customers as $customer): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $customer['id'] ?></td>
|
||||
<td class="fw-bold"><?= htmlspecialchars($customer['name']) ?></td>
|
||||
<td><a href="mailto:<?= htmlspecialchars($customer['email']) ?>" class="text-decoration-none"><?= htmlspecialchars($customer['email']) ?></a></td>
|
||||
<td>
|
||||
<span class="badge bg-warning text-dark border border-warning">
|
||||
<i class="bi bi-star-fill small me-1"></i> <?= $customer['points'] ?> pts
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('M d, Y', strtotime($customer['created_at'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($customers)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5 text-muted">No loyalty members yet.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
106
admin/orders.php
Normal file
106
admin/orders.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'update_status') {
|
||||
$order_id = $_POST['order_id'];
|
||||
$new_status = $_POST['status'];
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Order Management</h2>
|
||||
<span class="badge bg-success bg-opacity-10 text-success border border-success px-3 py-2 rounded-pill">
|
||||
<i class="bi bi-circle-fill small me-1"></i> Live
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Source</th>
|
||||
<th>Items</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Time</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $order['id'] ?></td>
|
||||
<td>
|
||||
<?php if ($order['table_number']): ?>
|
||||
<span class="badge bg-secondary">Table <?= htmlspecialchars($order['table_number']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-info text-dark"><?= ucfirst($order['order_type']) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><small class="text-muted"><?= htmlspecialchars($order['items_summary']) ?></small></td>
|
||||
<td class="fw-bold"><?= format_currency($order['total_amount']) ?></td>
|
||||
<td>
|
||||
<span class="badge rounded-pill status-<?= $order['status'] ?>">
|
||||
<?= ucfirst($order['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('H:i', strtotime($order['created_at'])) ?></td>
|
||||
<td>
|
||||
<form method="POST" class="d-flex gap-2">
|
||||
<input type="hidden" name="order_id" value="<?= $order['id'] ?>">
|
||||
<input type="hidden" name="action" value="update_status">
|
||||
|
||||
<?php if ($order['status'] === 'pending'): ?>
|
||||
<button type="submit" name="status" value="preparing" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-play-fill"></i> Start
|
||||
</button>
|
||||
<button type="submit" name="status" value="cancelled" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
<?php elseif ($order['status'] === 'preparing'): ?>
|
||||
<button type="submit" name="status" value="ready" class="btn btn-sm btn-warning text-dark">
|
||||
<i class="bi bi-check-circle"></i> Ready
|
||||
</button>
|
||||
<?php elseif ($order['status'] === 'ready'): ?>
|
||||
<button type="submit" name="status" value="completed" class="btn btn-sm btn-success">
|
||||
<i class="bi bi-check-all"></i> Complete
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">-</span>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($orders)): ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
||||
No active orders.
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
75
admin/outlet_edit.php
Normal file
75
admin/outlet_edit.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header("Location: outlets.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
$message = '';
|
||||
|
||||
// Fetch Outlet
|
||||
$stmt = $pdo->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 = '<div class="alert alert-danger">Name is required.</div>';
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE outlets SET name = ?, address = ? WHERE id = ?");
|
||||
if ($stmt->execute([$name, $address, $id])) {
|
||||
$message = '<div class="alert alert-success">Outlet updated successfully!</div>';
|
||||
// Refresh outlet data
|
||||
$stmt = $pdo->prepare("SELECT * FROM outlets WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$outlet = $stmt->fetch();
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating outlet.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="outlets.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Outlets</a>
|
||||
<h2 class="fw-bold mb-0">Edit Outlet: <?= htmlspecialchars($outlet['name']) ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($outlet['name']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($outlet['address']) ?></textarea>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="outlets.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
89
admin/outlets.php
Normal file
89
admin/outlets.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (isset($_POST['action']) && $_POST['action'] === 'add_outlet') {
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Outlets</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addOutletModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Outlet
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($outlets as $outlet): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $outlet['id'] ?></td>
|
||||
<td class="fw-bold"><?= htmlspecialchars($outlet['name']) ?></td>
|
||||
<td><small class="text-muted"><?= htmlspecialchars($outlet['address']) ?></small></td>
|
||||
<td>
|
||||
<a href="outlet_edit.php?id=<?= $outlet['id'] ?>" class="btn btn-sm btn-outline-secondary me-1"><i class="bi bi-pencil"></i></a>
|
||||
<a href="?delete=<?= $outlet['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this outlet?')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Outlet Modal -->
|
||||
<div class="modal fade" id="addOutletModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Outlet</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_outlet">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Address</label>
|
||||
<textarea name="address" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Outlet</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
136
admin/product_edit.php
Normal file
136
admin/product_edit.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header("Location: products.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
$message = '';
|
||||
|
||||
// Fetch Product
|
||||
$stmt = $pdo->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 = '<div class="alert alert-danger">Failed to upload image.</div>';
|
||||
}
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Invalid file type. Allowed: jpg, png, gif, webp.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
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 = '<div class="alert alert-success">Product updated successfully!</div>';
|
||||
// Refresh product data
|
||||
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$product = $stmt->fetch();
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error updating product.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Categories
|
||||
$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="products.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Products</a>
|
||||
<h2 class="fw-bold mb-0">Edit Product: <?= htmlspecialchars($product['name']) ?></h2>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($product['name']) ?>" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select name="category_id" class="form-select" required>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $cat['id'] == $product['category_id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($cat['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Price ($)</label>
|
||||
<input type="number" step="0.01" name="price" class="form-control" value="<?= htmlspecialchars($product['price']) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="5"><?= htmlspecialchars($product['description']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Current Image</label>
|
||||
<div class="mb-2">
|
||||
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" class="img-fluid rounded border" alt="Product Image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Upload New Image</label>
|
||||
<input type="file" name="image" class="form-control" accept="image/*">
|
||||
<div class="form-text">Leave empty to keep current image.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="products.php" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
137
admin/product_variants.php
Normal file
137
admin/product_variants.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if (!isset($_GET['product_id'])) {
|
||||
header("Location: products.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$product_id = $_GET['product_id'];
|
||||
|
||||
// Fetch Product Details
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="products.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Products</a>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-0">Variants: <?= htmlspecialchars($product['name']) ?></h2>
|
||||
<p class="text-muted mb-0">Manage sizes, extras, or options.</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addVariantModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Variant
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">Variant Name</th>
|
||||
<th>Price Adjustment</th>
|
||||
<th>Final Price (Est.)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($variants as $variant): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium"><?= htmlspecialchars($variant['name']) ?></td>
|
||||
<td>
|
||||
<?php if ($variant['price_adjustment'] > 0): ?>
|
||||
<span class="text-danger">+ <?= format_currency($variant['price_adjustment']) ?></span>
|
||||
<?php elseif ($variant['price_adjustment'] < 0): ?>
|
||||
<span class="text-success">- <?= format_currency(abs((float)$variant['price_adjustment'])) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">No change</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted">
|
||||
<?= format_currency($product['price'] + $variant['price_adjustment']) ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="?product_id=<?= $product_id ?>&delete=<?= $variant['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this variant?')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($variants)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5 text-muted">No variants defined (e.g., Small, Large, Spicy).</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Variant Modal -->
|
||||
<div class="modal fade" id="addVariantModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Variant</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_variant">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Variant Name (e.g., Large, Extra Cheese)</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price Adjustment (+/-)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" name="price_adjustment" class="form-control" value="0.00" required>
|
||||
</div>
|
||||
<div class="form-text">Enter positive value for extra cost, negative for discount.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Variant</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
160
admin/products.php
Normal file
160
admin/products.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$message = '';
|
||||
|
||||
// Handle Add Product
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_product') {
|
||||
$name = $_POST['name'];
|
||||
$category_id = $_POST['category_id'];
|
||||
$price = $_POST['price'];
|
||||
$description = $_POST['description'];
|
||||
|
||||
// Image handling
|
||||
$image_url = 'https://placehold.co/400x300?text=' . urlencode($name);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, description, image_url) VALUES (?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $category_id, $price, $description, $image_url])) {
|
||||
$message = '<div class="alert alert-success">Product added successfully!</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error adding product.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Products</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Product
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?= $message ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">Image</th>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" alt="" class="rounded" width="48" height="48" style="object-fit: cover;">
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold"><?= htmlspecialchars($product['name']) ?></div>
|
||||
<small class="text-muted"><?= htmlspecialchars(substr($product['description'] ?? '', 0, 50)) ?>...</small>
|
||||
</td>
|
||||
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span></td>
|
||||
<td class="fw-bold"><?= format_currency($product['price']) ?></td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a href="product_edit.php?id=<?= $product['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Product"><i class="bi bi-pencil"></i></a>
|
||||
<a href="product_variants.php?product_id=<?= $product['id'] ?>" class="btn btn-sm btn-outline-secondary" title="Manage Variants"><i class="bi bi-gear"></i></a>
|
||||
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')" title="Delete"><i class="bi bi-trash"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Product Modal -->
|
||||
<div class="modal fade" id="addProductModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New Product</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_product">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select name="category_id" class="form-select" required>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price ($)</label>
|
||||
<input type="number" step="0.01" name="price" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Product Image</label>
|
||||
<input type="file" name="image" class="form-control" accept="image/*">
|
||||
<div class="form-text">Leave empty to use a placeholder.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Product</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
113
admin/tables.php
Normal file
113
admin/tables.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_table') {
|
||||
$stmt = $pdo->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';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Tables</h2>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addTableModal">
|
||||
<i class="bi bi-plus-lg"></i> Add Table
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Area</th>
|
||||
<th>Capacity</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($tables as $table): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $table['id'] ?></td>
|
||||
<td class="fw-bold"><?= htmlspecialchars($table['name']) ?></td>
|
||||
<td><span class="badge bg-secondary"><?= htmlspecialchars($table['area_name'] ?? 'N/A') ?></span></td>
|
||||
<td><?= htmlspecialchars($table['capacity']) ?> pax</td>
|
||||
<td>
|
||||
<a href="?delete=<?= $table['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this table?')"><i class="bi bi-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($tables)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">No tables found. Add one to get started.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Table Modal -->
|
||||
<div class="modal fade" id="addTableModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Table</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="action" value="add_table">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Table Name/Number</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="e.g. T1, Window 5" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Area</label>
|
||||
<select name="area_id" class="form-select" required>
|
||||
<option value="">Select Area</option>
|
||||
<?php foreach ($areas as $area): ?>
|
||||
<option value="<?= $area['id'] ?>"><?= htmlspecialchars($area['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Capacity (Pax)</label>
|
||||
<input type="number" name="capacity" class="form-control" value="4" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save Table</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
32
api/order.php
Normal file
32
api/order.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(['success' => 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()]);
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
.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);
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
z-index: 1050;
|
||||
}
|
||||
@ -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 = '<p class="text-center text-muted mt-5">Your cart is empty.</p>';
|
||||
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 = `
|
||||
<div>
|
||||
<div class="cart-item-name">${item.name} x${item.quantity}</div>
|
||||
<div class="text-muted small">${formatCurrency(item.price)} ea</div>
|
||||
</div>
|
||||
<div class="cart-item-price">${formatCurrency(itemTotal)}</div>
|
||||
`;
|
||||
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 = `
|
||||
<div id="${toastId}" class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
||||
const toastElement = document.getElementById(toastId);
|
||||
const toast = new bootstrap.Toast(toastElement);
|
||||
toast.show();
|
||||
toastElement.addEventListener('hidden.bs.toast', () => toastElement.remove());
|
||||
}
|
||||
});
|
||||
15
db/company_settings.sql
Normal file
15
db/company_settings.sql
Normal file
@ -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);
|
||||
@ -15,3 +15,5 @@ function db() {
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
39
db/init.php
Normal file
39
db/init.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Execute schema
|
||||
$sql = file_get_contents(__DIR__ . '/schema.sql');
|
||||
$pdo->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();
|
||||
}
|
||||
82
db/schema.sql
Normal file
82
db/schema.sql
Normal file
@ -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
|
||||
);
|
||||
34
includes/functions.php
Normal file
34
includes/functions.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// Function to fetch company settings from DB
|
||||
function get_company_settings() {
|
||||
static $settings = null;
|
||||
if ($settings === null) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$stmt = $pdo->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']);
|
||||
}
|
||||
217
index.php
217
index.php
@ -1,150 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$pdo = db();
|
||||
$categories = $pdo->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();
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<title>Menu - <?= htmlspecialchars($settings['company_name']) ?></title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<nav class="navbar">
|
||||
<div class="container d-flex justify-content-between align-items-center">
|
||||
<a href="index.php" class="brand-logo"><?= htmlspecialchars($settings['company_name']) ?></a>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-dark me-3">Table <?= htmlspecialchars($table_id) ?></span>
|
||||
<a href="admin/orders.php" class="btn btn-sm btn-outline-dark">Staff POS</a>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<!-- Menu Column -->
|
||||
<div class="col-lg-8">
|
||||
<h1 class="mb-4 fw-bold">Order Now</h1>
|
||||
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<h2 class="menu-category-title"><?= htmlspecialchars($category['name']) ?></h2>
|
||||
<div class="row g-4 mb-5">
|
||||
<?php foreach ($all_products as $product):
|
||||
if ($product['category_id'] == $category['id']): ?>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="product-card">
|
||||
<img src="https://picsum.photos/seed/<?= $product['id'] ?>/400/300" alt="<?= htmlspecialchars($product['name']) ?>" class="product-image">
|
||||
<div class="product-info">
|
||||
<h3 class="product-name"><?= htmlspecialchars($product['name']) ?></h3>
|
||||
<p class="product-desc"><?= htmlspecialchars($product['description']) ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="product-price"><?= format_currency($product['price']) ?></span>
|
||||
<button class="btn btn-add btn-sm add-to-cart"
|
||||
data-id="<?= $product['id'] ?>"
|
||||
data-name="<?= htmlspecialchars($product['name']) ?>"
|
||||
data-price="<?= $product['price'] ?>">
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Cart Column -->
|
||||
<div class="col-lg-4 d-none d-lg-block">
|
||||
<div class="cart-sidebar">
|
||||
<h4 class="fw-bold mb-4">Your Order</h4>
|
||||
<div id="cart-items" class="flex-grow-1 overflow-auto">
|
||||
<p class="text-center text-muted mt-5">Your cart is empty.</p>
|
||||
</div>
|
||||
<div class="cart-total mt-3">
|
||||
<span>Total</span>
|
||||
<span id="cart-total-price"><?= format_currency(0) ?></span>
|
||||
</div>
|
||||
<button class="btn btn-dark w-100 btn-lg mt-4" id="checkout-btn" disabled>Place Order</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Cart Trigger -->
|
||||
<div class="fixed-bottom d-lg-none p-3 bg-white border-top">
|
||||
<button class="btn btn-dark w-100 btn-lg" id="mobile-cart-btn">View Cart (<?= format_currency(0) ?>)</button>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script>
|
||||
const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user