Autosave: 20260222-110909
This commit is contained in:
parent
3ce9ce30e6
commit
3d24190863
74
admin/area_edit.php
Normal file
74
admin/area_edit.php
Normal 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'; ?>
|
||||
@ -55,7 +55,8 @@ include 'includes/header.php';
|
||||
<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>
|
||||
<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>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -104,4 +105,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -2,29 +2,22 @@
|
||||
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();
|
||||
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order ASC, 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">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
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
@ -34,49 +27,45 @@ include 'includes/header.php';
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th style="width: 80px;">Image</th>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
<th>Sort Order</th>
|
||||
<th class="text-end pe-4">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>
|
||||
<?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>
|
||||
</td>
|
||||
</tr>
|
||||
<?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>
|
||||
</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'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
150
admin/category_edit.php
Normal file
150
admin/category_edit.php
Normal 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'; ?>
|
||||
@ -14,30 +14,69 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$vat_rate = $_POST['vat_rate'] ?? 0;
|
||||
$currency_symbol = $_POST['currency_symbol'] ?? '$';
|
||||
$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 {
|
||||
// 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();
|
||||
|
||||
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]);
|
||||
$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, $ctr_number, $vat_number, $logo_url, $favicon_url]);
|
||||
} 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]);
|
||||
$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, $ctr_number, $vat_number, $logo_url, $favicon_url]);
|
||||
}
|
||||
|
||||
$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
|
||||
];
|
||||
$settings = get_company_settings(); // Re-fetch to get updated values
|
||||
// Manually update immediate values for display if fetch is cached/laggy (though re-fetch is better)
|
||||
$settings['ctr_number'] = $ctr_number;
|
||||
$settings['vat_number'] = $vat_number;
|
||||
$settings['logo_url'] = $logo_url;
|
||||
$settings['favicon_url'] = $favicon_url;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$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-body">
|
||||
<form method="POST">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Company Name</label>
|
||||
@ -74,6 +113,19 @@ include 'includes/header.php';
|
||||
</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">
|
||||
<h5 class="mb-3">Financial Settings</h5>
|
||||
|
||||
@ -95,6 +147,36 @@ include 'includes/header.php';
|
||||
</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">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Save Changes
|
||||
@ -104,4 +186,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
81
admin/customer_edit.php
Normal file
81
admin/customer_edit.php
Normal 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
121
admin/customers.php
Normal 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'; ?>
|
||||
@ -1,8 +1,20 @@
|
||||
<?php
|
||||
// admin/includes/header.php
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
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
|
||||
function isActive($page) {
|
||||
return basename($_SERVER['PHP_SELF']) === $page ? 'active' : '';
|
||||
@ -13,7 +25,10 @@ function isActive($page) {
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
@ -41,7 +56,13 @@ function isActive($page) {
|
||||
<!-- 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>
|
||||
<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">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@ -50,9 +71,13 @@ function isActive($page) {
|
||||
|
||||
<!-- 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">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<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
|
||||
</a>
|
||||
</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">
|
||||
<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>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@ -113,4 +148,4 @@ function isActive($page) {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content pt-5 pt-md-4">
|
||||
<div class="main-content pt-5 pt-md-4">
|
||||
@ -128,8 +128,9 @@ include 'includes/header.php';
|
||||
<?php
|
||||
$badge = match($order['order_type']) {
|
||||
'dine-in' => 'bg-info',
|
||||
'takeaway' => 'bg-success',
|
||||
'delivery' => 'bg-warning',
|
||||
'drive-thru' => 'bg-purple', // custom class or just use primary
|
||||
'drive-thru' => 'bg-purple',
|
||||
default => 'bg-secondary'
|
||||
};
|
||||
?>
|
||||
@ -163,4 +164,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -35,6 +35,7 @@ include 'includes/header.php';
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Type</th>
|
||||
<th>Source</th>
|
||||
<th>Items</th>
|
||||
<th>Total</th>
|
||||
@ -48,10 +49,22 @@ include 'includes/header.php';
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $order['id'] ?></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>
|
||||
<?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; ?>
|
||||
</td>
|
||||
<td><small class="text-muted"><?= htmlspecialchars($order['items_summary']) ?></small></td>
|
||||
@ -91,7 +104,7 @@ include 'includes/header.php';
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($orders)): ?>
|
||||
<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>
|
||||
No active orders.
|
||||
</td>
|
||||
@ -103,4 +116,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
|
||||
93
admin/supplier_edit.php
Normal file
93
admin/supplier_edit.php
Normal 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
136
admin/suppliers.php
Normal 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
80
admin/table_edit.php
Normal 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'; ?>
|
||||
@ -57,7 +57,8 @@ include 'includes/header.php';
|
||||
<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>
|
||||
<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>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -110,4 +111,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -2,7 +2,8 @@
|
||||
header('Content-Type: application/json');
|
||||
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) {
|
||||
echo json_encode(['success' => false, 'error' => 'No data provided']);
|
||||
@ -13,20 +14,80 @@ 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']]);
|
||||
// Validate order_type against allowed ENUM values
|
||||
$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();
|
||||
|
||||
$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']]);
|
||||
if (!empty($data['items']) && is_array($data['items'])) {
|
||||
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();
|
||||
echo json_encode(['success' => true, 'order_id' => $order_id]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log("Order Error: " . $e->getMessage());
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
23
api/search_customers.php
Normal file
23
api/search_customers.php
Normal 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
39
api/tables.php
Normal 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()]);
|
||||
}
|
||||
BIN
assets/images/categories/cat_699ad97b80a9f.jpg
Normal file
BIN
assets/images/categories/cat_699ad97b80a9f.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/categories/cat_699ad9936c7f7.jpeg
Normal file
BIN
assets/images/categories/cat_699ad9936c7f7.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/images/categories/cat_699ad9aca9529.jpeg
Normal file
BIN
assets/images/categories/cat_699ad9aca9529.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/images/categories/cat_699ad9c24cfdf.jpeg
Normal file
BIN
assets/images/categories/cat_699ad9c24cfdf.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/company/favicon_699ada16cc653.png
Normal file
BIN
assets/images/company/favicon_699ada16cc653.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/images/company/logo_699ada16cc482.png
Normal file
BIN
assets/images/company/logo_699ada16cc482.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@ -2,84 +2,329 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let cart = [];
|
||||
const cartItemsContainer = document.getElementById('cart-items');
|
||||
const cartTotalPrice = document.getElementById('cart-total-price');
|
||||
const cartSubtotal = document.getElementById('cart-subtotal');
|
||||
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) {
|
||||
// Fallback if settings not defined
|
||||
const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2 };
|
||||
const symbol = settings.currency_symbol || '$';
|
||||
const decimals = parseInt(settings.currency_decimals || 2);
|
||||
return symbol + parseFloat(amount).toFixed(decimals);
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
// --- Product Filtering (Category + Search) ---
|
||||
function filterProducts() {
|
||||
const items = document.querySelectorAll('.product-item');
|
||||
items.forEach(item => {
|
||||
const matchesCategory = (currentCategory == 'all' || item.dataset.categoryId == currentCategory);
|
||||
const name = item.querySelector('.card-title').textContent.toLowerCase();
|
||||
const matchesSearch = name.includes(currentSearchQuery);
|
||||
|
||||
const existing = cart.find(item => item.id === product.id);
|
||||
if (existing) {
|
||||
existing.quantity++;
|
||||
if (matchesCategory && matchesSearch) {
|
||||
item.style.display = 'block';
|
||||
} 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();
|
||||
showToast(`${product.name} added to cart!`);
|
||||
searchTimeout = setTimeout(() => {
|
||||
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() {
|
||||
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);
|
||||
checkoutBtn.disabled = true;
|
||||
if (mobileCartBtn) mobileCartBtn.innerText = `View Cart (${formatCurrency(0)})`;
|
||||
return;
|
||||
}
|
||||
|
||||
cartItemsContainer.innerHTML = '';
|
||||
let total = 0;
|
||||
cart.forEach(item => {
|
||||
|
||||
cart.forEach((item, index) => {
|
||||
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>
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2';
|
||||
|
||||
// Updated Cart Item Layout
|
||||
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 class="cart-item-price">${formatCurrency(itemTotal)}</div>
|
||||
`;
|
||||
cartItemsContainer.appendChild(div);
|
||||
cartItemsContainer.appendChild(row);
|
||||
});
|
||||
|
||||
cartSubtotal.innerText = formatCurrency(total);
|
||||
cartTotalPrice.innerText = formatCurrency(total);
|
||||
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', () => {
|
||||
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 custId = selectedCustomerId.value;
|
||||
|
||||
const orderData = {
|
||||
table_number: table_id,
|
||||
table_number: (orderType === 'dine-in') ? currentTableId : null,
|
||||
order_type: orderType,
|
||||
customer_id: custId || null,
|
||||
total_amount: totalAmount,
|
||||
// Only send necessary fields
|
||||
items: cart.map(item => ({
|
||||
product_id: item.id,
|
||||
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', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@ -95,37 +343,42 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
checkoutBtn.disabled = false;
|
||||
checkoutBtn.innerHTML = 'Place Order <i class="bi bi-arrow-right ms-2"></i>';
|
||||
|
||||
if (data.success) {
|
||||
cart = [];
|
||||
updateCart();
|
||||
showToast('Order placed successfully! Please wait for preparation.', 'success');
|
||||
// Reset customer
|
||||
if (clearCustomerBtn) clearCustomerBtn.click();
|
||||
|
||||
showToast(`Order #${data.order_id} placed!`, 'success');
|
||||
} else {
|
||||
showToast('Error: ' + (data.error || 'Unknown error'), 'danger');
|
||||
showToast(`Error: ${data.error}`, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
showToast('Failed to place order.', 'danger');
|
||||
checkoutBtn.disabled = false;
|
||||
checkoutBtn.innerHTML = 'Place Order';
|
||||
showToast('Network Error', 'danger');
|
||||
});
|
||||
});
|
||||
|
||||
function showToast(message, type = 'dark') {
|
||||
function showToast(msg, type = 'primary') {
|
||||
const toastContainer = document.getElementById('toast-container');
|
||||
if (!toastContainer) return;
|
||||
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const toastHtml = `
|
||||
<div id="${toastId}" class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
const id = 'toast-' + Date.now();
|
||||
const html = `
|
||||
<div id="${id}" 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 class="toast-body">${msg}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></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());
|
||||
toastContainer.insertAdjacentHTML('beforeend', html);
|
||||
const el = document.getElementById(id);
|
||||
const t = new bootstrap.Toast(el, { delay: 3000 });
|
||||
t.show();
|
||||
el.addEventListener('hidden.bs.toast', () => el.remove());
|
||||
}
|
||||
});
|
||||
224
index.php
224
index.php
@ -15,83 +15,193 @@ $settings = get_company_settings();
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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-icons@1.10.5/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;600;700&display=swap" rel="stylesheet">
|
||||
<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>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="container d-flex justify-content-between align-items-center">
|
||||
<a href="index.php" class="brand-logo"><?= htmlspecialchars($settings['company_name']) ?></a>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-dark me-3">Table <?= htmlspecialchars($table_id) ?></span>
|
||||
<a href="admin/orders.php" class="btn btn-sm btn-outline-dark">Staff POS</a>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom shadow-sm" style="height: 60px;">
|
||||
<div class="container-fluid">
|
||||
<a href="index.php" class="navbar-brand d-flex align-items-center gap-2">
|
||||
<?php if (!empty($settings['logo_url'])): ?>
|
||||
<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>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<!-- Menu Column -->
|
||||
<div class="col-lg-8">
|
||||
<h1 class="mb-4 fw-bold">Order Now</h1>
|
||||
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row g-0">
|
||||
|
||||
<!-- Left Sidebar: Categories -->
|
||||
<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): ?>
|
||||
<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>
|
||||
<button class="category-btn mb-1" onclick="filterCategory(<?= $category['id'] ?>, this)">
|
||||
<?php if (!empty($category['image_url'])): ?>
|
||||
<img src="<?= htmlspecialchars($category['image_url']) ?>" style="width: 20px; height: 20px; border-radius: 4px; object-fit: cover;" class="me-2">
|
||||
<?php else: ?>
|
||||
<i class="bi bi-tag me-2"></i>
|
||||
<?php endif; ?>
|
||||
<?= htmlspecialchars($category['name']) ?>
|
||||
</button>
|
||||
<?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>
|
||||
<!-- Middle: Products -->
|
||||
<div class="col-md-7 col-12 product-area scrollable-y p-4 bg-light">
|
||||
<!-- Mobile Category Select (Visible only on small screens) -->
|
||||
<div class="d-md-none mb-3">
|
||||
<select class="form-select" onchange="filterCategory(this.value)">
|
||||
<option value="all">All Categories</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user