Autosave: 20260222-110909

This commit is contained in:
Flatlogic Bot 2026-02-22 11:09:10 +00:00
parent 3ce9ce30e6
commit 3d24190863
25 changed files with 1529 additions and 186 deletions

74
admin/area_edit.php Normal file
View File

@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$id = $_GET['id'] ?? null;
if (!$id) {
header('Location: areas.php');
exit;
}
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$outlet_id = $_POST['outlet_id'] ?? '';
if ($name && $outlet_id) {
$stmt = $pdo->prepare("UPDATE areas SET name = ?, outlet_id = ? WHERE id = ?");
$stmt->execute([$name, $outlet_id, $id]);
header('Location: areas.php');
exit;
}
}
// Fetch Area Details
$stmt = $pdo->prepare("SELECT * FROM areas WHERE id = ?");
$stmt->execute([$id]);
$area = $stmt->fetch();
if (!$area) {
die("Area not found.");
}
// 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">Edit Area</h2>
<a href="areas.php" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back
</a>
</div>
<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($area['name']) ?>" 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'] ?>" <?= $outlet['id'] == $area['outlet_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($outlet['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="d-flex justify-content-end gap-2">
<a href="areas.php" class="btn btn-light">Cancel</a>
<button type="submit" class="btn btn-primary">Update Area</button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -55,7 +55,8 @@ include 'includes/header.php';
<td class="fw-bold"><?= htmlspecialchars($area['name']) ?></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><span class="badge bg-info text-dark"><?= htmlspecialchars($area['outlet_name'] ?? 'N/A') ?></span></td>
<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> <a href="area_edit.php?id=<?= $area['id'] ?>" class="btn btn-sm btn-outline-primary me-1" title="Edit"><i class="bi bi-pencil"></i></a>
<a href="?delete=<?= $area['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this area?')" title="Delete"><i class="bi bi-trash"></i></a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -104,4 +105,4 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>

View File

@ -2,29 +2,22 @@
require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../db/config.php';
$pdo = db(); $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'])) { if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM categories WHERE id = ?")->execute([$_GET['delete']]); $pdo->prepare("DELETE FROM categories WHERE id = ?")->execute([$_GET['delete']]);
header("Location: categories.php"); header("Location: categories.php");
exit; exit;
} }
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll(); $categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order ASC, name ASC")->fetchAll();
include 'includes/header.php'; include 'includes/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">Categories</h2> <h2 class="fw-bold mb-0">Categories</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal"> <a href="category_edit.php" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Add Category <i class="bi bi-plus-lg"></i> Add Category
</button> </a>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
@ -34,49 +27,45 @@ include 'includes/header.php';
<thead class="bg-light"> <thead class="bg-light">
<tr> <tr>
<th class="ps-4">ID</th> <th class="ps-4">ID</th>
<th style="width: 80px;">Image</th>
<th>Name</th> <th>Name</th>
<th>Actions</th> <th>Sort Order</th>
<th class="text-end pe-4">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($categories as $cat): ?> <?php foreach ($categories as $cat): ?>
<tr> <tr>
<td class="ps-4 fw-medium">#<?= $cat['id'] ?></td> <td class="ps-4 fw-medium">#<?= $cat['id'] ?></td>
<td><?= htmlspecialchars($cat['name']) ?></td>
<td> <td>
<?php if (!empty($cat['image_url'])): ?>
<img src="<?= htmlspecialchars(strpos($cat['image_url'], 'http') === 0 ? $cat['image_url'] : '../' . $cat['image_url']) ?>"
class="rounded object-fit-cover"
width="50" height="50"
alt="<?= htmlspecialchars($cat['name']) ?>">
<?php else: ?>
<div class="rounded bg-light d-flex align-items-center justify-content-center text-muted border" style="width: 50px; height: 50px;">
<i class="bi bi-image"></i>
</div>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($cat['name']) ?></td>
<td><?= $cat['sort_order'] ?></td>
<td class="text-end pe-4">
<a href="category_edit.php?id=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i></a>
<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> <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> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($categories)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">No categories found.</td>
</tr>
<?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
<!-- Add Category Modal --> <?php include 'includes/footer.php'; ?>
<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'; ?>

150
admin/category_edit.php Normal file
View File

@ -0,0 +1,150 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
$category = null;
$message = '';
$isEdit = false;
// If ID provided, fetch category for editing
if ($id) {
$stmt = $pdo->prepare("SELECT * FROM categories WHERE id = ?");
$stmt->execute([$id]);
$category = $stmt->fetch();
if ($category) {
$isEdit = true;
} else {
// ID not found, redirect to list
header("Location: categories.php");
exit;
}
}
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name']);
$sort_order = (int)$_POST['sort_order'];
$image_url = $isEdit ? $category['image_url'] : null;
// Basic Validation
if (empty($name)) {
$message = '<div class="alert alert-danger">Category name is required.</div>';
} else {
// Image Upload Handling
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
$uploadDir = __DIR__ . '/../assets/images/categories/';
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('cat_') . '.' . $fileExt;
$targetFile = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
// Remove old image if exists and not default placeholder (optional, strict cleanup)
if ($isEdit && !empty($category['image_url']) && file_exists(__DIR__ . '/../' . $category['image_url'])) {
// unlink(__DIR__ . '/../' . $category['image_url']); // Uncomment to auto-delete old images
}
$image_url = 'assets/images/categories/' . $fileName;
} else {
$message = '<div class="alert alert-danger">Failed to upload image. Check permissions.</div>';
}
} else {
$message = '<div class="alert alert-danger">Invalid file type. Allowed: jpg, png, gif, webp.</div>';
}
}
if (empty($message)) {
try {
if ($isEdit) {
$stmt = $pdo->prepare("UPDATE categories SET name = ?, sort_order = ?, image_url = ? WHERE id = ?");
$stmt->execute([$name, $sort_order, $image_url, $id]);
$message = '<div class="alert alert-success">Category updated successfully!</div>';
// Refresh data
$stmt = $pdo->prepare("SELECT * FROM categories WHERE id = ?");
$stmt->execute([$id]);
$category = $stmt->fetch();
} else {
$stmt = $pdo->prepare("INSERT INTO categories (name, sort_order, image_url) VALUES (?, ?, ?)");
$stmt->execute([$name, $sort_order, $image_url]);
header("Location: categories.php?success=created");
exit;
}
} catch (PDOException $e) {
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
}
}
}
}
// Defaults for Add Mode or Error State
if (!$isEdit) {
$category = [
'name' => $_POST['name'] ?? '',
'sort_order' => $_POST['sort_order'] ?? 0,
'image_url' => ''
];
}
include 'includes/header.php';
?>
<div class="mb-4">
<a href="categories.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Categories</a>
<h2 class="fw-bold mb-0"><?= $isEdit ? 'Edit Category' : 'Add New Category' ?></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">Category Name <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($category['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Sort Order</label>
<input type="number" name="sort_order" class="form-control" value="<?= htmlspecialchars($category['sort_order']) ?>">
<div class="form-text">Lower numbers appear first. Default is 0.</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Category Image</label>
<?php if (!empty($category['image_url'])): ?>
<div class="mb-2">
<img src="<?= htmlspecialchars(strpos($category['image_url'], 'http') === 0 ? $category['image_url'] : '../' . $category['image_url']) ?>"
class="img-fluid rounded border"
style="max-height: 200px; width: auto;"
alt="Category Image">
</div>
<?php else: ?>
<div class="mb-2 p-4 bg-light text-center border rounded text-muted">
<i class="bi bi-image fs-1"></i><br>No Image
</div>
<?php endif; ?>
<input type="file" name="image" class="form-control" accept="image/*">
<div class="form-text">Allowed: JPG, PNG, GIF, WEBP. Leave empty to keep current.</div>
</div>
</div>
</div>
<hr>
<div class="d-flex justify-content-end gap-2">
<a href="categories.php" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary"><?= $isEdit ? 'Save Changes' : 'Create Category' ?></button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -14,30 +14,69 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$vat_rate = $_POST['vat_rate'] ?? 0; $vat_rate = $_POST['vat_rate'] ?? 0;
$currency_symbol = $_POST['currency_symbol'] ?? '$'; $currency_symbol = $_POST['currency_symbol'] ?? '$';
$currency_decimals = $_POST['currency_decimals'] ?? 2; $currency_decimals = $_POST['currency_decimals'] ?? 2;
$ctr_number = $_POST['ctr_number'] ?? '';
$vat_number = $_POST['vat_number'] ?? '';
// Handle File Uploads
$uploadDir = __DIR__ . '/../assets/images/company/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$logo_url = $settings['logo_url'] ?? null;
$favicon_url = $settings['favicon_url'] ?? null;
// Logo Upload
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
$fileInfo = pathinfo($_FILES['logo']['name']);
$fileExt = strtolower($fileInfo['extension']);
$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
if (in_array($fileExt, $allowedExts)) {
$fileName = 'logo_' . uniqid() . '.' . $fileExt;
$targetFile = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['logo']['tmp_name'], $targetFile)) {
$logo_url = 'assets/images/company/' . $fileName;
}
}
}
// Favicon Upload
if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
$fileInfo = pathinfo($_FILES['favicon']['name']);
$fileExt = strtolower($fileInfo['extension']);
$allowedExts = ['ico', 'png', 'svg']; // Favicons are usually ico/png/svg
if (in_array($fileExt, $allowedExts)) {
$fileName = 'favicon_' . uniqid() . '.' . $fileExt;
$targetFile = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['favicon']['tmp_name'], $targetFile)) {
$favicon_url = 'assets/images/company/' . $fileName;
}
}
}
try { try {
// Check if row exists (it should, from our functions.php logic or migration) // Check if row exists
$exists = $pdo->query("SELECT COUNT(*) FROM company_settings")->fetchColumn(); $exists = $pdo->query("SELECT COUNT(*) FROM company_settings")->fetchColumn();
if ($exists) { if ($exists) {
$stmt = $pdo->prepare("UPDATE company_settings SET company_name=?, address=?, phone=?, email=?, vat_rate=?, currency_symbol=?, currency_decimals=?, updated_at=NOW()"); $stmt = $pdo->prepare("UPDATE company_settings SET company_name=?, address=?, phone=?, email=?, vat_rate=?, currency_symbol=?, currency_decimals=?, ctr_number=?, vat_number=?, logo_url=?, favicon_url=?, updated_at=NOW()");
$stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals]); $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $ctr_number, $vat_number, $logo_url, $favicon_url]);
} else { } else {
$stmt = $pdo->prepare("INSERT INTO company_settings (company_name, address, phone, email, vat_rate, currency_symbol, currency_decimals) VALUES (?, ?, ?, ?, ?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO company_settings (company_name, address, phone, email, vat_rate, currency_symbol, currency_decimals, ctr_number, vat_number, logo_url, favicon_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals]); $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $ctr_number, $vat_number, $logo_url, $favicon_url]);
} }
$message = '<div class="alert alert-success">Company settings updated successfully!</div>'; $message = '<div class="alert alert-success">Company settings updated successfully!</div>';
// Refresh settings // Refresh settings
$settings = [ $settings = get_company_settings(); // Re-fetch to get updated values
'company_name' => $company_name, // Manually update immediate values for display if fetch is cached/laggy (though re-fetch is better)
'address' => $address, $settings['ctr_number'] = $ctr_number;
'phone' => $phone, $settings['vat_number'] = $vat_number;
'email' => $email, $settings['logo_url'] = $logo_url;
'vat_rate' => $vat_rate, $settings['favicon_url'] = $favicon_url;
'currency_symbol' => $currency_symbol,
'currency_decimals' => $currency_decimals
];
} catch (Exception $e) { } catch (Exception $e) {
$message = '<div class="alert alert-danger">Error updating settings: ' . htmlspecialchars($e->getMessage()) . '</div>'; $message = '<div class="alert alert-danger">Error updating settings: ' . htmlspecialchars($e->getMessage()) . '</div>';
} }
@ -54,7 +93,7 @@ include 'includes/header.php';
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST" enctype="multipart/form-data">
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label">Company Name</label> <label class="form-label">Company Name</label>
@ -74,6 +113,19 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<hr class="my-4">
<h5 class="mb-3">Legal & Tax Information</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">CTR No (Company Tax Registration)</label>
<input type="text" name="ctr_number" class="form-control" value="<?= htmlspecialchars($settings['ctr_number'] ?? '') ?>" placeholder="e.g. 123456789">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">VAT No (Value Added Tax Number)</label>
<input type="text" name="vat_number" class="form-control" value="<?= htmlspecialchars($settings['vat_number'] ?? '') ?>" placeholder="e.g. VAT-987654321">
</div>
</div>
<hr class="my-4"> <hr class="my-4">
<h5 class="mb-3">Financial Settings</h5> <h5 class="mb-3">Financial Settings</h5>
@ -95,6 +147,36 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<hr class="my-4">
<h5 class="mb-3">Branding</h5>
<div class="row align-items-center">
<div class="col-md-6 mb-3">
<label class="form-label">Company Logo</label>
<div class="d-flex align-items-center gap-3">
<?php if (!empty($settings['logo_url'])): ?>
<div class="border rounded p-1 bg-light">
<img src="<?= htmlspecialchars('../' . $settings['logo_url']) ?>" alt="Logo" style="height: 60px; max-width: 100px; object-fit: contain;">
</div>
<?php endif; ?>
<input type="file" name="logo" class="form-control" accept="image/*">
</div>
<div class="form-text">Recommended: PNG or SVG with transparent background.</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Favicon</label>
<div class="d-flex align-items-center gap-3">
<?php if (!empty($settings['favicon_url'])): ?>
<div class="border rounded p-1 bg-light">
<img src="<?= htmlspecialchars('../' . $settings['favicon_url']) ?>" alt="Favicon" style="height: 32px; width: 32px; object-fit: contain;">
</div>
<?php endif; ?>
<input type="file" name="favicon" class="form-control" accept=".ico,.png,.svg">
</div>
<div class="form-text">Recommended: 32x32 ICO or PNG.</div>
</div>
</div>
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Save Changes <i class="bi bi-save"></i> Save Changes
@ -104,4 +186,4 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>

81
admin/customer_edit.php Normal file
View File

@ -0,0 +1,81 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
if (!isset($_GET['id'])) {
header("Location: customers.php");
exit;
}
$id = $_GET['id'];
$message = '';
// Fetch Customer
$stmt = $pdo->prepare("SELECT * FROM customers WHERE id = ?");
$stmt->execute([$id]);
$customer = $stmt->fetch();
if (!$customer) {
header("Location: customers.php");
exit;
}
// Handle Update
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'];
$email = $_POST['email'];
$phone = $_POST['phone'];
$address = $_POST['address'];
$stmt = $pdo->prepare("UPDATE customers SET name = ?, email = ?, phone = ?, address = ? WHERE id = ?");
if ($stmt->execute([$name, $email, $phone, $address, $id])) {
$message = '<div class="alert alert-success">Customer updated successfully!</div>';
// Refresh data
$stmt = $pdo->prepare("SELECT * FROM customers WHERE id = ?");
$stmt->execute([$id]);
$customer = $stmt->fetch();
} else {
$message = '<div class="alert alert-danger">Error updating customer.</div>';
}
}
include 'includes/header.php';
?>
<div class="mb-4">
<a href="customers.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Customers</a>
<h2 class="fw-bold mb-0">Edit Customer: <?= htmlspecialchars($customer['name']) ?></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">Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($customer['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($customer['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($customer['phone']) ?>">
</div>
<div class="col-md-12 mb-3">
<label class="form-label">Address</label>
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($customer['address']) ?></textarea>
</div>
</div>
<div class="d-flex justify-content-end gap-2">
<a href="customers.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'; ?>

121
admin/customers.php Normal file
View File

@ -0,0 +1,121 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$message = '';
// Handle Add Customer
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_customer') {
$name = $_POST['name'];
$email = $_POST['email'];
$phone = $_POST['phone'];
$address = $_POST['address'];
$stmt = $pdo->prepare("INSERT INTO customers (name, email, phone, address) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$name, $email, $phone, $address])) {
$message = '<div class="alert alert-success">Customer added successfully!</div>';
} else {
$message = '<div class="alert alert-danger">Error adding customer.</div>';
}
}
// Handle Delete
if (isset($_GET['delete'])) {
$id = $_GET['delete'];
$pdo->prepare("DELETE FROM customers WHERE id = ?")->execute([$id]);
header("Location: customers.php");
exit;
}
// Fetch Customers
$customers = $pdo->query("SELECT * FROM customers 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">Customers</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-plus-lg"></i> Add Customer
</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">Name</th>
<th>Email</th>
<th>Phone</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($customers as $customer): ?>
<tr>
<td class="ps-4 fw-bold"><?= htmlspecialchars($customer['name']) ?></td>
<td><?= htmlspecialchars($customer['email']) ?></td>
<td><?= htmlspecialchars($customer['phone']) ?></td>
<td><?= htmlspecialchars(substr($customer['address'] ?? '', 0, 30)) ?>...</td>
<td>
<div class="btn-group">
<a href="customer_edit.php?id=<?= $customer['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Customer"><i class="bi bi-pencil"></i></a>
<a href="?delete=<?= $customer['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; ?>
<?php if (empty($customers)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">No customers found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Customer</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_customer">
<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">Email</label>
<input type="email" name="email" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Phone</label>
<input type="text" name="phone" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Address</label>
<textarea name="address" class="form-control" rows="3"></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 Customer</button>
</div>
</form>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -1,8 +1,20 @@
<?php <?php
// admin/includes/header.php
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
// Ensure functions are available if not already
if (!function_exists('get_company_settings')) {
// Attempt to locate config relative to this header file
if (file_exists(__DIR__ . '/../../db/config.php')) {
require_once __DIR__ . '/../../db/config.php';
}
}
$companySettings = function_exists('get_company_settings') ? get_company_settings() : [];
$companyName = $companySettings['company_name'] ?? 'Foody';
$logoUrl = $companySettings['logo_url'] ?? '';
$faviconUrl = $companySettings['favicon_url'] ?? '';
// Simple active link helper // Simple active link helper
function isActive($page) { function isActive($page) {
return basename($_SERVER['PHP_SELF']) === $page ? 'active' : ''; return basename($_SERVER['PHP_SELF']) === $page ? 'active' : '';
@ -13,7 +25,10 @@ function isActive($page) {
<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>Foody Admin Panel</title> <title><?= htmlspecialchars($companyName) ?> Admin Panel</title>
<?php if ($faviconUrl): ?>
<link rel="icon" href="../<?= htmlspecialchars($faviconUrl) ?>">
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.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.googleapis.com">
@ -41,7 +56,13 @@ function isActive($page) {
<!-- Mobile Header --> <!-- Mobile Header -->
<nav class="navbar navbar-light bg-white border-bottom d-md-none fixed-top"> <nav class="navbar navbar-light bg-white border-bottom d-md-none fixed-top">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand fw-bold text-dark" href="index.php">Foody<span class="text-primary">Admin</span></a> <a class="navbar-brand fw-bold text-dark" href="index.php">
<?php if ($logoUrl): ?>
<img src="../<?= htmlspecialchars($logoUrl) ?>" alt="Logo" style="height: 30px;">
<?php else: ?>
<?= htmlspecialchars($companyName) ?><span class="text-primary">Admin</span>
<?php endif; ?>
</a>
<button class="navbar-toggler border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu"> <button class="navbar-toggler border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@ -50,9 +71,13 @@ function isActive($page) {
<!-- Sidebar --> <!-- Sidebar -->
<div class="offcanvas-md offcanvas-start sidebar" tabindex="-1" id="sidebarMenu"> <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"> <div class="d-flex align-items-center justify-content-center px-4 pb-3 border-bottom d-none d-md-flex" style="min-height: 80px;">
<a href="index.php" class="text-decoration-none"> <a href="index.php" class="text-decoration-none">
<h4 class="fw-bold m-0 text-dark">Foody<span style="color: #FF6B6B;">.</span></h4> <?php if ($logoUrl): ?>
<img src="../<?= htmlspecialchars($logoUrl) ?>" alt="Logo" style="max-height: 50px; max-width: 100%;">
<?php else: ?>
<h4 class="fw-bold m-0 text-dark"><?= htmlspecialchars($companyName) ?><span style="color: #FF6B6B;">.</span></h4>
<?php endif; ?>
</a> </a>
</div> </div>
<div class="px-4 py-3 d-md-none"> <div class="px-4 py-3 d-md-none">
@ -95,9 +120,19 @@ function isActive($page) {
<i class="bi bi-ui-checks-grid me-2"></i> Tables <i class="bi bi-ui-checks-grid me-2"></i> Tables
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link <?= isActive('customers.php') ?>" href="customers.php">
<i class="bi bi-people-fill me-2"></i> Customers
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= isActive('suppliers.php') ?>" href="suppliers.php">
<i class="bi bi-truck me-2"></i> Suppliers
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= isActive('loyalty.php') ?>" href="loyalty.php"> <a class="nav-link <?= isActive('loyalty.php') ?>" href="loyalty.php">
<i class="bi bi-people me-2"></i> Loyalty <i class="bi bi-award me-2"></i> Loyalty
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -113,4 +148,4 @@ function isActive($page) {
</ul> </ul>
</div> </div>
<div class="main-content pt-5 pt-md-4"> <div class="main-content pt-5 pt-md-4">

View File

@ -128,8 +128,9 @@ include 'includes/header.php';
<?php <?php
$badge = match($order['order_type']) { $badge = match($order['order_type']) {
'dine-in' => 'bg-info', 'dine-in' => 'bg-info',
'takeaway' => 'bg-success',
'delivery' => 'bg-warning', 'delivery' => 'bg-warning',
'drive-thru' => 'bg-purple', // custom class or just use primary 'drive-thru' => 'bg-purple',
default => 'bg-secondary' default => 'bg-secondary'
}; };
?> ?>
@ -163,4 +164,4 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>

View File

@ -35,6 +35,7 @@ include 'includes/header.php';
<thead class="bg-light"> <thead class="bg-light">
<tr> <tr>
<th class="ps-4">ID</th> <th class="ps-4">ID</th>
<th>Type</th>
<th>Source</th> <th>Source</th>
<th>Items</th> <th>Items</th>
<th>Total</th> <th>Total</th>
@ -48,10 +49,22 @@ include 'includes/header.php';
<tr> <tr>
<td class="ps-4 fw-medium">#<?= $order['id'] ?></td> <td class="ps-4 fw-medium">#<?= $order['id'] ?></td>
<td> <td>
<?php if ($order['table_number']): ?> <?php
$badge = match($order['order_type']) {
'dine-in' => 'bg-info',
'takeaway' => 'bg-success',
'delivery' => 'bg-warning',
'drive-thru' => 'bg-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['order_type'] === 'dine-in' && $order['table_number']): ?>
<span class="badge bg-secondary">Table <?= htmlspecialchars($order['table_number']) ?></span> <span class="badge bg-secondary">Table <?= htmlspecialchars($order['table_number']) ?></span>
<?php else: ?> <?php else: ?>
<span class="badge bg-info text-dark"><?= ucfirst($order['order_type']) ?></span> <span class="badge bg-light text-dark border"><?= ucfirst($order['order_type']) ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td><small class="text-muted"><?= htmlspecialchars($order['items_summary']) ?></small></td> <td><small class="text-muted"><?= htmlspecialchars($order['items_summary']) ?></small></td>
@ -91,7 +104,7 @@ include 'includes/header.php';
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($orders)): ?> <?php if (empty($orders)): ?>
<tr> <tr>
<td colspan="7" class="text-center py-5 text-muted"> <td colspan="8" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2"></i> <i class="bi bi-inbox fs-1 d-block mb-2"></i>
No active orders. No active orders.
</td> </td>
@ -103,4 +116,4 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>

93
admin/supplier_edit.php Normal file
View File

@ -0,0 +1,93 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
if (!isset($_GET['id'])) {
header("Location: suppliers.php");
exit;
}
$id = $_GET['id'];
$message = '';
// Fetch Supplier
$stmt = $pdo->prepare("SELECT * FROM suppliers WHERE id = ?");
$stmt->execute([$id]);
$supplier = $stmt->fetch();
if (!$supplier) {
header("Location: suppliers.php");
exit;
}
// Handle Update
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'];
$contact_person = $_POST['contact_person'];
$email = $_POST['email'];
$phone = $_POST['phone'];
$vat_no = $_POST['vat_no'];
$address = $_POST['address'];
$stmt = $pdo->prepare("UPDATE suppliers SET name = ?, contact_person = ?, email = ?, phone = ?, vat_no = ?, address = ? WHERE id = ?");
if ($stmt->execute([$name, $contact_person, $email, $phone, $vat_no, $address, $id])) {
$message = '<div class="alert alert-success">Supplier updated successfully!</div>';
// Refresh data
$stmt = $pdo->prepare("SELECT * FROM suppliers WHERE id = ?");
$stmt->execute([$id]);
$supplier = $stmt->fetch();
} else {
$message = '<div class="alert alert-danger">Error updating supplier.</div>';
}
}
include 'includes/header.php';
?>
<div class="mb-4">
<a href="suppliers.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Suppliers</a>
<h2 class="fw-bold mb-0">Edit Supplier: <?= htmlspecialchars($supplier['name']) ?></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="name" class="form-control" value="<?= htmlspecialchars($supplier['name']) ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Contact Person</label>
<input type="text" name="contact_person" class="form-control" value="<?= htmlspecialchars($supplier['contact_person']) ?>">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($supplier['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($supplier['phone']) ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label">VAT No</label>
<input type="text" name="vat_no" class="form-control" value="<?= htmlspecialchars($supplier['vat_no']) ?>" placeholder="e.g. GB123456789">
</div>
<div class="mb-3">
<label class="form-label">Address</label>
<textarea name="address" class="form-control" rows="3"><?= htmlspecialchars($supplier['address']) ?></textarea>
</div>
<div class="d-flex justify-content-end gap-2">
<a href="suppliers.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'; ?>

136
admin/suppliers.php Normal file
View File

@ -0,0 +1,136 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$message = '';
// Handle Add Supplier
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_supplier') {
$name = $_POST['name'];
$contact_person = $_POST['contact_person'];
$email = $_POST['email'];
$phone = $_POST['phone'];
$address = $_POST['address'];
$vat_no = $_POST['vat_no'];
$stmt = $pdo->prepare("INSERT INTO suppliers (name, contact_person, email, phone, address, vat_no) VALUES (?, ?, ?, ?, ?, ?)");
if ($stmt->execute([$name, $contact_person, $email, $phone, $address, $vat_no])) {
$message = '<div class="alert alert-success">Supplier added successfully!</div>';
} else {
$message = '<div class="alert alert-danger">Error adding supplier.</div>';
}
}
// Handle Delete
if (isset($_GET['delete'])) {
$id = $_GET['delete'];
$pdo->prepare("DELETE FROM suppliers WHERE id = ?")->execute([$id]);
header("Location: suppliers.php");
exit;
}
// Fetch Suppliers
$suppliers = $pdo->query("SELECT * FROM suppliers 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">Suppliers</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
<i class="bi bi-plus-lg"></i> Add Supplier
</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">Company Name</th>
<th>Contact Person</th>
<th>Email / Phone</th>
<th>VAT No</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($suppliers as $supplier): ?>
<tr>
<td class="ps-4 fw-bold"><?= htmlspecialchars($supplier['name']) ?></td>
<td><?= htmlspecialchars($supplier['contact_person']) ?></td>
<td>
<div><?= htmlspecialchars($supplier['email']) ?></div>
<small class="text-muted"><?= htmlspecialchars($supplier['phone']) ?></small>
</td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($supplier['vat_no']) ?></span></td>
<td>
<div class="btn-group">
<a href="supplier_edit.php?id=<?= $supplier['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Supplier"><i class="bi bi-pencil"></i></a>
<a href="?delete=<?= $supplier['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; ?>
<?php if (empty($suppliers)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">No suppliers found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add Supplier Modal -->
<div class="modal fade" id="addSupplierModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Supplier</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_supplier">
<div class="mb-3">
<label class="form-label">Company Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Contact Person</label>
<input type="text" name="contact_person" class="form-control">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Phone</label>
<input type="text" name="phone" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label">VAT No</label>
<input type="text" name="vat_no" class="form-control" placeholder="e.g. GB123456789">
</div>
<div class="mb-3">
<label class="form-label">Address</label>
<textarea name="address" class="form-control" rows="3"></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 Supplier</button>
</div>
</form>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>

80
admin/table_edit.php Normal file
View File

@ -0,0 +1,80 @@
<?php
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$id = $_GET['id'] ?? null;
if (!$id) {
header('Location: tables.php');
exit;
}
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$area_id = $_POST['area_id'] ?? '';
$capacity = $_POST['capacity'] ?? 4;
if ($name && $area_id) {
$stmt = $pdo->prepare("UPDATE tables SET name = ?, area_id = ?, capacity = ? WHERE id = ?");
$stmt->execute([$name, $area_id, $capacity, $id]);
header('Location: tables.php');
exit;
}
}
// Fetch Table Details
$stmt = $pdo->prepare("SELECT * FROM tables WHERE id = ?");
$stmt->execute([$id]);
$table = $stmt->fetch();
if (!$table) {
die("Table not found.");
}
// 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">Edit Table</h2>
<a href="tables.php" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back
</a>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">Table Name/Number</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($table['name']) ?>" 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'] ?>" <?= $area['id'] == $table['area_id'] ? 'selected' : '' ?>>
<?= 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="<?= htmlspecialchars($table['capacity']) ?>" min="1" required>
</div>
<div class="d-flex justify-content-end gap-2">
<a href="tables.php" class="btn btn-light">Cancel</a>
<button type="submit" class="btn btn-primary">Update Table</button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -57,7 +57,8 @@ include 'includes/header.php';
<td><span class="badge bg-secondary"><?= htmlspecialchars($table['area_name'] ?? 'N/A') ?></span></td> <td><span class="badge bg-secondary"><?= htmlspecialchars($table['area_name'] ?? 'N/A') ?></span></td>
<td><?= htmlspecialchars($table['capacity']) ?> pax</td> <td><?= htmlspecialchars($table['capacity']) ?> pax</td>
<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> <a href="table_edit.php?id=<?= $table['id'] ?>" class="btn btn-sm btn-outline-primary me-1" title="Edit"><i class="bi bi-pencil"></i></a>
<a href="?delete=<?= $table['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this table?')" title="Delete"><i class="bi bi-trash"></i></a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -110,4 +111,4 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include 'includes/footer.php'; ?>

View File

@ -2,7 +2,8 @@
header('Content-Type: application/json'); header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../db/config.php';
$data = json_decode(file_get_contents('php://input'), true); $input = file_get_contents('php://input');
$data = json_decode($input, true);
if (!$data) { if (!$data) {
echo json_encode(['success' => false, 'error' => 'No data provided']); echo json_encode(['success' => false, 'error' => 'No data provided']);
@ -13,20 +14,80 @@ try {
$pdo = db(); $pdo = db();
$pdo->beginTransaction(); $pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_number, total_amount, status) VALUES (?, ?, ?, 'pending')"); // Validate order_type against allowed ENUM values
$stmt->execute([1, $data['table_number'] ?? '1', $data['total_amount']]); $allowed_types = ['dine-in', 'takeaway', 'delivery', 'drive-thru'];
$order_type = isset($data['order_type']) && in_array($data['order_type'], $allowed_types)
? $data['order_type']
: 'dine-in';
$table_id = null;
$table_number = null;
if ($order_type === 'dine-in') {
$tid = $data['table_number'] ?? null; // Front-end sends ID as table_number
if ($tid) {
$stmt = $pdo->prepare("SELECT id, name FROM tables WHERE id = ?");
$stmt->execute([$tid]);
$table = $stmt->fetch(PDO::FETCH_ASSOC);
if ($table) {
$table_id = $table['id'];
$table_number = $table['name'];
}
}
// If not found or not provided, leave null (Walk-in/Counter) or default to 1 if it exists
if (!$table_id) {
// Optional: try to find table 1
$stmt = $pdo->query("SELECT id, name FROM tables LIMIT 1");
$table = $stmt->fetch(PDO::FETCH_ASSOC);
if ($table) {
$table_id = $table['id'];
$table_number = $table['name'];
}
}
}
// Customer Handling
$customer_id = $data['customer_id'] ?? null;
$customer_name = $data['customer_name'] ?? null; // Fallback or manual entry
$customer_phone = $data['customer_phone'] ?? null;
if ($customer_id) {
$stmt = $pdo->prepare("SELECT name, phone FROM customers WHERE id = ?");
$stmt->execute([$customer_id]);
$cust = $stmt->fetch(PDO::FETCH_ASSOC);
if ($cust) {
$customer_name = $cust['name'];
$customer_phone = $cust['phone'];
} else {
// Invalid customer ID, clear it but keep manual name if any (though unlikely combo)
$customer_id = null;
}
}
$stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, total_amount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
$stmt->execute([1, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $data['total_amount'] ?? 0]);
$order_id = $pdo->lastInsertId(); $order_id = $pdo->lastInsertId();
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)"); $item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)");
foreach ($data['items'] as $item) { if (!empty($data['items']) && is_array($data['items'])) {
$item_stmt->execute([$order_id, $item['id'], $item['quantity'], $item['price']]); foreach ($data['items'] as $item) {
$pid = $item['product_id'] ?? ($item['id'] ?? null);
$qty = $item['quantity'] ?? 1;
$price = $item['unit_price'] ?? ($item['price'] ?? 0);
if ($pid) {
$item_stmt->execute([$order_id, $pid, $qty, $price]);
}
}
} }
$pdo->commit(); $pdo->commit();
echo json_encode(['success' => true, 'order_id' => $order_id]); echo json_encode(['success' => true, 'order_id' => $order_id]);
} catch (Exception $e) { } catch (Exception $e) {
if ($pdo->inTransaction()) { if ($pdo->inTransaction()) {
$pdo->rollBack(); $pdo->rollBack();
} }
error_log("Order Error: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => $e->getMessage()]); echo json_encode(['success' => false, 'error' => $e->getMessage()]);
} }

23
api/search_customers.php Normal file
View File

@ -0,0 +1,23 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
$q = $_GET['q'] ?? '';
if (strlen($q) < 2) {
echo json_encode([]);
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, name, phone, email FROM customers WHERE name LIKE ? OR phone LIKE ? LIMIT 10");
$searchTerm = "%$q%";
$stmt->execute([$searchTerm, $searchTerm]);
$customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($customers);
} catch (Exception $e) {
error_log("Customer Search Error: " . $e->getMessage());
echo json_encode(['error' => 'Database error']);
}

39
api/tables.php Normal file
View File

@ -0,0 +1,39 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
try {
$pdo = db();
// Fetch all tables with their area names
$sql = "
SELECT t.id, t.name, t.capacity, a.name AS area_name
FROM tables t
LEFT JOIN areas a ON t.area_id = a.id
ORDER BY a.name ASC, t.name ASC
";
$stmt = $pdo->query($sql);
$tables = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch currently occupied table IDs
// Orders that are NOT completed and NOT cancelled are considered active
$occupiedSql = "
SELECT DISTINCT table_id
FROM orders
WHERE status NOT IN ('completed', 'cancelled')
AND table_id IS NOT NULL
";
$occupiedStmt = $pdo->query($occupiedSql);
$occupiedTableIds = $occupiedStmt->fetchAll(PDO::FETCH_COLUMN);
// Mark tables as occupied
foreach ($tables as &$table) {
$table['is_occupied'] = in_array($table['id'], $occupiedTableIds);
}
echo json_encode(['success' => true, 'tables' => $tables]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -2,84 +2,329 @@ document.addEventListener('DOMContentLoaded', () => {
let cart = []; let cart = [];
const cartItemsContainer = document.getElementById('cart-items'); const cartItemsContainer = document.getElementById('cart-items');
const cartTotalPrice = document.getElementById('cart-total-price'); const cartTotalPrice = document.getElementById('cart-total-price');
const cartSubtotal = document.getElementById('cart-subtotal');
const checkoutBtn = document.getElementById('checkout-btn'); const checkoutBtn = document.getElementById('checkout-btn');
const mobileCartBtn = document.getElementById('mobile-cart-btn');
// Table Management
let currentTableId = null;
let currentTableName = null;
const tableDisplay = document.getElementById('current-table-display');
const tableModalEl = document.getElementById('tableSelectionModal');
const tableSelectionModal = new bootstrap.Modal(tableModalEl);
// Customer Search Elements
const customerSearchInput = document.getElementById('customer-search');
const customerResults = document.getElementById('customer-results');
const selectedCustomerId = document.getElementById('selected-customer-id');
const clearCustomerBtn = document.getElementById('clear-customer');
const customerInfo = document.getElementById('customer-info');
const customerNameDisplay = document.getElementById('customer-name-display');
// Helper for currency formatting // Product Search & Filter
const productSearchInput = document.getElementById('product-search-input');
let currentCategory = 'all';
let currentSearchQuery = '';
// Helper for currency
function formatCurrency(amount) { function formatCurrency(amount) {
// Fallback if settings not defined
const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 }; const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 };
const symbol = settings.currency_symbol || '$'; const symbol = settings.currency_symbol || '$';
const decimals = parseInt(settings.currency_decimals || 2); const decimals = parseInt(settings.currency_decimals || 2);
return symbol + parseFloat(amount).toFixed(decimals); return symbol + parseFloat(amount).toFixed(decimals);
} }
// Add to cart // --- Product Filtering (Category + Search) ---
document.querySelectorAll('.add-to-cart').forEach(button => { function filterProducts() {
button.addEventListener('click', (e) => { const items = document.querySelectorAll('.product-item');
const product = { items.forEach(item => {
id: e.target.dataset.id, const matchesCategory = (currentCategory == 'all' || item.dataset.categoryId == currentCategory);
name: e.target.dataset.name, const name = item.querySelector('.card-title').textContent.toLowerCase();
price: parseFloat(e.target.dataset.price), const matchesSearch = name.includes(currentSearchQuery);
quantity: 1
};
const existing = cart.find(item => item.id === product.id); if (matchesCategory && matchesSearch) {
if (existing) { item.style.display = 'block';
existing.quantity++;
} else { } else {
cart.push(product); item.style.display = 'none';
}
});
}
window.filterCategory = function(categoryId, btnElement) {
currentCategory = categoryId;
// Update Active Button State
if (btnElement) {
document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active'));
btnElement.classList.add('active');
} else if (typeof btnElement === 'undefined' && categoryId !== 'all') {
// Try to find the button corresponding to this category ID
// This might happen if triggered programmatically
}
filterProducts();
};
if (productSearchInput) {
productSearchInput.addEventListener('input', (e) => {
currentSearchQuery = e.target.value.trim().toLowerCase();
filterProducts();
});
}
// --- Customer Search ---
let searchTimeout;
if (customerSearchInput) {
customerSearchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
clearTimeout(searchTimeout);
if (query.length < 2) {
customerResults.style.display = 'none';
return;
} }
updateCart(); searchTimeout = setTimeout(() => {
showToast(`${product.name} added to cart!`); fetch(`api/search_customers.php?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
customerResults.innerHTML = '';
if (data.length > 0) {
data.forEach(cust => {
const a = document.createElement('a');
a.href = '#';
a.className = 'list-group-item list-group-item-action';
a.innerHTML = `<div class="fw-bold">${cust.name}</div><div class="small text-muted">${cust.phone || ''}</div>`;
a.onclick = (ev) => {
ev.preventDefault();
selectCustomer(cust);
};
customerResults.appendChild(a);
});
customerResults.style.display = 'block';
} else {
customerResults.innerHTML = '<div class="list-group-item text-muted">No results found</div>';
customerResults.style.display = 'block';
}
});
}, 300);
});
// Close search on click outside
document.addEventListener('click', (e) => {
if (!customerSearchInput.contains(e.target) && !customerResults.contains(e.target)) {
customerResults.style.display = 'none';
}
});
}
function selectCustomer(cust) {
selectedCustomerId.value = cust.id;
customerNameDisplay.textContent = cust.name;
customerSearchInput.value = cust.name; // Show name in input
customerSearchInput.disabled = true; // Lock input
customerResults.style.display = 'none';
clearCustomerBtn.classList.remove('d-none');
// customerInfo.classList.remove('d-none');
}
if (clearCustomerBtn) {
clearCustomerBtn.addEventListener('click', () => {
selectedCustomerId.value = '';
customerSearchInput.value = '';
customerSearchInput.disabled = false;
clearCustomerBtn.classList.add('d-none');
customerInfo.classList.add('d-none');
customerSearchInput.focus();
});
}
// --- Table & Order Type Logic ---
const orderTypeInputs = document.querySelectorAll('input[name="order_type"]');
function checkOrderType() {
const checked = document.querySelector('input[name="order_type"]:checked');
if (!checked) return;
const selected = checked.value;
if (selected === 'dine-in') {
if (!currentTableId) openTableSelectionModal();
if (tableDisplay) tableDisplay.style.display = 'inline-block';
} else {
if (tableDisplay) tableDisplay.style.display = 'none';
}
}
orderTypeInputs.forEach(input => {
input.addEventListener('change', checkOrderType);
});
function openTableSelectionModal() {
const container = document.getElementById('table-list-container');
container.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary" role="status"></div></div>';
tableSelectionModal.show();
fetch('api/tables.php')
.then(res => res.json())
.then(data => {
if (data.success) {
renderTables(data.tables);
} else {
container.innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
}
})
.catch(() => {
container.innerHTML = `<div class="alert alert-danger">Error loading tables.</div>`;
});
}
function renderTables(tables) {
const container = document.getElementById('table-list-container');
container.innerHTML = '';
if (tables.length === 0) {
container.innerHTML = '<div class="col-12 text-center">No tables found.</div>';
return;
}
tables.forEach(table => {
const isOccupied = table.is_occupied;
const col = document.createElement('div');
col.className = 'col-6 col-md-4 col-lg-3';
col.innerHTML = `
<div class="card h-100 shadow-sm ${isOccupied ? 'bg-secondary text-white opacity-50' : 'border-primary text-primary'}"
style="cursor: ${isOccupied ? 'not-allowed' : 'pointer'}"
${!isOccupied ? `onclick="selectTable(${table.id}, '${table.name}')"` : ''}>
<div class="card-body text-center p-3">
<h5 class="fw-bold mb-1">${table.name}</h5>
<small>${table.capacity} Pax</small>
<div class="mt-2 badge ${isOccupied ? 'bg-dark' : 'bg-success'}">${isOccupied ? 'Occupied' : 'Available'}</div>
</div>
</div>
`;
container.appendChild(col);
});
}
window.selectTable = function(id, name) {
currentTableId = id;
currentTableName = name;
if (tableDisplay) {
tableDisplay.innerHTML = `Table: ${name}`;
tableDisplay.style.display = 'block';
}
tableSelectionModal.hide();
showToast(`Selected Table: ${name}`, 'success');
};
// --- Cart Logic ---
document.querySelectorAll('.add-to-cart').forEach(card => {
card.addEventListener('click', (e) => {
// Find closest card just in case click was on child
const target = e.currentTarget;
const product = {
id: target.dataset.id,
name: target.dataset.name,
price: parseFloat(target.dataset.price),
quantity: 1
};
addToCart(product);
}); });
}); });
function addToCart(product) {
const existing = cart.find(item => item.id === product.id);
if (existing) {
existing.quantity++;
} else {
cart.push(product);
}
updateCart();
}
window.changeQuantity = function(index, delta) {
if (cart[index]) {
cart[index].quantity += delta;
if (cart[index].quantity <= 0) {
removeFromCart(index);
} else {
updateCart();
}
}
};
function updateCart() { function updateCart() {
if (cart.length === 0) { if (cart.length === 0) {
cartItemsContainer.innerHTML = '<p class="text-center text-muted mt-5">Your cart is empty.</p>'; cartItemsContainer.innerHTML = `
<div class="text-center text-muted mt-5">
<i class="bi bi-basket3 fs-1 text-light"></i>
<p class="mt-2">Cart is empty</p>
</div>`;
cartSubtotal.innerText = formatCurrency(0);
cartTotalPrice.innerText = formatCurrency(0); cartTotalPrice.innerText = formatCurrency(0);
checkoutBtn.disabled = true; checkoutBtn.disabled = true;
if (mobileCartBtn) mobileCartBtn.innerText = `View Cart (${formatCurrency(0)})`;
return; return;
} }
cartItemsContainer.innerHTML = ''; cartItemsContainer.innerHTML = '';
let total = 0; let total = 0;
cart.forEach(item => {
cart.forEach((item, index) => {
const itemTotal = item.price * item.quantity; const itemTotal = item.price * item.quantity;
total += itemTotal; total += itemTotal;
const div = document.createElement('div');
div.className = 'cart-item'; const row = document.createElement('div');
div.innerHTML = ` row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2';
<div>
<div class="cart-item-name">${item.name} x${item.quantity}</div> // Updated Cart Item Layout
<div class="text-muted small">${formatCurrency(item.price)} ea</div> row.innerHTML = `
<div class="flex-grow-1 me-2">
<div class="fw-bold text-truncate" style="max-width: 140px;">${item.name}</div>
<div class="small text-muted">${formatCurrency(item.price)}</div>
</div>
<div class="d-flex align-items-center bg-light rounded px-1">
<button class="btn btn-sm text-secondary p-0" style="width: 24px;" onclick="changeQuantity(${index}, -1)"><i class="bi bi-dash"></i></button>
<span class="mx-1 fw-bold small" style="min-width: 20px; text-align: center;">${item.quantity}</span>
<button class="btn btn-sm text-secondary p-0" style="width: 24px;" onclick="changeQuantity(${index}, 1)"><i class="bi bi-plus"></i></button>
</div>
<div class="text-end ms-3" style="min-width: 60px;">
<div class="fw-bold">${formatCurrency(itemTotal)}</div>
<button class="btn btn-sm text-danger p-0 mt-1" style="font-size: 0.8rem;" onclick="removeFromCart(${index})">Remove</button>
</div> </div>
<div class="cart-item-price">${formatCurrency(itemTotal)}</div>
`; `;
cartItemsContainer.appendChild(div); cartItemsContainer.appendChild(row);
}); });
cartSubtotal.innerText = formatCurrency(total);
cartTotalPrice.innerText = formatCurrency(total); cartTotalPrice.innerText = formatCurrency(total);
checkoutBtn.disabled = false; checkoutBtn.disabled = false;
if (mobileCartBtn) mobileCartBtn.innerText = `View Cart (${formatCurrency(total)})`;
} }
// Checkout window.removeFromCart = function(index) {
cart.splice(index, 1);
updateCart();
};
// --- Checkout ---
checkoutBtn.addEventListener('click', () => { checkoutBtn.addEventListener('click', () => {
if (cart.length === 0) return; if (cart.length === 0) return;
const table_id = new URLSearchParams(window.location.search).get('table') || '1'; const orderTypeInput = document.querySelector('input[name="order_type"]:checked');
const orderType = orderTypeInput ? orderTypeInput.value : 'dine-in';
// Calculate total if (orderType === 'dine-in' && !currentTableId) {
showToast('Please select a table first', 'warning');
openTableSelectionModal();
return;
}
const totalAmount = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0); const totalAmount = cart.reduce((acc, item) => acc + (item.price * item.quantity), 0);
const custId = selectedCustomerId.value;
const orderData = { const orderData = {
table_number: table_id, table_number: (orderType === 'dine-in') ? currentTableId : null,
order_type: orderType,
customer_id: custId || null,
total_amount: totalAmount, total_amount: totalAmount,
// Only send necessary fields
items: cart.map(item => ({ items: cart.map(item => ({
product_id: item.id, product_id: item.id,
quantity: item.quantity, quantity: item.quantity,
@ -87,7 +332,10 @@ document.addEventListener('DOMContentLoaded', () => {
})) }))
}; };
// Use existing api/order.php // Show loading state
checkoutBtn.disabled = true;
checkoutBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Processing...';
fetch('api/order.php', { fetch('api/order.php', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -95,37 +343,42 @@ document.addEventListener('DOMContentLoaded', () => {
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
checkoutBtn.disabled = false;
checkoutBtn.innerHTML = 'Place Order <i class="bi bi-arrow-right ms-2"></i>';
if (data.success) { if (data.success) {
cart = []; cart = [];
updateCart(); updateCart();
showToast('Order placed successfully! Please wait for preparation.', 'success'); // Reset customer
if (clearCustomerBtn) clearCustomerBtn.click();
showToast(`Order #${data.order_id} placed!`, 'success');
} else { } else {
showToast('Error: ' + (data.error || 'Unknown error'), 'danger'); showToast(`Error: ${data.error}`, 'danger');
} }
}) })
.catch(err => { .catch(err => {
console.error(err); checkoutBtn.disabled = false;
showToast('Failed to place order.', 'danger'); checkoutBtn.innerHTML = 'Place Order';
showToast('Network Error', 'danger');
}); });
}); });
function showToast(message, type = 'dark') { function showToast(msg, type = 'primary') {
const toastContainer = document.getElementById('toast-container'); const toastContainer = document.getElementById('toast-container');
if (!toastContainer) return; const id = 'toast-' + Date.now();
const html = `
const toastId = 'toast-' + Date.now(); <div id="${id}" class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex"> <div class="d-flex">
<div class="toast-body">${message}</div> <div class="toast-body">${msg}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button> <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div> </div>
</div> </div>
`; `;
toastContainer.insertAdjacentHTML('beforeend', toastHtml); toastContainer.insertAdjacentHTML('beforeend', html);
const toastElement = document.getElementById(toastId); const el = document.getElementById(id);
const toast = new bootstrap.Toast(toastElement); const t = new bootstrap.Toast(el, { delay: 3000 });
toast.show(); t.show();
toastElement.addEventListener('hidden.bs.toast', () => toastElement.remove()); el.addEventListener('hidden.bs.toast', () => el.remove());
} }
}); });

224
index.php
View File

@ -15,83 +15,193 @@ $settings = get_company_settings();
<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>Menu - <?= htmlspecialchars($settings['company_name']) ?></title> <title><?= htmlspecialchars($settings['company_name']) ?> - POS</title>
<?php if (!empty($settings['favicon_url'])): ?>
<link rel="icon" href="<?= htmlspecialchars($settings['favicon_url']) ?>">
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<link 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;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>"> <link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
<style>
body { height: 100vh; overflow: hidden; } /* Fix body for scrolling areas */
.scrollable-y { overflow-y: auto; height: 100%; scrollbar-width: thin; }
.category-sidebar { height: calc(100vh - 60px); background: #f8f9fa; }
.product-area { height: calc(100vh - 60px); background: #fff; }
.cart-sidebar { height: calc(100vh - 60px); background: #fff; border-left: 1px solid #dee2e6; display: flex; flex-direction: column; }
.product-card { transition: transform 0.1s; cursor: pointer; }
.product-card:active { transform: scale(0.98); }
.category-btn { text-align: left; border: none; background: none; padding: 10px 15px; width: 100%; display: block; border-radius: 8px; color: #333; font-weight: 500; }
.category-btn:hover { background-color: #e9ecef; }
.category-btn.active { background-color: #0d6efd; color: white; }
.search-dropdown { position: absolute; width: 100%; z-index: 1000; max-height: 200px; overflow-y: auto; display: none; }
</style>
</head> </head>
<body> <body>
<nav class="navbar"> <!-- Navbar -->
<div class="container d-flex justify-content-between align-items-center"> <nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom shadow-sm" style="height: 60px;">
<a href="index.php" class="brand-logo"><?= htmlspecialchars($settings['company_name']) ?></a> <div class="container-fluid">
<div class="d-flex align-items-center"> <a href="index.php" class="navbar-brand d-flex align-items-center gap-2">
<span class="badge bg-dark me-3">Table <?= htmlspecialchars($table_id) ?></span> <?php if (!empty($settings['logo_url'])): ?>
<a href="admin/orders.php" class="btn btn-sm btn-outline-dark">Staff POS</a> <img src="<?= htmlspecialchars($settings['logo_url']) ?>" alt="Logo" style="height: 32px; width: auto;">
<?php endif; ?>
<span class="fw-bold d-none d-md-block"><?= htmlspecialchars($settings['company_name']) ?></span>
</a>
<div class="d-flex align-items-center gap-3">
<div id="current-table-display" class="badge bg-light text-dark border px-3 py-2" style="display: none; font-size: 0.9rem;">
Table <?= htmlspecialchars($table_id) ?>
</div>
<a href="admin/orders.php" class="btn btn-sm btn-outline-dark"><i class="bi bi-shield-lock"></i> Admin</a>
</div> </div>
</div> </div>
</nav> </nav>
<div class="container mt-4"> <div class="container-fluid p-0">
<div class="row"> <div class="row g-0">
<!-- Menu Column -->
<div class="col-lg-8"> <!-- Left Sidebar: Categories -->
<h1 class="mb-4 fw-bold">Order Now</h1> <div class="col-md-2 d-none d-md-block category-sidebar scrollable-y p-3 border-end">
<h6 class="text-uppercase text-muted small fw-bold mb-3 ms-1">Categories</h6>
<button class="category-btn active mb-1" onclick="filterCategory('all', this)">
<i class="bi bi-grid me-2"></i> All Items
</button>
<?php foreach ($categories as $category): ?> <?php foreach ($categories as $category): ?>
<h2 class="menu-category-title"><?= htmlspecialchars($category['name']) ?></h2> <button class="category-btn mb-1" onclick="filterCategory(<?= $category['id'] ?>, this)">
<div class="row g-4 mb-5"> <?php if (!empty($category['image_url'])): ?>
<?php foreach ($all_products as $product): <img src="<?= htmlspecialchars($category['image_url']) ?>" style="width: 20px; height: 20px; border-radius: 4px; object-fit: cover;" class="me-2">
if ($product['category_id'] == $category['id']): ?> <?php else: ?>
<div class="col-md-6 col-lg-4"> <i class="bi bi-tag me-2"></i>
<div class="product-card"> <?php endif; ?>
<img src="https://picsum.photos/seed/<?= $product['id'] ?>/400/300" alt="<?= htmlspecialchars($product['name']) ?>" class="product-image"> <?= htmlspecialchars($category['name']) ?>
<div class="product-info"> </button>
<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; ?> <?php endforeach; ?>
</div> </div>
<!-- Cart Column --> <!-- Middle: Products -->
<div class="col-lg-4 d-none d-lg-block"> <div class="col-md-7 col-12 product-area scrollable-y p-4 bg-light">
<div class="cart-sidebar"> <!-- Mobile Category Select (Visible only on small screens) -->
<h4 class="fw-bold mb-4">Your Order</h4> <div class="d-md-none mb-3">
<div id="cart-items" class="flex-grow-1 overflow-auto"> <select class="form-select" onchange="filterCategory(this.value)">
<p class="text-center text-muted mt-5">Your cart is empty.</p> <option value="all">All Categories</option>
</div> <?php foreach ($categories as $cat): ?>
<div class="cart-total mt-3"> <option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
<span>Total</span> <?php endforeach; ?>
<span id="cart-total-price"><?= format_currency(0) ?></span> </select>
</div> </div>
<button class="btn btn-dark w-100 btn-lg mt-4" id="checkout-btn" disabled>Place Order</button>
<!-- Product Search Bar -->
<div class="mb-3">
<div class="input-group shadow-sm">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
<input type="text" id="product-search-input" class="form-control border-start-0 ps-0" placeholder="Search products..." autocomplete="off">
</div>
</div>
<div class="row g-3" id="products-container">
<?php foreach ($all_products as $product): ?>
<div class="col-6 col-lg-3 col-xl-3 product-item" data-category-id="<?= $product['category_id'] ?>">
<div class="card h-100 border-0 shadow-sm product-card add-to-cart"
data-id="<?= $product['id'] ?>"
data-name="<?= htmlspecialchars($product['name']) ?>"
data-price="<?= $product['price'] ?>">
<div class="position-relative">
<img src="https://picsum.photos/seed/<?= $product['id'] ?>/300/200" class="card-img-top object-fit-cover" alt="..." style="height: 120px;">
<div class="position-absolute bottom-0 end-0 m-2">
<span class="badge bg-dark rounded-pill"><?= format_currency($product['price']) ?></span>
</div>
</div>
<div class="card-body p-2">
<h6 class="card-title fw-bold small mb-1 text-truncate"><?= htmlspecialchars($product['name']) ?></h6>
<p class="card-text small text-muted text-truncate mb-0"><?= htmlspecialchars($product['category_name']) ?></p>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Right: Cart & Order Info -->
<div class="col-md-3 col-12 cart-sidebar">
<!-- Top Section: Customer & Type -->
<div class="p-3 border-bottom bg-white">
<!-- Order Type -->
<div class="btn-group w-100 mb-3" role="group">
<input type="radio" class="btn-check" name="order_type" id="ot-dine-in" value="dine-in" checked>
<label class="btn btn-outline-primary btn-sm" for="ot-dine-in">Dine-In</label>
<input type="radio" class="btn-check" name="order_type" id="ot-takeaway" value="takeaway">
<label class="btn btn-outline-primary btn-sm" for="ot-takeaway">Takeaway</label>
<input type="radio" class="btn-check" name="order_type" id="ot-delivery" value="delivery">
<label class="btn btn-outline-primary btn-sm" for="ot-delivery">Delivery</label>
</div>
<!-- Customer Search -->
<div class="position-relative">
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-person"></i></span>
<input type="text" class="form-control border-start-0 ps-0" id="customer-search" placeholder="Search Customer..." autocomplete="off">
<button class="btn btn-outline-secondary d-none" type="button" id="clear-customer"><i class="bi bi-x"></i></button>
</div>
<div class="list-group shadow-sm search-dropdown" id="customer-results"></div>
<input type="hidden" id="selected-customer-id">
<div id="customer-info" class="small text-success mt-1 d-none">
<i class="bi bi-check-circle-fill me-1"></i> <span id="customer-name-display"></span>
</div>
</div>
</div>
<!-- Cart Items (Flex Grow) -->
<div class="flex-grow-1 overflow-auto p-3 bg-white" id="cart-items">
<div class="text-center text-muted mt-5">
<i class="bi bi-basket3 fs-1 text-light"></i>
<p class="mt-2">Cart is empty</p>
</div>
</div>
<!-- Bottom: Totals & Action -->
<div class="p-3 border-top bg-light">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal</span>
<span class="fw-bold" id="cart-subtotal"><?= format_currency(0) ?></span>
</div>
<!-- Tax could go here -->
<div class="d-flex justify-content-between mb-3">
<span class="fs-5 fw-bold">Total</span>
<span class="fs-4 fw-bold text-primary" id="cart-total-price"><?= format_currency(0) ?></span>
</div>
<button class="btn btn-primary w-100 btn-lg shadow-sm" id="checkout-btn" disabled>
Place Order <i class="bi bi-arrow-right ms-2"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container position-fixed bottom-0 start-50 translate-middle-x p-3" id="toast-container" style="z-index: 1060;"></div>
<!-- Table Selection Modal -->
<div class="modal fade" id="tableSelectionModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold">Select Table</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="close-table-modal" style="display:none;"></button>
</div>
<div class="modal-body bg-light">
<div id="table-list-container" class="row g-3"></div>
</div> </div>
</div> </div>
</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> <script>
const COMPANY_SETTINGS = <?= json_encode($settings) ?>; const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
</script> </script>