Autosave: 20260223-061939
This commit is contained in:
parent
e5617b6c15
commit
37dfb898e7
146
admin/ad_edit.php
Normal file
146
admin/ad_edit.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||||
|
$ad = null;
|
||||||
|
$message = '';
|
||||||
|
$isEdit = false;
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM ads_images WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$ad = $stmt->fetch();
|
||||||
|
if ($ad) {
|
||||||
|
$isEdit = true;
|
||||||
|
} else {
|
||||||
|
header("Location: ads.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$title = trim($_POST['title']);
|
||||||
|
$sort_order = (int)$_POST['sort_order'];
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
$image_path = $isEdit ? $ad['image_path'] : null;
|
||||||
|
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$uploadDir = __DIR__ . '/../assets/images/ads/';
|
||||||
|
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('ad_') . '.' . $fileExt;
|
||||||
|
$targetFile = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
|
||||||
|
$image_path = 'assets/images/ads/' . $fileName;
|
||||||
|
} else {
|
||||||
|
$message = '<div class="alert alert-danger">Failed to upload image.</div>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message = '<div class="alert alert-danger">Invalid file type. Allowed: jpg, png, gif, webp.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($image_path) && !$isEdit) {
|
||||||
|
$message = '<div class="alert alert-danger">Image is required for new advertisements.</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($message)) {
|
||||||
|
try {
|
||||||
|
if ($isEdit) {
|
||||||
|
$stmt = $pdo->prepare("UPDATE ads_images SET title = ?, sort_order = ?, is_active = ?, image_path = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$title, $sort_order, $is_active, $image_path, $id]);
|
||||||
|
header("Location: ads.php?success=updated");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO ads_images (title, sort_order, is_active, image_path) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$title, $sort_order, $is_active, $image_path]);
|
||||||
|
header("Location: ads.php?success=created");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isEdit) {
|
||||||
|
$ad = [
|
||||||
|
'title' => $_POST['title'] ?? '',
|
||||||
|
'sort_order' => $_POST['sort_order'] ?? 0,
|
||||||
|
'is_active' => 1,
|
||||||
|
'image_path' => ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="ads.php" class="text-decoration-none text-muted mb-2 d-inline-block"><i class="bi bi-arrow-left"></i> Back to Ads Management</a>
|
||||||
|
<h2 class="fw-bold mb-0"><?= $isEdit ? 'Edit Advertisement' : 'Add New Advertisement' ?></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">Title / Caption</label>
|
||||||
|
<input type="text" name="title" class="form-control" value="<?= htmlspecialchars($ad['title'] ?? '') ?>" placeholder="e.g. Special Offer 50% Off">
|
||||||
|
<div class="form-text">This will be shown as a caption on the image.</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Sort Order</label>
|
||||||
|
<input type="number" name="sort_order" class="form-control" value="<?= htmlspecialchars($ad['sort_order'] ?? 0) ?>">
|
||||||
|
<div class="form-text">Lower numbers appear first in the slider.</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" <?= ($ad['is_active'] ?? 1) ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label" for="isActiveSwitch">Active (Show in Slider)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Advertisement Image <span class="text-danger">*</span></label>
|
||||||
|
<?php if (!empty($ad['image_path'])): ?>
|
||||||
|
<div class="mb-2">
|
||||||
|
<img src="../<?= htmlspecialchars($ad['image_path']) ?>"
|
||||||
|
class="img-fluid rounded border shadow-sm"
|
||||||
|
style="max-height: 250px; width: 100%; object-fit: cover;"
|
||||||
|
alt="Ad Image">
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="mb-2 p-5 bg-light text-center border rounded text-muted">
|
||||||
|
<i class="bi bi-images fs-1"></i><br>No Image Selected
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<input type="file" name="image" class="form-control" accept="image/*" <?= !$isEdit ? 'required' : '' ?>>
|
||||||
|
<div class="form-text">Recommended size: 1920x1080 (HD) or 16:9 aspect ratio.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-end gap-2">
|
||||||
|
<a href="ads.php" class="btn btn-secondary">Cancel</a>
|
||||||
|
<button type="submit" class="btn btn-primary"><?= $isEdit ? 'Save Changes' : 'Upload Image' ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
120
admin/ads.php
Normal file
120
admin/ads.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Ensure the table exists (idempotent)
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS ads_images (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
image_path VARCHAR(255) NOT NULL,
|
||||||
|
title VARCHAR(255) DEFAULT NULL,
|
||||||
|
sort_order INT DEFAULT 0,
|
||||||
|
is_active TINYINT(1) DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)");
|
||||||
|
|
||||||
|
if (isset($_GET['delete'])) {
|
||||||
|
$id = $_GET['delete'];
|
||||||
|
|
||||||
|
// Get image path to delete file
|
||||||
|
$stmt = $pdo->prepare("SELECT image_path FROM ads_images WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$ad = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($ad) {
|
||||||
|
$fullPath = __DIR__ . '/../' . $ad['image_path'];
|
||||||
|
if (file_exists($fullPath) && is_file($fullPath)) {
|
||||||
|
unlink($fullPath);
|
||||||
|
}
|
||||||
|
$pdo->prepare("DELETE FROM ads_images WHERE id = ?")->execute([$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: ads.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT * FROM ads_images ORDER BY sort_order ASC, created_at DESC";
|
||||||
|
$ads_pagination = paginate_query($pdo, $query);
|
||||||
|
$ads = $ads_pagination['data'];
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold mb-0">Advertisement Slider</h2>
|
||||||
|
<p class="text-muted mb-0">Manage pictures for the public ads display page.</p>
|
||||||
|
</div>
|
||||||
|
<a href="ad_edit.php" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-lg"></i> Add Image
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info border-0 shadow-sm d-flex align-items-center">
|
||||||
|
<i class="bi bi-info-circle-fill me-3 fs-4"></i>
|
||||||
|
<div>
|
||||||
|
These images will be displayed in a slider on the <strong><a href="../ads.php" target="_blank" class="alert-link">ads.php</a></strong> page.
|
||||||
|
You can upload up to 7 images for optimal performance.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="p-3 border-bottom bg-light">
|
||||||
|
<?php render_pagination_controls($ads_pagination); ?>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">Order</th>
|
||||||
|
<th style="width: 150px;">Preview</th>
|
||||||
|
<th>Title / Caption</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th class="text-end pe-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($ads as $ad): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4 fw-medium"><?= $ad['sort_order'] ?></td>
|
||||||
|
<td>
|
||||||
|
<img src="../<?= htmlspecialchars($ad['image_path']) ?>"
|
||||||
|
class="rounded object-fit-cover border shadow-sm"
|
||||||
|
width="120" height="70"
|
||||||
|
alt="<?= htmlspecialchars($ad['title'] ?? '') ?>">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?= htmlspecialchars($ad['title'] ?: 'No title') ?></div>
|
||||||
|
<small class="text-muted"><?= htmlspecialchars($ad['image_path']) ?></small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($ad['is_active']): ?>
|
||||||
|
<span class="badge bg-success-subtle text-success px-3">Active</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary px-3">Inactive</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<a href="ad_edit.php?id=<?= $ad['id'] ?>" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i></a>
|
||||||
|
<a href="?delete=<?= $ad['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this image?')"><i class="bi bi-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($ads)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-5 text-muted">
|
||||||
|
<i class="bi bi-images fs-1 d-block mb-3"></i>
|
||||||
|
No advertisement images found. Click "Add Image" to get started.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 border-top bg-light">
|
||||||
|
<?php render_pagination_controls($ads_pagination); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
@ -2,8 +2,14 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Global SweetAlert2 replacement for confirm()
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize all dropdowns manually to ensure they work
|
||||||
|
var dropdownElementList = [].slice.call(document.querySelectorAll('.dropdown-toggle'))
|
||||||
|
var dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
|
||||||
|
return new bootstrap.Dropdown(dropdownToggleEl)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global SweetAlert2 replacement for confirm()
|
||||||
const confirmLinks = document.querySelectorAll('a[onclick^="return confirm"]');
|
const confirmLinks = document.querySelectorAll('a[onclick^="return confirm"]');
|
||||||
|
|
||||||
confirmLinks.forEach(link => {
|
confirmLinks.forEach(link => {
|
||||||
|
|||||||
@ -2,14 +2,25 @@
|
|||||||
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')) {
|
// Ensure functions are available
|
||||||
// Attempt to locate config relative to this header file
|
|
||||||
if (file_exists(__DIR__ . '/../../db/config.php')) {
|
if (file_exists(__DIR__ . '/../../db/config.php')) {
|
||||||
require_once __DIR__ . '/../../db/config.php';
|
require_once __DIR__ . '/../../db/config.php';
|
||||||
}
|
}
|
||||||
|
if (file_exists(__DIR__ . '/../../includes/functions.php')) {
|
||||||
|
require_once __DIR__ . '/../../includes/functions.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Require login for all admin pages
|
||||||
|
if (function_exists('require_login')) {
|
||||||
|
require_login();
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentUser = function_exists('get_logged_user') ? get_logged_user() : null;
|
||||||
|
$userName = $currentUser['full_name'] ?? ($currentUser['username'] ?? 'Admin');
|
||||||
|
$userGroup = $currentUser['group_name'] ?? 'System';
|
||||||
|
$userInitial = strtoupper(substr($userName, 0, 1));
|
||||||
|
|
||||||
$companySettings = function_exists('get_company_settings') ? get_company_settings() : [];
|
$companySettings = function_exists('get_company_settings') ? get_company_settings() : [];
|
||||||
$companyName = $companySettings['company_name'] ?? 'Foody';
|
$companyName = $companySettings['company_name'] ?? 'Foody';
|
||||||
$logoUrl = $companySettings['logo_url'] ?? '';
|
$logoUrl = $companySettings['logo_url'] ?? '';
|
||||||
@ -19,6 +30,19 @@ $faviconUrl = $companySettings['favicon_url'] ?? '';
|
|||||||
function isActive($page) {
|
function isActive($page) {
|
||||||
return basename($_SERVER['PHP_SELF']) === $page ? 'active' : '';
|
return basename($_SERVER['PHP_SELF']) === $page ? 'active' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group active helper
|
||||||
|
function isGroupActive($pages) {
|
||||||
|
return in_array(basename($_SERVER['PHP_SELF']), $pages) ? 'show' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGroupExpanded($pages) {
|
||||||
|
return in_array(basename($_SERVER['PHP_SELF']), $pages) ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupToggleClass($pages) {
|
||||||
|
return in_array(basename($_SERVER['PHP_SELF']), $pages) ? '' : 'collapsed';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -86,6 +110,14 @@ function isActive($page) {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer; /* Clickable */
|
||||||
|
width: 100%;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.sidebar-heading:hover {
|
||||||
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
.sidebar-heading i {
|
.sidebar-heading i {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@ -189,7 +221,7 @@ function isActive($page) {
|
|||||||
<h5 class="fw-bold" style="color: var(--text-primary);">Menu</h5>
|
<h5 class="fw-bold" style="color: var(--text-primary);">Menu</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content accordion" id="sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('index.php') ?>" href="index.php">
|
<a class="nav-link <?= isActive('index.php') ?>" href="index.php">
|
||||||
@ -198,7 +230,14 @@ function isActive($page) {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h6 class="sidebar-heading"><i class="bi bi-shop"></i> POS & Operations</h6>
|
<?php $posGroup = ['orders.php', 'ads.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($posGroup) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapsePos" role="button" aria-expanded="<?= isGroupExpanded($posGroup) ?>" aria-controls="collapsePos">
|
||||||
|
<span><i class="bi bi-shop"></i> POS & Operations</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($posGroup) ?>" id="collapsePos" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="../pos.php" target="_blank">
|
<a class="nav-link" href="../pos.php" target="_blank">
|
||||||
@ -216,8 +255,17 @@ function isActive($page) {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="sidebar-heading"><i class="bi bi-menu-button-wide"></i> Menu Management</h6>
|
<?php $menuGroup = ['products.php', 'product_edit.php', 'categories.php', 'category_edit.php', 'product_variants.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($menuGroup) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapseMenu" role="button" aria-expanded="<?= isGroupExpanded($menuGroup) ?>" aria-controls="collapseMenu">
|
||||||
|
<span><i class="bi bi-menu-button-wide"></i> Menu Management</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($menuGroup) ?>" id="collapseMenu" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('products.php') ?>" href="products.php">
|
<a class="nav-link <?= isActive('products.php') ?>" href="products.php">
|
||||||
@ -230,8 +278,17 @@ function isActive($page) {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="sidebar-heading"><i class="bi bi-gear-wide-connected"></i> Restaurant Setup</h6>
|
<?php $setupGroup = ['outlets.php', 'outlet_edit.php', 'areas.php', 'area_edit.php', 'tables.php', 'table_edit.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($setupGroup) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapseSetup" role="button" aria-expanded="<?= isGroupExpanded($setupGroup) ?>" aria-controls="collapseSetup">
|
||||||
|
<span><i class="bi bi-gear-wide-connected"></i> Restaurant Setup</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($setupGroup) ?>" id="collapseSetup" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('outlets.php') ?>" href="outlets.php">
|
<a class="nav-link <?= isActive('outlets.php') ?>" href="outlets.php">
|
||||||
@ -249,8 +306,17 @@ function isActive($page) {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="sidebar-heading"><i class="bi bi-people"></i> People & Partners</h6>
|
<?php $peopleGroup = ['customers.php', 'customer_edit.php', 'suppliers.php', 'supplier_edit.php', 'loyalty.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($peopleGroup) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapsePeople" role="button" aria-expanded="<?= isGroupExpanded($peopleGroup) ?>" aria-controls="collapsePeople">
|
||||||
|
<span><i class="bi bi-people"></i> People & Partners</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($peopleGroup) ?>" id="collapsePeople" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('customers.php') ?>" href="customers.php">
|
<a class="nav-link <?= isActive('customers.php') ?>" href="customers.php">
|
||||||
@ -268,8 +334,40 @@ function isActive($page) {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="sidebar-heading"><i class="bi bi-sliders"></i> Settings</h6>
|
<?php $userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($userGroupPages) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapseUsers" role="button" aria-expanded="<?= isGroupExpanded($userGroupPages) ?>" aria-controls="collapseUsers">
|
||||||
|
<span><i class="bi bi-person-badge"></i> User Management</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($userGroupPages) ?>" id="collapseUsers" data-bs-parent="#sidebarAccordion">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('users.php') ?>" href="users.php">
|
||||||
|
<i class="bi bi-people me-2"></i> Users
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php">
|
||||||
|
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $settingsGroup = ['payment_types.php', 'payment_type_edit.php', 'integrations.php', 'company.php']; ?>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($settingsGroup) ?>"
|
||||||
|
data-bs-toggle="collapse" href="#collapseSettings" role="button" aria-expanded="<?= isGroupExpanded($settingsGroup) ?>" aria-controls="collapseSettings">
|
||||||
|
<span><i class="bi bi-sliders"></i> Settings</span>
|
||||||
|
<i class="bi bi-chevron-down chevron-icon"></i>
|
||||||
|
</a>
|
||||||
|
<div class="collapse <?= isGroupActive($settingsGroup) ?>" id="collapseSettings" data-bs-parent="#sidebarAccordion">
|
||||||
<ul class="nav flex-column mb-5">
|
<ul class="nav flex-column mb-5">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('payment_types.php') ?>" href="payment_types.php">
|
<a class="nav-link <?= isActive('payment_types.php') ?>" href="payment_types.php">
|
||||||
@ -295,6 +393,8 @@ function isActive($page) {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="main-content pt-5 pt-md-4">
|
<div class="main-content pt-5 pt-md-4">
|
||||||
<!-- Top Header -->
|
<!-- Top Header -->
|
||||||
@ -319,15 +419,15 @@ function isActive($page) {
|
|||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a href="#" class="d-flex align-items-center text-decoration-none dropdown-toggle" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false" style="color: var(--text-primary);">
|
<a href="#" class="d-flex align-items-center text-decoration-none dropdown-toggle" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false" style="color: var(--text-primary);">
|
||||||
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center text-white me-2 shadow-sm" style="width:38px;height:38px; font-weight:600;">A</div>
|
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center text-white me-2 shadow-sm" style="width:38px;height:38px; font-weight:600;"><?= $userInitial ?></div>
|
||||||
<span class="d-none d-sm-inline fw-medium">Admin</span>
|
<span class="d-none d-sm-inline fw-medium"><?= htmlspecialchars($userName) ?></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0" aria-labelledby="userDropdown">
|
<ul class="dropdown-menu dropdown-menu-end shadow border-0" aria-labelledby="userDropdown">
|
||||||
<li><span class="dropdown-item-text text-muted small">Signed in as Admin</span></li>
|
<li><span class="dropdown-item-text text-muted small">Signed in as <?= htmlspecialchars($userGroup) ?></span></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></li>
|
<li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
|
<li><a class="dropdown-item text-danger" href="/logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ 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">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="fw-bold mb-1">Dashboard</h2>
|
<h2 class="fw-bold mb-1">Dashboard</h2>
|
||||||
<p class="text-muted">Welcome back, Admin!</p>
|
<p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="orders.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
<a href="orders.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
||||||
|
|||||||
@ -28,9 +28,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
// Wablas
|
// Wablas
|
||||||
if ($provider === 'wablas') {
|
if ($provider === 'wablas') {
|
||||||
$keys = ['domain', 'token', 'secret_key', 'order_template'];
|
$keys = ['domain', 'token', 'secret_key', 'order_template', 'is_enabled'];
|
||||||
foreach ($keys as $k) {
|
foreach ($keys as $k) {
|
||||||
$val = $_POST[$k] ?? '';
|
$val = $_POST[$k] ?? '0';
|
||||||
|
if ($k === 'is_enabled' && !isset($_POST[$k])) {
|
||||||
|
$val = '0';
|
||||||
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO integration_settings (provider, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
$stmt = $pdo->prepare("INSERT INTO integration_settings (provider, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
||||||
$stmt->execute(['wablas', $k, $val]);
|
$stmt->execute(['wablas', $k, $val]);
|
||||||
}
|
}
|
||||||
@ -73,6 +76,7 @@ $wablasDom = getSetting($allSettings, 'wablas', 'domain');
|
|||||||
$wablasTok = getSetting($allSettings, 'wablas', 'token');
|
$wablasTok = getSetting($allSettings, 'wablas', 'token');
|
||||||
$wablasSecKey = getSetting($allSettings, 'wablas', 'secret_key');
|
$wablasSecKey = getSetting($allSettings, 'wablas', 'secret_key');
|
||||||
$wablasTemplate = getSetting($allSettings, 'wablas', 'order_template');
|
$wablasTemplate = getSetting($allSettings, 'wablas', 'order_template');
|
||||||
|
$wablasEnabled = getSetting($allSettings, 'wablas', 'is_enabled');
|
||||||
|
|
||||||
// Default template if empty
|
// Default template if empty
|
||||||
if (empty($wablasTemplate)) {
|
if (empty($wablasTemplate)) {
|
||||||
@ -147,10 +151,16 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
<div class="card shadow h-100">
|
<div class="card shadow h-100">
|
||||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||||
<h6 class="m-0 fw-bold text-success">Wablas WhatsApp</h6>
|
<h6 class="m-0 fw-bold text-success">Wablas WhatsApp</h6>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_enabled" id="is_enabled_switch" form="wablas_form" value="1" <?= $wablasEnabled === '1' ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label" for="is_enabled_switch">Enabled</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST" id="wablas_form">
|
||||||
<input type="hidden" name="provider" value="wablas">
|
<input type="hidden" name="provider" value="wablas">
|
||||||
|
|
||||||
|
<!-- Also keep a hidden input to send '0' if checkbox is unchecked (handled in PHP POST block too) -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Domain</label>
|
<label class="form-label">Domain</label>
|
||||||
<input type="text" class="form-control" name="domain" placeholder="https://..." value="<?= htmlspecialchars($wablasDom) ?>">
|
<input type="text" class="form-control" name="domain" placeholder="https://..." value="<?= htmlspecialchars($wablasDom) ?>">
|
||||||
|
|||||||
@ -38,9 +38,6 @@ if (!empty($_GET['end_date'])) {
|
|||||||
|
|
||||||
// Filter: Search (Order No)
|
// Filter: Search (Order No)
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
// Exact match for ID usually, but LIKE might be more user friendly if they type partial?
|
|
||||||
// "search by order no" usually implies ID. Let's stick to ID or simple LIKE.
|
|
||||||
// If numeric, assume ID.
|
|
||||||
if (is_numeric($_GET['search'])) {
|
if (is_numeric($_GET['search'])) {
|
||||||
$where[] = "o.id = :search";
|
$where[] = "o.id = :search";
|
||||||
$params[':search'] = $_GET['search'];
|
$params[':search'] = $_GET['search'];
|
||||||
@ -49,8 +46,13 @@ if (!empty($_GET['search'])) {
|
|||||||
|
|
||||||
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||||
|
|
||||||
// Changed alias 'out' to 'ot' to avoid reserved keyword conflict
|
// Calculate Total Sum for filtered orders
|
||||||
// Added join to payment_types to get payment name
|
$sum_query = "SELECT SUM(total_amount) as total_sum FROM orders o $where_clause";
|
||||||
|
$stmt_sum = $pdo->prepare($sum_query);
|
||||||
|
$stmt_sum->execute($params);
|
||||||
|
$total_sum = $stmt_sum->fetchColumn() ?: 0;
|
||||||
|
|
||||||
|
// Main Query
|
||||||
$query = "SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
$query = "SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
||||||
(SELECT GROUP_CONCAT(CONCAT(p.name, ' x', oi.quantity) SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items_summary
|
(SELECT GROUP_CONCAT(CONCAT(p.name, ' x', oi.quantity) SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items_summary
|
||||||
FROM orders o
|
FROM orders o
|
||||||
@ -62,6 +64,9 @@ $query = "SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
|
|||||||
$orders_pagination = paginate_query($pdo, $query, $params);
|
$orders_pagination = paginate_query($pdo, $query, $params);
|
||||||
$orders = $orders_pagination['data'];
|
$orders = $orders_pagination['data'];
|
||||||
|
|
||||||
|
// Add total sum to pagination object for rendering
|
||||||
|
$orders_pagination['total_amount_sum'] = $total_sum;
|
||||||
|
|
||||||
include 'includes/header.php';
|
include 'includes/header.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@ -72,6 +77,59 @@ include 'includes/header.php';
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary Stats -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0 bg-primary bg-opacity-10 text-primary p-3 rounded">
|
||||||
|
<i class="bi bi-currency-dollar fs-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Total Revenue</h6>
|
||||||
|
<div class="fs-4 fw-bold text-primary"><?= format_currency($total_sum) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0 bg-success bg-opacity-10 text-success p-3 rounded">
|
||||||
|
<i class="bi bi-receipt fs-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Total Orders</h6>
|
||||||
|
<div class="fs-4 fw-bold text-success"><?= $orders_pagination['total_rows'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0 bg-info bg-opacity-10 text-info p-3 rounded">
|
||||||
|
<i class="bi bi-calendar-event fs-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-0 small text-uppercase fw-bold">Date Range</h6>
|
||||||
|
<div class="small fw-bold">
|
||||||
|
<?= !empty($_GET['start_date']) ? date('M d, Y', strtotime($_GET['start_date'])) : 'Start' ?>
|
||||||
|
-
|
||||||
|
<?= !empty($_GET['end_date']) ? date('M d, Y', strtotime($_GET['end_date'])) : 'Today' ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-body bg-light">
|
<div class="card-body bg-light">
|
||||||
@ -148,9 +206,9 @@ include 'includes/header.php';
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if (!empty($order['customer_name'])): ?>
|
<?php if (!empty($order['customer_name'])): ?>
|
||||||
<div><?= htmlspecialchars($order['customer_name']) ?></div>
|
<div><?= htmlspecialchars((string)($order['customer_name'] ?? '')) ?></div>
|
||||||
<?php if (!empty($order['customer_phone'])): ?>
|
<?php if (!empty($order['customer_phone'])): ?>
|
||||||
<small class="text-muted"><i class="bi bi-telephone me-1"></i><?= htmlspecialchars($order['customer_phone']) ?></small>
|
<small class="text-muted"><i class="bi bi-telephone me-1"></i><?= htmlspecialchars((string)($order['customer_phone'] ?? '')) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="text-muted small">Guest</span>
|
<span class="text-muted small">Guest</span>
|
||||||
@ -170,12 +228,12 @@ include 'includes/header.php';
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($order['order_type'] === 'dine-in' && $order['table_number']): ?>
|
<?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((string)($order['table_number'] ?? '')) ?></span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="badge bg-light text-dark border"><?= 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((string)($order['items_summary'] ?? '')) ?></small></td>
|
||||||
<td><?= format_currency($order['total_amount']) ?></td>
|
<td><?= format_currency($order['total_amount']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
@ -189,7 +247,7 @@ include 'includes/header.php';
|
|||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
<span class="badge <?= $payment_badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $payment_badge) ?>">
|
<span class="badge <?= $payment_badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $payment_badge) ?>">
|
||||||
<?= htmlspecialchars($payment_name) ?>
|
<?= htmlspecialchars((string)($payment_name ?? '')) ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
204
admin/user_edit.php
Normal file
204
admin/user_edit.php
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
require_permission('all');
|
||||||
|
|
||||||
|
$id = $_GET['id'] ?? null;
|
||||||
|
$user = null;
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
header('Location: users.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$full_name = $_POST['full_name'];
|
||||||
|
$username = $_POST['username'];
|
||||||
|
$email = $_POST['email'];
|
||||||
|
$group_id = $_POST['group_id'];
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
$assigned_outlets = $_POST['outlets'] ?? [];
|
||||||
|
|
||||||
|
// Check if username changed and if new one exists
|
||||||
|
if ($username !== $user['username']) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
|
||||||
|
$stmt->execute([$username, $id]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$message = '<div class="alert alert-danger">Username already taken.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$message) {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
|
$sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ? WHERE id = ?";
|
||||||
|
$params = [$full_name, $username, $email, $group_id, $is_active, $id];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
// Update password if provided
|
||||||
|
if (!empty($_POST['password'])) {
|
||||||
|
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||||
|
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update assigned outlets
|
||||||
|
$pdo->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$id]);
|
||||||
|
if (!empty($assigned_outlets)) {
|
||||||
|
$stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
|
||||||
|
foreach ($assigned_outlets as $outlet_id) {
|
||||||
|
$stmt_outlet->execute([$id, $outlet_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
$message = '<div class="alert alert-success">User updated successfully!</div>';
|
||||||
|
|
||||||
|
// Refresh user data
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$message = '<div class="alert alert-danger">Error updating user: ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll();
|
||||||
|
$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||||
|
$user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
|
||||||
|
$user_outlets->execute([$id]);
|
||||||
|
$assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="users.php" class="text-decoration-none text-muted small"><i class="bi bi-arrow-left"></i> Back to Users</a>
|
||||||
|
<h2 class="fw-bold mt-2">Edit User: <?= htmlspecialchars($user['username']) ?></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?= $message ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">FULL NAME</label>
|
||||||
|
<input type="text" name="full_name" class="form-control" value="<?= htmlspecialchars($user['full_name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">USERNAME</label>
|
||||||
|
<input type="text" name="username" class="form-control" value="<?= htmlspecialchars($user['username']) ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">EMAIL</label>
|
||||||
|
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label>
|
||||||
|
<select name="group_id" class="form-select" required>
|
||||||
|
<?php foreach ($groups as $group): ?>
|
||||||
|
<option value="<?= $group['id'] ?>" <?= $user['group_id'] == $group['id'] ? 'selected' : '' ?>><?= htmlspecialchars($group['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small fw-bold text-muted">NEW PASSWORD (LEAVE BLANK TO KEEP CURRENT)</label>
|
||||||
|
<input type="password" name="password" class="form-control" placeholder="******">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small fw-bold text-muted d-block mb-2">ASSIGNED OUTLETS</label>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($all_outlets as $outlet): ?>
|
||||||
|
<div class="col-md-4 mb-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="outlets[]" value="<?= $outlet['id'] ?>" id="outlet_<?= $outlet['id'] ?>" <?= in_array($outlet['id'], $assigned_outlet_ids) ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label small" for="outlet_<?= $outlet['id'] ?>">
|
||||||
|
<?= htmlspecialchars($outlet['name']) ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<div class="form-text mt-1">Assign one or more outlets to this user.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" <?= $user['is_active'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label fw-bold text-muted small" for="isActiveSwitch">ACTIVE ACCOUNT</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end gap-2">
|
||||||
|
<a href="users.php" class="btn btn-light rounded-pill px-4">Cancel</a>
|
||||||
|
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Update User</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm rounded-4 bg-primary bg-gradient text-white shadow">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h5 class="fw-bold mb-3">User Info</h5>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<div class="bg-white text-primary rounded-circle d-flex align-items-center justify-content-center me-3" style="width:50px;height:50px; font-weight:700; font-size:1.5rem;">
|
||||||
|
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold"><?= htmlspecialchars($user['full_name']) ?></div>
|
||||||
|
<div class="small opacity-75">Member since <?= date('M Y', strtotime($user['created_at'])) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small">
|
||||||
|
<div class="mb-1"><i class="bi bi-envelope me-2"></i> <?= htmlspecialchars($user['email']) ?></div>
|
||||||
|
<div><i class="bi bi-person-badge me-2"></i> User ID: #<?= $user['id'] ?></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="opacity-25 my-3">
|
||||||
|
<div class="small">
|
||||||
|
<div class="fw-bold mb-2">Assigned Outlets:</div>
|
||||||
|
<?php if (empty($assigned_outlet_ids)): ?>
|
||||||
|
<div class="opacity-75">No outlets assigned.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<ul class="list-unstyled mb-0 opacity-75">
|
||||||
|
<?php foreach ($all_outlets as $outlet): ?>
|
||||||
|
<?php if (in_array($outlet['id'], $assigned_outlet_ids)): ?>
|
||||||
|
<li><i class="bi bi-geo-alt me-1"></i> <?= htmlspecialchars($outlet['name']) ?></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
141
admin/user_groups.php
Normal file
141
admin/user_groups.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
require_permission('all'); // Only super admin can manage groups
|
||||||
|
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
// Handle Add Group
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_group') {
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$permissions = isset($_POST['perms']) ? implode(',', $_POST['perms']) : '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO user_groups (name, permissions) VALUES (?, ?)");
|
||||||
|
if ($stmt->execute([$name, $permissions])) {
|
||||||
|
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Group added successfully!</div>';
|
||||||
|
} else {
|
||||||
|
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding group.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete
|
||||||
|
if (isset($_GET['delete'])) {
|
||||||
|
$id = $_GET['delete'];
|
||||||
|
// Prevent deleting the Administrator group (id 1 usually)
|
||||||
|
if ($id != 1) {
|
||||||
|
$pdo->prepare("DELETE FROM user_groups WHERE id = ?")->execute([$id]);
|
||||||
|
}
|
||||||
|
header("Location: user_groups.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY id ASC")->fetchAll();
|
||||||
|
|
||||||
|
$available_perms = [
|
||||||
|
'manage_orders' => 'Manage Orders',
|
||||||
|
'manage_products' => 'Manage Products/Menu',
|
||||||
|
'manage_reports' => 'View Reports',
|
||||||
|
'manage_settings' => 'System Settings',
|
||||||
|
'pos' => 'Access POS Terminal',
|
||||||
|
'kitchen' => 'Access Kitchen View',
|
||||||
|
'all' => 'Full Administrator Access'
|
||||||
|
];
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold mb-1 text-dark">User Groups / Roles</h2>
|
||||||
|
<p class="text-muted mb-0">Define what different users can do</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary shadow-sm rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addGroupModal">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> Add Group
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?= $message ?>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm rounded-3 overflow-hidden">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">ID</th>
|
||||||
|
<th>Group Name</th>
|
||||||
|
<th>Permissions</th>
|
||||||
|
<th class="text-end pe-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($groups as $group): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4"><?= $group['id'] ?></td>
|
||||||
|
<td><span class="fw-bold"><?= htmlspecialchars($group['name']) ?></span></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
if ($group['permissions'] === 'all') {
|
||||||
|
echo '<span class="badge bg-danger rounded-pill px-3">Full Access</span>';
|
||||||
|
} else {
|
||||||
|
$perms = explode(',', $group['permissions']);
|
||||||
|
foreach ($perms as $p) {
|
||||||
|
if (isset($available_perms[$p])) {
|
||||||
|
echo '<span class="badge bg-info bg-opacity-10 text-info border border-info border-opacity-25 rounded-pill px-2 me-1 mb-1" style="font-weight:500;">' . $available_perms[$p] . '</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<?php if ($group['id'] != 1): ?>
|
||||||
|
<a href="?delete=<?= $group['id'] ?>" class="btn btn-sm btn-outline-danger border-0 rounded-circle" onclick="return confirm('Delete this group?')">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Group Modal -->
|
||||||
|
<div class="modal fade" id="addGroupModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||||
|
<div class="modal-header border-0 pb-0 px-4 pt-4">
|
||||||
|
<h5 class="modal-title fw-bold">Create New Group</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<input type="hidden" name="action" value="add_group">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small fw-bold text-muted">GROUP NAME</label>
|
||||||
|
<input type="text" name="name" class="form-control form-control-lg border-0 bg-light" placeholder="e.g. Supervisor" required style="border-radius: 12px;">
|
||||||
|
</div>
|
||||||
|
<label class="form-label small fw-bold text-muted mb-3">PERMISSIONS</label>
|
||||||
|
<div class="row g-2">
|
||||||
|
<?php foreach ($available_perms as $key => $label): ?>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-switch p-3 bg-light rounded-3 d-flex align-items-center justify-content-between">
|
||||||
|
<label class="form-check-label ms-0 fw-medium" for="perm_<?= $key ?>"><?= $label ?></label>
|
||||||
|
<input class="form-check-input" type="checkbox" name="perms[]" value="<?= $key ?>" id="perm_<?= $key ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 p-4 pt-0">
|
||||||
|
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Save Group</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
222
admin/users.php
Normal file
222
admin/users.php
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
require_permission('all'); // Only super admin can manage users
|
||||||
|
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
// Handle Add User
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_user') {
|
||||||
|
$username = $_POST['username'];
|
||||||
|
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||||
|
$full_name = $_POST['full_name'];
|
||||||
|
$email = $_POST['email'];
|
||||||
|
$group_id = $_POST['group_id'];
|
||||||
|
$assigned_outlets = $_POST['outlets'] ?? [];
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Username already exists.</div>';
|
||||||
|
} else {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (username, password, full_name, email, group_id) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$username, $password, $full_name, $email, $group_id]);
|
||||||
|
$user_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
if (!empty($assigned_outlets)) {
|
||||||
|
$stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
|
||||||
|
foreach ($assigned_outlets as $outlet_id) {
|
||||||
|
$stmt_outlet->execute([$user_id, $outlet_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>User added successfully!</div>';
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding user: ' . $e->getMessage() . '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete
|
||||||
|
if (isset($_GET['delete'])) {
|
||||||
|
$id = $_GET['delete'];
|
||||||
|
$currentUser = get_logged_user();
|
||||||
|
// Prevent deleting yourself
|
||||||
|
if ($id != $currentUser['id']) {
|
||||||
|
$pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]);
|
||||||
|
}
|
||||||
|
header("Location: users.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch users with group names and assigned outlets
|
||||||
|
$users_sql = "
|
||||||
|
SELECT u.*, g.name as group_name, GROUP_CONCAT(o.name SEPARATOR ', ') as assigned_outlets
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_groups g ON u.group_id = g.id
|
||||||
|
LEFT JOIN user_outlets uo ON u.id = uo.user_id
|
||||||
|
LEFT JOIN outlets o ON uo.outlet_id = o.id
|
||||||
|
GROUP BY u.id
|
||||||
|
ORDER BY u.id ASC
|
||||||
|
";
|
||||||
|
$users = $pdo->query($users_sql)->fetchAll();
|
||||||
|
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll();
|
||||||
|
$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||||
|
|
||||||
|
include 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold mb-1 text-dark">User Management</h2>
|
||||||
|
<p class="text-muted mb-0">Manage system users and their access levels</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary shadow-sm rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||||
|
<i class="bi bi-person-plus-fill me-1"></i> Add User
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?= $message ?>
|
||||||
|
|
||||||
|
<div class="card border-0 shadow-sm rounded-3 overflow-hidden">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">User</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Role / Group</th>
|
||||||
|
<th>Outlets</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th class="text-end pe-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-flex align-items-center justify-content-center me-3 shadow-sm" style="width:42px;height:42px; font-weight:600;">
|
||||||
|
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold text-dark"><?= htmlspecialchars($user['full_name']) ?></div>
|
||||||
|
<div class="text-muted small">@<?= htmlspecialchars($user['username']) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($user['email']) ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 rounded-pill px-3" style="font-weight:600;">
|
||||||
|
<?= htmlspecialchars($user['group_name'] ?? 'No Group') ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="small text-muted" style="max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="<?= htmlspecialchars($user['assigned_outlets'] ?: 'None') ?>">
|
||||||
|
<?= htmlspecialchars($user['assigned_outlets'] ?: 'None') ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($user['is_active']): ?>
|
||||||
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 rounded-pill px-3">Active</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 rounded-pill px-3">Inactive</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
<div class="d-inline-flex gap-2">
|
||||||
|
<a href="user_edit.php?id=<?= $user['id'] ?>" class="btn btn-sm btn-icon-soft edit" title="Edit User">
|
||||||
|
<i class="bi bi-pencil-fill"></i>
|
||||||
|
</a>
|
||||||
|
<?php if ($user['id'] != get_logged_user()['id']): ?>
|
||||||
|
<a href="?delete=<?= $user['id'] ?>" class="btn btn-sm btn-icon-soft delete" onclick="return confirm('Delete this user?')" title="Delete">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add User Modal -->
|
||||||
|
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||||
|
<div class="modal-header border-0 pb-0 px-4 pt-4">
|
||||||
|
<h5 class="modal-title fw-bold">Add New User</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<input type="hidden" name="action" value="add_user">
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label small fw-bold text-muted">FULL NAME</label>
|
||||||
|
<input type="text" name="full_name" class="form-control border-0 bg-light" placeholder="e.g. John Doe" required style="border-radius: 12px;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label small fw-bold text-muted">EMAIL</label>
|
||||||
|
<input type="email" name="email" class="form-control border-0 bg-light" placeholder="e.g. john@example.com" required style="border-radius: 12px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">USERNAME</label>
|
||||||
|
<input type="text" name="username" class="form-control border-0 bg-light" placeholder="e.g. jdoe" required style="border-radius: 12px;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-muted">PASSWORD</label>
|
||||||
|
<input type="password" name="password" class="form-control border-0 bg-light" placeholder="******" required style="border-radius: 12px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label>
|
||||||
|
<select name="group_id" class="form-select border-0 bg-light" required style="border-radius: 12px;">
|
||||||
|
<?php foreach ($groups as $group): ?>
|
||||||
|
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-muted d-block mb-2">ASSIGNED OUTLETS</label>
|
||||||
|
<div class="p-3 bg-light rounded-4">
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($all_outlets as $outlet): ?>
|
||||||
|
<div class="col-md-6 mb-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="outlets[]" value="<?= $outlet['id'] ?>" id="new_outlet_<?= $outlet['id'] ?>">
|
||||||
|
<label class="form-check-label small" for="new_outlet_<?= $outlet['id'] ?>">
|
||||||
|
<?= htmlspecialchars($outlet['name']) ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 p-4 pt-0">
|
||||||
|
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Create User</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
302
ads.php
Normal file
302
ads.php
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch active advertisement images
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM ads_images WHERE is_active = 1 ORDER BY sort_order ASC");
|
||||||
|
$stmt->execute();
|
||||||
|
$ads = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Get current outlet (default to 1 if not specified)
|
||||||
|
$outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : 1;
|
||||||
|
|
||||||
|
// Fetch company settings for branding
|
||||||
|
$stmt = $pdo->query("SELECT * FROM company_settings LIMIT 1");
|
||||||
|
$company = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$companyName = $company['company_name'] ?? 'Foody';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Now Serving & Ads - <?= htmlspecialchars($companyName) ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #0d6efd;
|
||||||
|
--ready-color: #198754;
|
||||||
|
--preparing-color: #0dcaf0;
|
||||||
|
--bg-dark: #121212;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: var(--bg-dark);
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
.serving-board {
|
||||||
|
flex: 0 0 35%;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
border-right: 2px solid #333;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.ads-slider {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.board-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.board-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.order-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
.order-number {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.order-ready {
|
||||||
|
background-color: var(--ready-color);
|
||||||
|
color: white;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
.order-preparing {
|
||||||
|
background-color: #333;
|
||||||
|
color: #aaa;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
.carousel-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.carousel-caption {
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
bottom: 5%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
.caption-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle View Button */
|
||||||
|
#toggle-view {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
#toggle-view:hover { opacity: 1; }
|
||||||
|
|
||||||
|
/* Fullscreen Ads Class */
|
||||||
|
.fullscreen-ads .serving-board { display: none; }
|
||||||
|
.fullscreen-ads .ads-slider { flex: 0 0 100%; }
|
||||||
|
|
||||||
|
.empty-msg {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #555;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="split-view">
|
||||||
|
|
||||||
|
<div class="main-container" id="main-view">
|
||||||
|
<!-- Left: Serving Board -->
|
||||||
|
<div class="serving-board">
|
||||||
|
<div class="board-header">
|
||||||
|
<h1 class="board-title">Order Status</h1>
|
||||||
|
<p class="text-muted"><?= htmlspecialchars($companyName) ?> Outlet #<?= $outlet_id ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-5">
|
||||||
|
<h2 class="section-title text-success">
|
||||||
|
<i class="bi bi-bell-fill"></i> Now Serving
|
||||||
|
</h2>
|
||||||
|
<div id="ready-orders" class="order-grid">
|
||||||
|
<!-- Ready orders injected here -->
|
||||||
|
<div class="empty-msg">No orders ready</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title text-info">
|
||||||
|
<i class="bi bi-hourglass-split"></i> Preparing
|
||||||
|
</h2>
|
||||||
|
<div id="preparing-orders" class="order-grid">
|
||||||
|
<!-- Preparing orders injected here -->
|
||||||
|
<div class="empty-msg">No orders preparing</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-auto pt-4 border-top border-dark text-center text-muted">
|
||||||
|
<small id="clock"></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Ads Slider -->
|
||||||
|
<div class="ads-slider">
|
||||||
|
<?php if (!empty($ads)): ?>
|
||||||
|
<div id="adCarousel" class="carousel slide carousel-fade" data-bs-ride="carousel" data-bs-interval="10000">
|
||||||
|
<div class="carousel-inner">
|
||||||
|
<?php foreach ($ads as $index => $ad): ?>
|
||||||
|
<div class="carousel-item <?= $index === 0 ? 'active' : '' ?>">
|
||||||
|
<img src="<?= htmlspecialchars($ad['image_path']) ?>" alt="<?= htmlspecialchars($ad['title'] ?? '') ?>">
|
||||||
|
<?php if (!empty($ad['title'])): ?>
|
||||||
|
<div class="carousel-caption d-none d-md-block">
|
||||||
|
<h5 class="caption-title"><?= htmlspecialchars($ad['title']) ?></h5>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="h-100 d-flex flex-column align-items-center justify-content-center text-muted">
|
||||||
|
<i class="bi bi-images fs-1 mb-3"></i>
|
||||||
|
<p>No advertisements uploaded yet.</p>
|
||||||
|
<small>Go to Admin > Ads Management to upload pictures.</small>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="toggle-view" class="btn btn-dark btn-sm rounded-circle shadow">
|
||||||
|
<i class="bi bi-fullscreen"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const OUTLET_ID = <?= $outlet_id ?>;
|
||||||
|
let isFullscreen = false;
|
||||||
|
|
||||||
|
// Fetch Orders for the Board
|
||||||
|
async function fetchBoardOrders() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/kitchen.php?outlet_id=' + OUTLET_ID);
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
const orders = await response.json();
|
||||||
|
updateBoard(orders);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching orders:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoard(orders) {
|
||||||
|
const readyContainer = document.getElementById('ready-orders');
|
||||||
|
const preparingContainer = document.getElementById('preparing-orders');
|
||||||
|
|
||||||
|
const readyOrders = orders.filter(o => o.status === 'ready');
|
||||||
|
const preparingOrders = orders.filter(o => o.status === 'preparing');
|
||||||
|
|
||||||
|
// Render Ready
|
||||||
|
if (readyOrders.length > 0) {
|
||||||
|
readyContainer.innerHTML = readyOrders.map(o => `
|
||||||
|
<div class="order-number order-ready">${o.id}</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
readyContainer.innerHTML = '<div class="empty-msg">Waiting for orders...</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Preparing
|
||||||
|
if (preparingOrders.length > 0) {
|
||||||
|
preparingContainer.innerHTML = preparingOrders.map(o => `
|
||||||
|
<div class="order-number order-preparing">${o.id}</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
preparingContainer.innerHTML = '<div class="empty-msg">No active prep</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle View Logic (Alternate between Split and Fullscreen Ads)
|
||||||
|
const toggleBtn = document.getElementById('toggle-view');
|
||||||
|
toggleBtn.addEventListener('click', () => {
|
||||||
|
isFullscreen = !isFullscreen;
|
||||||
|
const body = document.body;
|
||||||
|
const icon = toggleBtn.querySelector('i');
|
||||||
|
|
||||||
|
if (isFullscreen) {
|
||||||
|
body.classList.add('fullscreen-ads');
|
||||||
|
icon.classList.replace('bi-fullscreen', 'bi-fullscreen-exit');
|
||||||
|
} else {
|
||||||
|
body.classList.remove('fullscreen-ads');
|
||||||
|
icon.classList.replace('bi-fullscreen-exit', 'bi-fullscreen');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-alternate every 30 seconds (Optional, but user mentioned "alternatively")
|
||||||
|
setInterval(() => {
|
||||||
|
// If you want it to auto-switch, uncomment below:
|
||||||
|
toggleBtn.click();
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
setInterval(() => {
|
||||||
|
const clock = document.getElementById('clock');
|
||||||
|
if (clock) {
|
||||||
|
const now = new Date();
|
||||||
|
clock.textContent = now.toLocaleDateString() + ' ' + now.toLocaleTimeString();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Initial Fetch & Poll
|
||||||
|
fetchBoardOrders();
|
||||||
|
setInterval(fetchBoardOrders, 5000); // Poll every 5s for the board
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -121,6 +121,8 @@ body {
|
|||||||
color: var(--text-heading) !important;
|
color: var(--text-heading) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-heading i {
|
.sidebar-heading i {
|
||||||
@ -128,6 +130,22 @@ body {
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Chevron Rotation Logic */
|
||||||
|
.sidebar-heading .chevron-icon {
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
margin-right: 0 !important; /* Override generic margin */
|
||||||
|
color: var(--text-heading) !important; /* Softer color for chevron */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading[aria-expanded="true"] .chevron-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading.collapsed .chevron-icon {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
.brand-logo {
|
.brand-logo {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@ -394,3 +412,7 @@ body {
|
|||||||
[data-theme="dark"] .table {
|
[data-theme="dark"] .table {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
/* Ensure Dropdowns are always on top */
|
||||||
|
.dropdown-menu {
|
||||||
|
z-index: 1050 !important;
|
||||||
|
}
|
||||||
28
db/migrations/008_user_system.sql
Normal file
28
db/migrations/008_user_system.sql
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS user_groups (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
permissions TEXT, -- JSON or comma-separated list of capabilities
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
group_id INT,
|
||||||
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
full_name VARCHAR(255),
|
||||||
|
email VARCHAR(255) UNIQUE,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed default groups
|
||||||
|
INSERT INTO user_groups (name, permissions) VALUES ('Administrator', 'all');
|
||||||
|
INSERT INTO user_groups (name, permissions) VALUES ('Manager', 'manage_orders,manage_products,manage_reports');
|
||||||
|
INSERT INTO user_groups (name, permissions) VALUES ('Cashier', 'pos,manage_orders');
|
||||||
|
INSERT INTO user_groups (name, permissions) VALUES ('Waiter', 'pos');
|
||||||
|
|
||||||
|
-- Seed default admin user (password: admin123)
|
||||||
|
-- Using PHP to hash the password properly would be better, but for initial seeding we can use a placeholder if we have a setup script.
|
||||||
|
-- Let's just create the tables for now.
|
||||||
7
db/migrations/009_user_outlets.sql
Normal file
7
db/migrations/009_user_outlets.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS user_outlets (
|
||||||
|
user_id INT(11) NOT NULL,
|
||||||
|
outlet_id INT(11) NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, outlet_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
8
db/migrations/010_ads_images.sql
Normal file
8
db/migrations/010_ads_images.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS ads_images (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
image_path VARCHAR(255) NOT NULL,
|
||||||
|
title VARCHAR(255) DEFAULT NULL,
|
||||||
|
sort_order INT DEFAULT 0,
|
||||||
|
is_active TINYINT(1) DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@ -4,6 +4,7 @@ class WablasService {
|
|||||||
private $domain;
|
private $domain;
|
||||||
private $token;
|
private $token;
|
||||||
private $secret_key;
|
private $secret_key;
|
||||||
|
private $is_enabled;
|
||||||
|
|
||||||
public function __construct($pdo) {
|
public function __construct($pdo) {
|
||||||
$this->pdo = $pdo;
|
$this->pdo = $pdo;
|
||||||
@ -23,6 +24,7 @@ class WablasService {
|
|||||||
$this->domain = $settings['domain'] ?? '';
|
$this->domain = $settings['domain'] ?? '';
|
||||||
$this->token = $settings['token'] ?? '';
|
$this->token = $settings['token'] ?? '';
|
||||||
$this->secret_key = $settings['secret_key'] ?? '';
|
$this->secret_key = $settings['secret_key'] ?? '';
|
||||||
|
$this->is_enabled = ($settings['is_enabled'] ?? '0') === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConnection() {
|
public function testConnection() {
|
||||||
@ -72,6 +74,10 @@ class WablasService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function sendMessage($phone, $message) {
|
public function sendMessage($phone, $message) {
|
||||||
|
if (!$this->is_enabled) {
|
||||||
|
return ['success' => false, 'message' => 'WhatsApp sending is disabled in settings'];
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($this->domain) || empty($this->token)) {
|
if (empty($this->domain) || empty($this->token)) {
|
||||||
return ['success' => false, 'message' => 'Wablas configuration missing'];
|
return ['success' => false, 'message' => 'Wablas configuration missing'];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,9 +83,9 @@ function paginate_query($pdo, $query, $params = [], $default_limit = 20) {
|
|||||||
|
|
||||||
// Add LIMIT and OFFSET
|
// Add LIMIT and OFFSET
|
||||||
// Note: PDO parameters for LIMIT/OFFSET can be tricky with some drivers, sticking to direct injection for integers is safe here
|
// Note: PDO parameters for LIMIT/OFFSET can be tricky with some drivers, sticking to direct injection for integers is safe here
|
||||||
$query .= " LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
$query_with_limit = $query . " LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||||
|
|
||||||
$stmt = $pdo->prepare($query);
|
$stmt = $pdo->prepare($query_with_limit);
|
||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
$data = $stmt->fetchAll();
|
$data = $stmt->fetchAll();
|
||||||
|
|
||||||
@ -115,16 +115,16 @@ function render_pagination_controls($pagination, $extra_params = []) {
|
|||||||
|
|
||||||
// Limit Selector
|
// Limit Selector
|
||||||
$limits = [20, 50, 100, -1];
|
$limits = [20, 50, 100, -1];
|
||||||
echo '<div class="d-flex justify-content-between align-items-center mb-3 bg-white p-2 rounded border">';
|
echo '<div class="d-flex justify-content-between align-items-center mb-0 bg-white p-2 rounded flex-wrap gap-2">';
|
||||||
|
|
||||||
echo '<div class="d-flex align-items-center">';
|
echo '<div class="d-flex align-items-center flex-wrap gap-3">';
|
||||||
echo '<form method="GET" class="d-flex align-items-center mb-0">';
|
echo '<form method="GET" class="d-flex align-items-center mb-0">';
|
||||||
// Preserve other GET params
|
// Preserve other GET params
|
||||||
foreach ($params as $key => $val) {
|
foreach ($params as $key => $val) {
|
||||||
if ($key !== 'limit') echo '<input type="hidden" name="'.htmlspecialchars($key).'" value="'.htmlspecialchars($val).'">';
|
if ($key !== 'limit') echo '<input type="hidden" name="'.htmlspecialchars((string)$key).'" value="'.htmlspecialchars((string)$val).'">';
|
||||||
}
|
}
|
||||||
echo '<small class="me-2 text-muted">Show:</small>';
|
echo '<small class="me-2 text-muted fw-bold">SHOW:</small>';
|
||||||
echo '<select name="limit" class="form-select form-select-sm border-0 bg-light" style="width: auto; font-weight: 500;" onchange="this.form.submit()">';
|
echo '<select name="limit" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">';
|
||||||
foreach ($limits as $l) {
|
foreach ($limits as $l) {
|
||||||
$label = $l == -1 ? 'All' : $l;
|
$label = $l == -1 ? 'All' : $l;
|
||||||
$selected = $limit == $l ? 'selected' : '';
|
$selected = $limit == $l ? 'selected' : '';
|
||||||
@ -134,26 +134,35 @@ function render_pagination_controls($pagination, $extra_params = []) {
|
|||||||
echo '</form>';
|
echo '</form>';
|
||||||
|
|
||||||
// Total Count
|
// Total Count
|
||||||
echo '<span class="text-muted small ms-3 border-start ps-3">Total: <strong>' . $pagination['total_rows'] . '</strong></span>';
|
echo '<span class="text-muted small border-start ps-3">Total: <strong>' . $pagination['total_rows'] . '</strong> items</span>';
|
||||||
|
|
||||||
|
// Optional Total Amount (Sum)
|
||||||
|
if (isset($pagination['total_amount_sum'])) {
|
||||||
|
echo '<span class="text-success small border-start ps-3">Total Sum: <strong class="fs-5">' . format_currency($pagination['total_amount_sum']) . '</strong></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($total_pages > 0) {
|
||||||
|
echo '<span class="text-muted small border-start ps-3">Page <strong>' . $page . '</strong> of <strong>' . $total_pages . '</strong></span>';
|
||||||
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
// Pagination Links
|
// Pagination Links
|
||||||
if ($total_pages > 1) {
|
if ($total_pages > 1) {
|
||||||
echo '<nav><ul class="pagination pagination-sm mb-0">';
|
echo '<nav aria-label="Page navigation">';
|
||||||
|
echo '<ul class="pagination pagination-sm mb-0">';
|
||||||
|
|
||||||
// Previous
|
// Previous
|
||||||
$prev_disabled = $page <= 1 ? 'disabled' : '';
|
$prev_disabled = $page <= 1 ? 'disabled' : '';
|
||||||
$prev_page = max(1, $page - 1);
|
$prev_page = max(1, $page - 1);
|
||||||
$url_params = array_merge($params, ['page' => $prev_page, 'limit' => $limit]);
|
$url_params = array_merge($params, ['page' => $prev_page, 'limit' => $limit]);
|
||||||
$prev_url = '?' . http_build_query($url_params);
|
$prev_url = '?' . http_build_query($url_params);
|
||||||
echo "<li class='page-item $prev_disabled'><a class='page-link' href='$prev_url'>«</a></li>";
|
echo "<li class='page-item $prev_disabled'><a class='page-link' href='$prev_url' aria-label='Previous'><span aria-hidden='true'>«</span></a></li>";
|
||||||
|
|
||||||
// Logic to show limited page numbers with ellipsis
|
// Logic to show limited page numbers with ellipsis
|
||||||
// Always show first, last, current, and surrounding
|
|
||||||
$shown_pages = [];
|
$shown_pages = [];
|
||||||
$shown_pages[] = 1;
|
$shown_pages[] = 1;
|
||||||
$shown_pages[] = $total_pages;
|
$shown_pages[] = $total_pages;
|
||||||
for ($i = $page - 2; $i <= $page + 2; $i++) {
|
for ($i = $page - 3; $i <= $page + 3; $i++) {
|
||||||
if ($i > 1 && $i < $total_pages) {
|
if ($i > 1 && $i < $total_pages) {
|
||||||
$shown_pages[] = $i;
|
$shown_pages[] = $i;
|
||||||
}
|
}
|
||||||
@ -179,9 +188,76 @@ function render_pagination_controls($pagination, $extra_params = []) {
|
|||||||
$next_page = min($total_pages, $page + 1);
|
$next_page = min($total_pages, $page + 1);
|
||||||
$url_params = array_merge($params, ['page' => $next_page, 'limit' => $limit]);
|
$url_params = array_merge($params, ['page' => $next_page, 'limit' => $limit]);
|
||||||
$next_url = '?' . http_build_query($url_params);
|
$next_url = '?' . http_build_query($url_params);
|
||||||
echo "<li class='page-item $next_disabled'><a class='page-link' href='$next_url'>»</a></li>";
|
echo "<li class='page-item $next_disabled'><a class='page-link' href='$next_url' aria-label='Next'><span aria-hidden='true'>»</span></a></li>";
|
||||||
|
|
||||||
echo '</ul></nav>';
|
echo '</ul></nav>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
function init_session() {
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function login_user($username, $password) {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT u.*, g.name as group_name, g.permissions
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_groups g ON u.group_id = g.id
|
||||||
|
WHERE u.username = ? AND u.is_active = 1
|
||||||
|
LIMIT 1");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
init_session();
|
||||||
|
unset($user['password']); // Don't store hash in session
|
||||||
|
$_SESSION['user'] = $user;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout_user() {
|
||||||
|
init_session();
|
||||||
|
unset($_SESSION['user']);
|
||||||
|
session_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_logged_user() {
|
||||||
|
init_session();
|
||||||
|
return $_SESSION['user'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_login() {
|
||||||
|
if (!get_logged_user()) {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function has_permission($permission) {
|
||||||
|
$user = get_logged_user();
|
||||||
|
if (!$user) return false;
|
||||||
|
|
||||||
|
$userPermissions = $user['permissions'] ?? '';
|
||||||
|
if ($userPermissions === 'all') return true;
|
||||||
|
|
||||||
|
$perms = explode(',', $userPermissions);
|
||||||
|
return in_array($permission, $perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_permission($permission) {
|
||||||
|
require_login();
|
||||||
|
if (!has_permission($permission)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo "Access Denied: You don't have permission to access this page.";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
kitchen.php
57
kitchen.php
@ -1,8 +1,41 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/db/config.php';
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/includes/functions.php';
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
$currentUser = get_logged_user();
|
||||||
|
|
||||||
|
// Fetch outlets based on user assignment
|
||||||
|
if (has_permission('all')) {
|
||||||
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
$current_outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : 1;
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT o.* FROM outlets o
|
||||||
|
JOIN user_outlets uo ON o.id = uo.outlet_id
|
||||||
|
WHERE uo.user_id = ?
|
||||||
|
ORDER BY o.name
|
||||||
|
");
|
||||||
|
$stmt->execute([$currentUser['id']]);
|
||||||
|
$outlets = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : (count($outlets) > 0 ? (int)$outlets[0]['id'] : 1);
|
||||||
|
|
||||||
|
// Security check: ensure user has access to this outlet
|
||||||
|
if (!has_permission('all')) {
|
||||||
|
$has_access = false;
|
||||||
|
foreach ($outlets as $o) {
|
||||||
|
if ($o['id'] == $current_outlet_id) {
|
||||||
|
$has_access = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$has_access && count($outlets) > 0) {
|
||||||
|
$current_outlet_id = (int)$outlets[0]['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -12,6 +45,7 @@ $current_outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : 1;
|
|||||||
<title>Kitchen Display System</title>
|
<title>Kitchen Display System</title>
|
||||||
<!-- Re-using existing CSS if any, or adding Bootstrap/Custom -->
|
<!-- Re-using existing CSS if any, or adding Bootstrap/Custom -->
|
||||||
<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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
<link href="assets/css/custom.css" rel="stylesheet">
|
<link href="assets/css/custom.css" rel="stylesheet">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<style>
|
<style>
|
||||||
@ -57,16 +91,28 @@ $current_outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : 1;
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="d-flex justify-content-between align-items-center py-3 px-4 bg-white shadow-sm mb-4">
|
<div class="d-flex justify-content-between align-items-center py-3 px-4 bg-white shadow-sm mb-4">
|
||||||
<h2 class="h4 mb-0">Kitchen Display</h2>
|
<h2 class="h4 mb-0">Kitchen Display</h2>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<select id="outlet-selector" class="form-select form-select-sm me-3" style="width: auto;">
|
<select id="outlet-selector" class="form-select form-select-sm" style="width: auto;">
|
||||||
<?php foreach ($outlets as $outlet): ?>
|
<?php foreach ($outlets as $outlet): ?>
|
||||||
<option value="<?= $outlet['id'] ?>" <?= $current_outlet_id == $outlet['id'] ? 'selected' : '' ?>>
|
<option value="<?= $outlet['id'] ?>" <?= $current_outlet_id == $outlet['id'] ? 'selected' : '' ?>>
|
||||||
<?= htmlspecialchars($outlet['name']) ?>
|
<?= htmlspecialchars($outlet['name']) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<span id="clock" class="text-muted me-3"></span>
|
<span id="clock" class="text-muted d-none d-md-inline"></span>
|
||||||
<a href="index.php" class="btn btn-outline-secondary btn-sm">Back to Home</a>
|
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-person-circle"></i> <?= htmlspecialchars($currentUser['username']) ?>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end shadow border-0">
|
||||||
|
<li><a class="dropdown-item" href="admin/"><i class="bi bi-shield-lock me-2"></i> Admin Panel</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="index.php" class="btn btn-outline-secondary btn-sm">Home</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -219,5 +265,6 @@ fetchOrders();
|
|||||||
setInterval(fetchOrders, 10000); // Poll every 10s
|
setInterval(fetchOrders, 10000); // Poll every 10s
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
88
login.php
Normal file
88
login.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/includes/functions.php';
|
||||||
|
|
||||||
|
init_session();
|
||||||
|
|
||||||
|
// Redirect if already logged in
|
||||||
|
if (get_logged_user()) {
|
||||||
|
header('Location: /admin/index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = $_POST['username'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (login_user($username, $password)) {
|
||||||
|
header('Location: /admin/index.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Invalid username or password.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = get_company_settings();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login - <?= htmlspecialchars($settings['company_name']) ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/custom.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
box-shadow: 0 1rem 3rem rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="card login-card border-0">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h3 class="fw-bold"><?= htmlspecialchars($settings['company_name']) ?></h3>
|
||||||
|
<p class="text-muted">Sign in to your account</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger small py-2"><?= $error ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-medium">Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light border-0"><i class="bi bi-person"></i></span>
|
||||||
|
<input type="text" name="username" class="form-control bg-light border-0" required autofocus>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small fw-medium">Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light border-0"><i class="bi bi-lock"></i></span>
|
||||||
|
<input type="password" name="password" class="form-control bg-light border-0" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
logout.php
Normal file
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/includes/functions.php';
|
||||||
|
logout_user();
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
49
pos.php
49
pos.php
@ -1,11 +1,45 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
require_once __DIR__ . '/db/config.php';
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/includes/functions.php';
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
$currentUser = get_logged_user();
|
||||||
|
|
||||||
|
// Fetch outlets based on user assignment
|
||||||
|
if (has_permission('all')) {
|
||||||
|
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT o.* FROM outlets o
|
||||||
|
JOIN user_outlets uo ON o.id = uo.outlet_id
|
||||||
|
WHERE uo.user_id = ?
|
||||||
|
ORDER BY o.name
|
||||||
|
");
|
||||||
|
$stmt->execute([$currentUser['id']]);
|
||||||
|
$outlets = $stmt->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
$outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : (count($outlets) > 0 ? (int)$outlets[0]['id'] : 1);
|
||||||
|
|
||||||
|
// Security check: ensure user has access to this outlet
|
||||||
|
if (!has_permission('all')) {
|
||||||
|
$has_access = false;
|
||||||
|
foreach ($outlets as $o) {
|
||||||
|
if ($o['id'] == $outlet_id) {
|
||||||
|
$has_access = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$has_access && count($outlets) > 0) {
|
||||||
|
$outlet_id = (int)$outlets[0]['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll();
|
$categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll();
|
||||||
$all_products = $pdo->query("SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll();
|
$all_products = $pdo->query("SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll();
|
||||||
$outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
|
||||||
$payment_types = $pdo->query("SELECT * FROM payment_types WHERE is_active = 1 ORDER BY id")->fetchAll();
|
$payment_types = $pdo->query("SELECT * FROM payment_types WHERE is_active = 1 ORDER BY id")->fetchAll();
|
||||||
|
|
||||||
// Fetch variants
|
// Fetch variants
|
||||||
@ -16,7 +50,6 @@ foreach ($variants_raw as $v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$table_id = $_GET['table'] ?? '1'; // Default table
|
$table_id = $_GET['table'] ?? '1'; // Default table
|
||||||
$outlet_id = isset($_GET['outlet_id']) ? (int)$_GET['outlet_id'] : 1;
|
|
||||||
$settings = get_company_settings();
|
$settings = get_company_settings();
|
||||||
$order_type = $_GET['order_type'] ?? 'takeaway';
|
$order_type = $_GET['order_type'] ?? 'takeaway';
|
||||||
|
|
||||||
@ -79,7 +112,17 @@ foreach ($outlets as $o) {
|
|||||||
<div id="current-table-display" class="badge bg-light text-dark border px-3 py-2" style="display: none; font-size: 0.9rem;">
|
<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) ?>
|
Table <?= htmlspecialchars($table_id) ?>
|
||||||
</div>
|
</div>
|
||||||
<a href="admin/" class="btn btn-sm btn-outline-dark"><i class="bi bi-shield-lock"></i> Admin</a>
|
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-person-circle"></i> <?= htmlspecialchars($currentUser['username']) ?>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a class="dropdown-item" href="admin/"><i class="bi bi-shield-lock"></i> Admin Panel</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item text-danger" href="logout.php"><i class="bi bi-box-arrow-right"></i> Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user