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 {
|
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-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
font-size: 14px;
|
background-color: var(--secondary-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.5;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-wrapper {
|
.navbar {
|
||||||
display: flex;
|
background-color: var(--white);
|
||||||
align-items: center;
|
border-bottom: 1px solid #EAEAEA;
|
||||||
justify-content: center;
|
padding: 1rem 0;
|
||||||
min-height: 100vh;
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-category-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
border-bottom: 2px solid var(--accent-color);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
height: 180px;
|
||||||
box-sizing: border-box;
|
background-color: #EEEEEE;
|
||||||
position: relative;
|
object-fit: cover;
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gradient {
|
.product-info {
|
||||||
0% {
|
padding: 1.25rem;
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-container {
|
.product-name {
|
||||||
width: 100%;
|
font-weight: 600;
|
||||||
max-width: 600px;
|
font-size: 1.1rem;
|
||||||
background: rgba(255, 255, 255, 0.85);
|
margin-bottom: 0.5rem;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
}
|
||||||
border-radius: 20px;
|
|
||||||
|
.product-desc {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--white);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add:hover {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-sidebar {
|
||||||
|
background: var(--white);
|
||||||
|
height: 100vh;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
border-left: 1px solid #EAEAEA;
|
||||||
|
padding: 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-header {
|
.cart-item {
|
||||||
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;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #EEEEEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-messages {
|
.cart-item-name {
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom Scrollbar */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
|
||||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.visitor {
|
|
||||||
align-self: flex-end;
|
|
||||||
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
|
|
||||||
color: #fff;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.bot {
|
|
||||||
align-self: flex-start;
|
|
||||||
background: #ffffff;
|
|
||||||
color: #212529;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input-area {
|
|
||||||
padding: 1.25rem;
|
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input-area form {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input-area input:focus {
|
|
||||||
border-color: #23a6d5;
|
|
||||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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-container h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #212529;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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-weight: 600;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control {
|
.cart-item-price {
|
||||||
width: 100%;
|
font-weight: 700;
|
||||||
padding: 0.75rem 1rem;
|
font-size: 0.9rem;
|
||||||
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 {
|
.cart-total {
|
||||||
outline: none;
|
margin-top: auto;
|
||||||
border-color: #23a6d5;
|
padding-top: 1rem;
|
||||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
|
border-top: 2px solid var(--primary-color);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-table-container {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
@ -1,39 +1,131 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const chatForm = document.getElementById('chat-form');
|
let cart = [];
|
||||||
const chatInput = document.getElementById('chat-input');
|
const cartItemsContainer = document.getElementById('cart-items');
|
||||||
const chatMessages = document.getElementById('chat-messages');
|
const cartTotalPrice = document.getElementById('cart-total-price');
|
||||||
|
const checkoutBtn = document.getElementById('checkout-btn');
|
||||||
|
const mobileCartBtn = document.getElementById('mobile-cart-btn');
|
||||||
|
|
||||||
const appendMessage = (text, sender) => {
|
// Helper for currency formatting
|
||||||
const msgDiv = document.createElement('div');
|
function formatCurrency(amount) {
|
||||||
msgDiv.classList.add('message', sender);
|
// Fallback if settings not defined
|
||||||
msgDiv.textContent = text;
|
const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 };
|
||||||
chatMessages.appendChild(msgDiv);
|
const symbol = settings.currency_symbol || '$';
|
||||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
const decimals = parseInt(settings.currency_decimals || 2);
|
||||||
|
return symbol + parseFloat(amount).toFixed(decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
};
|
};
|
||||||
|
|
||||||
chatForm.addEventListener('submit', async (e) => {
|
const existing = cart.find(item => item.id === product.id);
|
||||||
e.preventDefault();
|
if (existing) {
|
||||||
const message = chatInput.value.trim();
|
existing.quantity++;
|
||||||
if (!message) return;
|
} else {
|
||||||
|
cart.push(product);
|
||||||
|
}
|
||||||
|
|
||||||
appendMessage(message, 'visitor');
|
updateCart();
|
||||||
chatInput.value = '';
|
showToast(`${product.name} added to cart!`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
function updateCart() {
|
||||||
const response = await fetch('api/chat.php', {
|
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',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ message })
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Artificial delay for realism
|
function showToast(message, type = 'dark') {
|
||||||
setTimeout(() => {
|
const toastContainer = document.getElementById('toast-container');
|
||||||
appendMessage(data.reply, 'bot');
|
if (!toastContainer) return;
|
||||||
}, 500);
|
|
||||||
} catch (error) {
|
const toastId = 'toast-' + Date.now();
|
||||||
console.error('Error:', error);
|
const toastHtml = `
|
||||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
<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;
|
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
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
@ini_set('display_errors', '1');
|
require_once __DIR__ . '/db/config.php';
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
$pdo = db();
|
||||||
$now = date('Y-m-d H:i:s');
|
$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>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>New Style</title>
|
<title>Menu - <?= htmlspecialchars($settings['company_name']) ?></title>
|
||||||
<?php
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
// 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; ?>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||||
: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>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<nav class="navbar">
|
||||||
<div class="card">
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<a href="index.php" class="brand-logo"><?= htmlspecialchars($settings['company_name']) ?></a>
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<div class="d-flex align-items-center">
|
||||||
<span class="sr-only">Loading…</span>
|
<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>
|
</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>
|
</div>
|
||||||
</main>
|
</nav>
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<div class="container mt-4">
|
||||||
</footer>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user