38682-vm/kitchen.php
2026-02-28 12:59:44 +00:00

387 lines
15 KiB
PHP

<?php
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/functions.php';
require_permission('kitchen_view');
$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);
} 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'];
}
}
$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>Kitchen Display System</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 href="assets/css/custom.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
body { background-color: #f4f6f9; }
.order-card {
border: none;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
transition: transform 0.2s;
height: 100%;
display: flex;
flex-direction: column;
}
.order-card:hover { transform: translateY(-2px); }
.card-header {
background-color: #fff;
border-bottom: 1px solid #eee;
border-radius: 12px 12px 0 0 !important;
padding: 1rem;
font-weight: 600;
}
.status-pending { border-left: 5px solid #ffc107; }
.status-preparing { border-left: 5px solid #17a2b8; }
.status-ready { border-left: 5px solid #28a745; }
.badge-pending { background-color: #ffc107; color: #000; }
.badge-preparing { background-color: #17a2b8; }
.badge-ready { background-color: #28a745; }
.item-list { list-style: none; padding: 0; margin: 0; }
.item-list li {
padding: 0.5rem 0;
border-bottom: 1px dashed #eee;
display: flex;
justify-content: space-between;
}
.item-list li:last-child { border-bottom: none; }
.item-qty { font-weight: bold; margin-right: 10px; color: #333; }
#kitchen-grid { padding: 20px; }
.card-footer { border-top: none; background: white; padding: 1rem; border-radius: 0 0 12px 12px; }
</style>
</head>
<body>
<div class="container-fluid">
<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>
<div class="d-flex align-items-center gap-3">
<select id="outlet-selector" class="form-select form-select-sm" style="width: auto;">
<?php foreach ($outlets as $outlet): ?>
<option value="<?= $outlet['id'] ?>" <?= $current_outlet_id == $outlet['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($outlet['name']) ?>
</option>
<?php endforeach; ?>
</select>
<span id="clock" class="text-muted d-none d-md-inline"></span>
<?php if (has_permission('kitchen_add')): ?>
<button class="btn btn-danger btn-sm" onclick="serveAll()">
<i class="bi bi-check2-all"></i> Serve All
</button>
<?php endif; ?>
<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">
<?php if (has_permission('dashboard_view')): ?>
<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>
<?php endif; ?>
<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 id="kitchen-grid" class="row g-4">
<!-- Orders will be injected here -->
<div class="col-12 text-center text-muted py-5">
Loading orders...
</div>
</div>
<footer class="text-center py-4 text-muted small">
Powered By Abidarcafe @2026
</footer>
</div>
<script>
const OUTLET_ID = <?= $current_outlet_id ?>;
const CAN_EDIT = <?= has_permission('kitchen_add') ? 'true' : 'false' ?>;
const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
const BASE_URL = '<?= get_base_url() ?>';
let activeOrders = [];
async function fetchOrders() {
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();
activeOrders = orders;
renderOrders(orders);
} catch (error) {
console.error('Error fetching orders:', error);
}
}
function renderOrders(orders) {
const grid = document.getElementById('kitchen-grid');
if (orders.length === 0) {
grid.innerHTML = '<div class="col-12 text-center text-muted py-5">No active orders</div>';
return;
}
const newHtml = orders.map(order => {
let actionBtn = '';
let statusBadge = '';
let cardClass = 'order-card ';
if (order.status === 'pending') {
statusBadge = '<span class="badge badge-pending">Pending</span>';
cardClass += 'status-pending';
if (CAN_EDIT) actionBtn = `<button class="btn btn-info btn-sm w-100 text-white mb-2" onclick="updateStatus(${order.id}, 'preparing')">Start Preparing</button>`;
} else if (order.status === 'preparing') {
statusBadge = '<span class="badge badge-preparing">Preparing</span>';
cardClass += 'status-preparing';
if (CAN_EDIT) actionBtn = `<button class="btn btn-success btn-sm w-100 mb-2" onclick="updateStatus(${order.id}, 'ready')">Mark Ready</button>`;
} else if (order.status === 'ready') {
statusBadge = '<span class="badge badge-ready">Ready</span>';
cardClass += 'status-ready';
if (CAN_EDIT) actionBtn = `<button class="btn btn-secondary btn-sm w-100 mb-2" onclick="updateStatus(${order.id}, 'completed')">Serve / Complete</button>`;
}
const itemsHtml = order.items.map(item => `
<li>
<span><span class="item-qty">${item.quantity}x</span> ${item.product_name} ${item.variant_name ? '<small class="text-muted">('+item.variant_name+')</small>' : ''}</span>
</li>
`).join('');
const created = new Date(order.created_at);
const timeString = created.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
return `
<div class="col-md-4 col-lg-3">
<div class="${cardClass}">
<div class="card-header d-flex justify-content-between align-items-center">
<div>#${order.id} <small class="text-muted">${timeString}</small></div>
${statusBadge}
</div>
<div class="card-body">
<h5 class="card-title mb-3">
${order.order_type === 'dine-in' ? `Table ${order.table_number}` : order.order_type.toUpperCase()}
${order.customer_name ? `<br><small class="text-muted">${order.customer_name}</small>` : ''}
</h5>
<ul class="item-list mb-3">
${itemsHtml}
</ul>
</div>
<div class="card-footer">
${actionBtn}
<button class="btn btn-outline-primary btn-sm w-100" onclick="printKitchenTicket(${order.id})">
<i class="bi bi-printer"></i> Print Ticket
</button>
</div>
</div>
</div>
`;
}).join('');
grid.innerHTML = newHtml;
}
function printKitchenTicket(orderId) {
const order = activeOrders.find(o => o.id == orderId);
if (!order) return;
const width = 450;
const height = 600;
const left = (screen.width - width) / 2;
const top = (screen.height - height) / 2;
const win = window.open('', '_blank', `width=${width},height=${height},top=${top},left=${left}`);
if (!win) {
alert('Please allow popups for printing.');
return;
}
const itemsHtml = order.items.map(item => `
<tr>
<td style="padding: 10px 0; border-bottom: 1px solid #000;">
<div style="font-size: 20px; font-weight: bold;">${item.quantity} x ${item.product_name}</div>
${item.variant_name ? `<div style="font-size: 16px;">Variant: ${item.variant_name}</div>` : ''}
</td>
</tr>
`).join('');
const html = `
<html>
<head>
<title>Kitchen Ticket #${order.id}</title>
<style>
body { font-family: 'Courier New', monospace; padding: 20px; color: #000; }
.header { text-align: center; border-bottom: 2px dashed #000; padding-bottom: 10px; margin-bottom: 10px; }
.info { font-size: 18px; margin-bottom: 10px; }
.info div { margin-bottom: 5px; }
table { width: 100%; border-collapse: collapse; }
@media print { body { width: 80mm; padding: 5mm; } @page { size: 80mm auto; margin: 0; } }
</style>
</head>
<body>
<div class="header">
<h1 style="margin:0;">KITCHEN TICKET</h1>
<div style="font-size: 24px; font-weight: bold;">#${order.id}</div>
</div>
<div class="info">
<div><strong>Type:</strong> ${order.order_type.toUpperCase()}</div>
${order.order_type === 'dine-in' ? `<div><strong>Table:</strong> ${order.table_number}</div>` : ''}
<div><strong>Time:</strong> ${new Date(order.created_at).toLocaleString()}</div>
${order.customer_name ? `<div><strong>Cust:</strong> ${order.customer_name}</div>` : ''}
</div>
<div style="border-bottom: 2px solid #000; margin-bottom: 10px;"></div>
<table>
<tbody>${itemsHtml}</tbody>
</table>
<div style="border-top: 2px dashed #000; margin-top: 20px; padding-top: 10px; text-align: center;">
<strong>*** END OF TICKET ***</strong>
</div>
<script>window.onload = function() { window.print(); setTimeout(function() { window.close(); }, 500); }<\/script>
</body>
</html>
`;
win.document.write(html);
win.document.close();
}
function updateStatus(orderId, newStatus) {
if (!CAN_EDIT) return;
Swal.fire({
title: 'Update Status?',
text: `Move order #${orderId} to ${newStatus}?`,
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, update it!'
}).then((result) => {
if (result.isConfirmed) {
performUpdate(orderId, newStatus);
}
});
}
async function performUpdate(orderId, newStatus) {
try {
const response = await fetch('api/kitchen.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order_id: orderId, status: newStatus })
});
const result = await response.json();
if (result.success) {
fetchOrders();
Swal.fire({
icon: 'success',
title: 'Updated!',
text: `Order #${orderId} moved to ${newStatus}`,
timer: 1500,
showConfirmButton: false
});
} else {
Swal.fire('Error', result.error || 'Failed to update', 'error');
}
} catch (error) {
console.error('Error updating status:', error);
Swal.fire('Error', 'Failed to connect to server', 'error');
}
}
async function serveAll() {
if (!CAN_EDIT) return;
const result = await Swal.fire({
title: 'Serve All Orders?',
text: "This will mark all active orders as completed and clear the screen.",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, Serve All!'
});
if (result.isConfirmed) {
try {
const response = await fetch('api/kitchen.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'serve_all', outlet_id: OUTLET_ID })
});
const data = await response.json();
if (data.success) {
fetchOrders();
Swal.fire('Cleared!', 'All orders have been served.', 'success');
} else {
Swal.fire('Error', data.error || 'Failed to clear orders', 'error');
}
} catch (error) {
console.error('Error:', error);
Swal.fire('Error', 'Server connection failed', 'error');
}
}
}
const outletSelector = document.getElementById('outlet-selector');
if (outletSelector) {
outletSelector.addEventListener('change', function() {
window.location.href = '?outlet_id=' + this.value;
});
}
setInterval(() => {
const clock = document.getElementById('clock');
if (clock) clock.textContent = new Date().toLocaleTimeString();
}, 1000);
fetchOrders();
setInterval(fetchOrders, 10000);
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>