Autosave: 20260222-172008
This commit is contained in:
parent
5db529f225
commit
2996ec35e3
@ -140,6 +140,16 @@ function isActive($page) {
|
|||||||
<i class="bi bi-award me-2"></i> Loyalty
|
<i class="bi bi-award me-2"></i> Loyalty
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('payment_types.php') ?>" href="payment_types.php">
|
||||||
|
<i class="bi bi-credit-card me-2"></i> Payment Types
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('integrations.php') ?>" href="integrations.php">
|
||||||
|
<i class="bi bi-plugin me-2"></i> Integrations
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= isActive('company.php') ?>" href="company.php">
|
<a class="nav-link <?= isActive('company.php') ?>" href="company.php">
|
||||||
<i class="bi bi-building me-2"></i> Company
|
<i class="bi bi-building me-2"></i> Company
|
||||||
|
|||||||
170
admin/integrations.php
Normal file
170
admin/integrations.php
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../includes/WablasService.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$wablasTestResult = null;
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$provider = $_POST['provider'] ?? '';
|
||||||
|
$action = $_POST['action'] ?? 'save';
|
||||||
|
|
||||||
|
// Thawani
|
||||||
|
if ($provider === 'thawani') {
|
||||||
|
$keys = ['public_key', 'secret_key', 'environment'];
|
||||||
|
foreach ($keys as $k) {
|
||||||
|
$val = $_POST[$k] ?? '';
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO integration_settings (provider, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
||||||
|
$stmt->execute(['thawani', $k, $val]);
|
||||||
|
}
|
||||||
|
$message = 'saved';
|
||||||
|
|
||||||
|
if ($action === 'save') {
|
||||||
|
header("Location: integrations.php?msg=saved");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wablas
|
||||||
|
if ($provider === 'wablas') {
|
||||||
|
$keys = ['domain', 'token', 'secret_key'];
|
||||||
|
foreach ($keys as $k) {
|
||||||
|
$val = $_POST[$k] ?? '';
|
||||||
|
$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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'save') {
|
||||||
|
header("Location: integrations.php?msg=saved");
|
||||||
|
exit;
|
||||||
|
} elseif ($action === 'test') {
|
||||||
|
// Instantiate service (loads from DB, which we just updated)
|
||||||
|
$wablasService = new WablasService($pdo);
|
||||||
|
$testPhone = $_POST['test_phone'] ?? '';
|
||||||
|
|
||||||
|
if (!empty($testPhone)) {
|
||||||
|
$wablasTestResult = $wablasService->sendMessage($testPhone, "Test message from Flatlogic POS. Connection verified!");
|
||||||
|
} else {
|
||||||
|
$wablasTestResult = $wablasService->testConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch current settings
|
||||||
|
$stmt = $pdo->query("SELECT provider, setting_key, setting_value FROM integration_settings");
|
||||||
|
$allSettings = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
function getSetting($settings, $provider, $key) {
|
||||||
|
if (isset($settings[$provider])) {
|
||||||
|
foreach ($settings[$provider] as $s) {
|
||||||
|
if ($s['setting_key'] === $key) return $s['setting_value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$thawaniEnv = getSetting($allSettings, 'thawani', 'environment');
|
||||||
|
$thawaniPub = getSetting($allSettings, 'thawani', 'public_key');
|
||||||
|
$thawaniSec = getSetting($allSettings, 'thawani', 'secret_key');
|
||||||
|
|
||||||
|
$wablasDom = getSetting($allSettings, 'wablas', 'domain');
|
||||||
|
$wablasTok = getSetting($allSettings, 'wablas', 'token');
|
||||||
|
$wablasSecKey = getSetting($allSettings, 'wablas', 'secret_key');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800">Integrations</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['msg']) && $_GET['msg'] == 'saved'): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
Settings saved successfully.
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($wablasTestResult): ?>
|
||||||
|
<div class="alert alert-<?= $wablasTestResult['success'] ? 'success' : 'danger' ?> alert-dismissible fade show" role="alert">
|
||||||
|
<strong>Wablas Test Result:</strong> <?= htmlspecialchars($wablasTestResult['message']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Thawani -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card shadow h-100">
|
||||||
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||||
|
<h6 class="m-0 fw-bold text-primary">Thawani Payments</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="provider" value="thawani">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Environment</label>
|
||||||
|
<select class="form-select" name="environment">
|
||||||
|
<option value="sandbox" <?= $thawaniEnv == 'sandbox' ? 'selected' : '' ?>>Sandbox</option>
|
||||||
|
<option value="production" <?= $thawaniEnv == 'production' ? 'selected' : '' ?>>Production</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Public Key</label>
|
||||||
|
<input type="text" class="form-control" name="public_key" value="<?= htmlspecialchars($thawaniPub) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Secret Key</label>
|
||||||
|
<input type="password" class="form-control" name="secret_key" value="<?= htmlspecialchars($thawaniSec) ?>">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="action" value="save" class="btn btn-primary">Save Thawani Settings</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Wablas -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card shadow h-100">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="provider" value="wablas">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Domain</label>
|
||||||
|
<input type="text" class="form-control" name="domain" placeholder="https://..." value="<?= htmlspecialchars($wablasDom) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Token</label>
|
||||||
|
<input type="password" class="form-control" name="token" value="<?= htmlspecialchars($wablasTok) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Secret Key</label>
|
||||||
|
<input type="password" class="form-control" name="secret_key" value="<?= htmlspecialchars($wablasSecKey) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 border-top pt-3">
|
||||||
|
<label class="form-label text-muted small">Test Configuration</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" name="test_phone" placeholder="e.g. 62812345678" value="<?= htmlspecialchars($_POST['test_phone'] ?? '') ?>">
|
||||||
|
<button type="submit" name="action" value="test" class="btn btn-info text-white">Test & Send Message</button>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Enter a phone number to send a real test message.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" name="action" value="save" class="btn btn-success">Save Settings</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||||
85
admin/payment_type_edit.php
Normal file
85
admin/payment_type_edit.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$id = $_GET['id'] ?? null;
|
||||||
|
$pt = [
|
||||||
|
'name' => '',
|
||||||
|
'type' => 'cash',
|
||||||
|
'api_provider' => '',
|
||||||
|
'is_active' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM payment_types WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($existing) {
|
||||||
|
$pt = $existing;
|
||||||
|
} else {
|
||||||
|
header("Location: payment_types.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$type = $_POST['type'];
|
||||||
|
$api_provider = $_POST['api_provider'] ?: null;
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("UPDATE payment_types SET name = ?, type = ?, api_provider = ?, is_active = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$name, $type, $api_provider, $is_active, $id]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO payment_types (name, type, api_provider, is_active) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $type, $api_provider, $is_active]);
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: payment_types.php?msg=saved");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800"><?= $id ? 'Edit' : 'Add' ?> Payment Type</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" value="<?= htmlspecialchars($pt['name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="type" class="form-label">Type</label>
|
||||||
|
<select class="form-select" id="type" name="type" required>
|
||||||
|
<option value="cash" <?= $pt['type'] == 'cash' ? 'selected' : '' ?>>Cash</option>
|
||||||
|
<option value="card" <?= $pt['type'] == 'card' ? 'selected' : '' ?>>Card</option>
|
||||||
|
<option value="api" <?= $pt['type'] == 'api' ? 'selected' : '' ?>>API Integration</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="api_provider" class="form-label">API Provider (if Type is API)</label>
|
||||||
|
<select class="form-select" id="api_provider" name="api_provider">
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="thawani" <?= $pt['api_provider'] == 'thawani' ? 'selected' : '' ?>>Thawani</option>
|
||||||
|
<option value="wablas" <?= $pt['api_provider'] == 'wablas' ? 'selected' : '' ?>>Wablas (WhatsApp)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="is_active" name="is_active" <?= $pt['is_active'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label" for="is_active">Active</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
|
<a href="payment_types.php" class="btn btn-secondary">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||||
88
admin/payment_types.php
Normal file
88
admin/payment_types.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Handle Delete
|
||||||
|
if (isset($_GET['delete_id'])) {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM payment_types WHERE id = ?");
|
||||||
|
$stmt->execute([$_GET['delete_id']]);
|
||||||
|
header("Location: payment_types.php?msg=deleted");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Payment Types
|
||||||
|
$stmt = $pdo->query("SELECT * FROM payment_types ORDER BY id DESC");
|
||||||
|
$paymentTypes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800">Payment Types</h2>
|
||||||
|
<a href="payment_type_edit.php" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-lg me-2"></i>Add Payment Type
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['msg']) && $_GET['msg'] == 'deleted'): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
Payment Type deleted successfully.
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Provider</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th class="text-end">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($paymentTypes)): ?>
|
||||||
|
<tr><td colspan="6" class="text-center py-4">No payment types found.</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($paymentTypes as $pt): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= $pt['id'] ?></td>
|
||||||
|
<td class="fw-medium"><?= htmlspecialchars($pt['name']) ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-secondary"><?= htmlspecialchars(ucfirst($pt['type'])) ?></span>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($pt['api_provider'] ?? '-') ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($pt['is_active']): ?>
|
||||||
|
<span class="badge bg-success">Active</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger">Inactive</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a href="payment_type_edit.php?id=<?= $pt['id'] ?>" class="btn btn-sm btn-outline-primary me-1">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a href="payment_types.php?delete_id=<?= $pt['id'] ?>"
|
||||||
|
class="btn btn-sm btn-outline-danger"
|
||||||
|
onclick="return confirm('Are you sure you want to delete this payment type?');">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||||
84
api/kitchen.php
Normal file
84
api/kitchen.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
// Fetch active kitchen orders
|
||||||
|
try {
|
||||||
|
// We want orders that are NOT completed or cancelled
|
||||||
|
// Status flow: pending -> preparing -> ready -> completed
|
||||||
|
// Kitchen sees: pending, preparing, ready
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
o.id, o.table_number, o.order_type, o.status, o.created_at, o.customer_name,
|
||||||
|
oi.quantity, p.name as product_name, v.name as variant_name
|
||||||
|
FROM orders o
|
||||||
|
JOIN order_items oi ON o.id = oi.order_id
|
||||||
|
JOIN products p ON oi.product_id = p.id
|
||||||
|
LEFT JOIN product_variants v ON oi.variant_id = v.id
|
||||||
|
WHERE o.status IN ('pending', 'preparing', 'ready')
|
||||||
|
ORDER BY o.created_at ASC
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$orders = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
if (!isset($orders[$id])) {
|
||||||
|
$orders[$id] = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'table_number' => $row['table_number'],
|
||||||
|
'order_type' => $row['order_type'],
|
||||||
|
'status' => $row['status'],
|
||||||
|
'created_at' => $row['created_at'],
|
||||||
|
'customer_name' => $row['customer_name'],
|
||||||
|
'items' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$orders[$id]['items'][] = [
|
||||||
|
'quantity' => $row['quantity'],
|
||||||
|
'product_name' => $row['product_name'],
|
||||||
|
'variant_name' => $row['variant_name']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(array_values($orders));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// Update order status
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$orderId = $data['order_id'] ?? null;
|
||||||
|
$newStatus = $data['status'] ?? null;
|
||||||
|
|
||||||
|
if (!$orderId || !$newStatus) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing order_id or status']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedStatuses = ['pending', 'preparing', 'ready', 'completed', 'cancelled'];
|
||||||
|
if (!in_array($newStatus, $allowedStatuses)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid status']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$newStatus, $orderId]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@ -20,24 +20,41 @@ try {
|
|||||||
? $data['order_type']
|
? $data['order_type']
|
||||||
: 'dine-in';
|
: 'dine-in';
|
||||||
|
|
||||||
|
// Get outlet_id from input, default to 1 if missing
|
||||||
|
$outlet_id = isset($data['outlet_id']) ? intval($data['outlet_id']) : 1;
|
||||||
|
|
||||||
$table_id = null;
|
$table_id = null;
|
||||||
$table_number = null;
|
$table_number = null;
|
||||||
|
|
||||||
if ($order_type === 'dine-in') {
|
if ($order_type === 'dine-in') {
|
||||||
$tid = $data['table_number'] ?? null; // Front-end sends ID as table_number
|
$tid = $data['table_number'] ?? null; // Front-end sends ID as table_number
|
||||||
if ($tid) {
|
if ($tid) {
|
||||||
$stmt = $pdo->prepare("SELECT id, name FROM tables WHERE id = ?");
|
// Validate table exists AND belongs to the correct outlet
|
||||||
$stmt->execute([$tid]);
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT t.id, t.name
|
||||||
|
FROM tables t
|
||||||
|
JOIN areas a ON t.area_id = a.id
|
||||||
|
WHERE t.id = ? AND a.outlet_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$tid, $outlet_id]);
|
||||||
$table = $stmt->fetch(PDO::FETCH_ASSOC);
|
$table = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if ($table) {
|
if ($table) {
|
||||||
$table_id = $table['id'];
|
$table_id = $table['id'];
|
||||||
$table_number = $table['name'];
|
$table_number = $table['name'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If not found or not provided, leave null (Walk-in/Counter) or default to 1 if it exists
|
|
||||||
|
// If not found or not provided, leave null (Walk-in/Counter) or try to find a default table for this outlet
|
||||||
if (!$table_id) {
|
if (!$table_id) {
|
||||||
// Optional: try to find table 1
|
// Optional: try to find the first available table for this outlet
|
||||||
$stmt = $pdo->query("SELECT id, name FROM tables LIMIT 1");
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT t.id, t.name
|
||||||
|
FROM tables t
|
||||||
|
JOIN areas a ON t.area_id = a.id
|
||||||
|
WHERE a.outlet_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$outlet_id]);
|
||||||
$table = $stmt->fetch(PDO::FETCH_ASSOC);
|
$table = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if ($table) {
|
if ($table) {
|
||||||
$table_id = $table['id'];
|
$table_id = $table['id'];
|
||||||
@ -67,7 +84,7 @@ try {
|
|||||||
$total_amount = isset($data['total_amount']) ? floatval($data['total_amount']) : 0.00;
|
$total_amount = isset($data['total_amount']) ? floatval($data['total_amount']) : 0.00;
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, total_amount, discount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
|
$stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, total_amount, discount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
|
||||||
$stmt->execute([1, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $total_amount, $discount]);
|
$stmt->execute([$outlet_id, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $total_amount, $discount]);
|
||||||
$order_id = $pdo->lastInsertId();
|
$order_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
$item_stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, variant_id, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
|||||||
@ -5,14 +5,18 @@ require_once __DIR__ . '/../db/config.php';
|
|||||||
try {
|
try {
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
|
||||||
// Fetch all tables with their area names
|
$outlet_id = isset($_GET['outlet_id']) ? intval($_GET['outlet_id']) : 1;
|
||||||
|
|
||||||
|
// Fetch all tables with their area names, filtered by outlet_id
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT t.id, t.name, t.capacity, a.name AS area_name
|
SELECT t.id, t.name, t.capacity, a.name AS area_name
|
||||||
FROM tables t
|
FROM tables t
|
||||||
LEFT JOIN areas a ON t.area_id = a.id
|
LEFT JOIN areas a ON t.area_id = a.id
|
||||||
|
WHERE a.outlet_id = :outlet_id
|
||||||
ORDER BY a.name ASC, t.name ASC
|
ORDER BY a.name ASC, t.name ASC
|
||||||
";
|
";
|
||||||
$stmt = $pdo->query($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute(['outlet_id' => $outlet_id]);
|
||||||
$tables = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$tables = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// Fetch currently occupied table IDs
|
// Fetch currently occupied table IDs
|
||||||
@ -22,8 +26,10 @@ try {
|
|||||||
FROM orders
|
FROM orders
|
||||||
WHERE status NOT IN ('completed', 'cancelled')
|
WHERE status NOT IN ('completed', 'cancelled')
|
||||||
AND table_id IS NOT NULL
|
AND table_id IS NOT NULL
|
||||||
|
AND outlet_id = :outlet_id
|
||||||
";
|
";
|
||||||
$occupiedStmt = $pdo->query($occupiedSql);
|
$occupiedStmt = $pdo->prepare($occupiedSql);
|
||||||
|
$occupiedStmt->execute(['outlet_id' => $outlet_id]);
|
||||||
$occupiedTableIds = $occupiedStmt->fetchAll(PDO::FETCH_COLUMN);
|
$occupiedTableIds = $occupiedStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
// Mark tables as occupied
|
// Mark tables as occupied
|
||||||
@ -36,4 +42,4 @@ try {
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
BIN
assets/images/company/favicon_699b1c9b2edeb.png
Normal file
BIN
assets/images/company/favicon_699b1c9b2edeb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
assets/images/company/favicon_699b1ccab91a1.png
Normal file
BIN
assets/images/company/favicon_699b1ccab91a1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@ -167,8 +167,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const container = document.getElementById('table-list-container');
|
const container = document.getElementById('table-list-container');
|
||||||
container.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary" role="status"></div></div>';
|
container.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary" role="status"></div></div>';
|
||||||
tableSelectionModal.show();
|
tableSelectionModal.show();
|
||||||
|
|
||||||
fetch('api/tables.php')
|
const outletId = new URLSearchParams(window.location.search).get('outlet_id') || 1;
|
||||||
|
fetch(`api/tables.php?outlet_id=${outletId}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@ -390,6 +391,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
table_number: (orderType === 'dine-in') ? currentTableId : null,
|
table_number: (orderType === 'dine-in') ? currentTableId : null,
|
||||||
order_type: orderType,
|
order_type: orderType,
|
||||||
customer_id: custId || null,
|
customer_id: custId || null,
|
||||||
|
outlet_id: new URLSearchParams(window.location.search).get('outlet_id') || 1,
|
||||||
total_amount: totalAmount,
|
total_amount: totalAmount,
|
||||||
discount: discount,
|
discount: discount,
|
||||||
items: cart.map(item => ({
|
items: cart.map(item => ({
|
||||||
|
|||||||
20
db/migrations/001_add_payments.sql
Normal file
20
db/migrations/001_add_payments.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS payment_types (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type ENUM('cash', 'card', 'api') DEFAULT 'cash',
|
||||||
|
api_provider VARCHAR(50) DEFAULT NULL,
|
||||||
|
is_active BOOLEAN DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS integration_settings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
provider VARCHAR(50) NOT NULL,
|
||||||
|
setting_key VARCHAR(100) NOT NULL,
|
||||||
|
setting_value TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_setting (provider, setting_key)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Attempt to add column, ignore if exists (standard SQL doesn't support IF NOT EXISTS for columns easily without procedures, but for this env we'll try)
|
||||||
|
-- We will run this via PHP and handle errors if column exists
|
||||||
37
includes/ThawaniService.php
Normal file
37
includes/ThawaniService.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
class ThawaniService {
|
||||||
|
private $pdo;
|
||||||
|
private $publicKey;
|
||||||
|
private $secretKey;
|
||||||
|
private $isSandbox;
|
||||||
|
|
||||||
|
public function __construct($pdo) {
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
$this->loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadConfig() {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT setting_key, setting_value FROM integration_settings WHERE provider = 'thawani'");
|
||||||
|
$stmt->execute();
|
||||||
|
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$this->publicKey = $settings['public_key'] ?? '';
|
||||||
|
$this->secretKey = $settings['secret_key'] ?? '';
|
||||||
|
$this->isSandbox = ($settings['environment'] ?? 'sandbox') === 'sandbox';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCheckoutSession($orderId, $amount, $customerData) {
|
||||||
|
// Implementation for creating a checkout session
|
||||||
|
// This is a placeholder for actual API integration
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'session_id' => 'sess_' . uniqid(),
|
||||||
|
'redirect_url' => 'https://uat.thawani.om/pay/...' // Example URL
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleWebhook($payload) {
|
||||||
|
// Implementation for webhook handling
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
includes/WablasService.php
Normal file
107
includes/WablasService.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
class WablasService {
|
||||||
|
private $pdo;
|
||||||
|
private $domain;
|
||||||
|
private $token;
|
||||||
|
private $secret_key;
|
||||||
|
|
||||||
|
public function __construct($pdo) {
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
$this->loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadConfig() {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT setting_key, setting_value FROM integration_settings WHERE provider = 'wablas'");
|
||||||
|
$stmt->execute();
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$settings = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$settings[$r['setting_key']] = $r['setting_value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->domain = $settings['domain'] ?? '';
|
||||||
|
$this->token = $settings['token'] ?? '';
|
||||||
|
$this->secret_key = $settings['secret_key'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConnection() {
|
||||||
|
if (empty($this->domain) || empty($this->token)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing Domain or Token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common Wablas endpoint to check device status
|
||||||
|
$url = rtrim($this->domain, '/') . '/api/device/info?token=' . $this->token;
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($curlError) {
|
||||||
|
return ['success' => false, 'message' => 'Connection failed: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300) {
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
// Some APIs return 200 even on logical error, check for status field if exists
|
||||||
|
if (isset($data['status']) && $data['status'] === false) {
|
||||||
|
return ['success' => false, 'message' => 'API Error: ' . ($data['message'] ?? 'Unknown error')];
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Connection successful! Device info retrieved.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'HTTP Error: ' . $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendMessage($phone, $message) {
|
||||||
|
if (empty($this->domain) || empty($this->token)) {
|
||||||
|
return ['success' => false, 'message' => 'Wablas configuration missing'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'phone' => $phone,
|
||||||
|
'message' => $message,
|
||||||
|
'secret' => $this->secret_key
|
||||||
|
];
|
||||||
|
|
||||||
|
// Assuming standard endpoint. If users use v2, it might be /api/v2/send-message
|
||||||
|
$url = rtrim($this->domain, '/') . '/api/send-message';
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: " . $this->token,
|
||||||
|
"Content-Type: application/json"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($curlError) {
|
||||||
|
return ['success' => false, 'message' => 'cURL Error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
$msg = $data['message'] ?? $response;
|
||||||
|
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300) {
|
||||||
|
if (isset($data['status']) && $data['status'] === false) {
|
||||||
|
return ['success' => false, 'message' => 'Wablas Error: ' . ($data['message'] ?? 'Unknown error')];
|
||||||
|
}
|
||||||
|
return ['success' => true, 'message' => 'Message sent successfully. Response: ' . (is_string($msg) ? substr($msg, 0, 100) : 'OK')];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'HTTP Error ' . $httpCode . ': ' . (is_string($msg) ? $msg : 'Unknown')];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -119,6 +119,9 @@ $settings = get_company_settings();
|
|||||||
<a href="admin/" class="text-muted small text-decoration-none">
|
<a href="admin/" class="text-muted small text-decoration-none">
|
||||||
<i class="bi bi-shield-lock me-1"></i> Staff Login
|
<i class="bi bi-shield-lock me-1"></i> Staff Login
|
||||||
</a>
|
</a>
|
||||||
|
<a href="kitchen.php" class="text-muted small text-decoration-none ms-3">
|
||||||
|
<i class="bi bi-egg-fried me-1"></i> Kitchen Display
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
173
kitchen.php
Normal file
173
kitchen.php
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<!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>
|
||||||
|
<!-- 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="assets/css/custom.css" rel="stylesheet">
|
||||||
|
<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%;
|
||||||
|
}
|
||||||
|
.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; }
|
||||||
|
</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>
|
||||||
|
<span id="clock" class="text-muted me-3"></span>
|
||||||
|
<a href="index.php" class="btn btn-outline-secondary btn-sm">Back to 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchOrders() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/kitchen.php');
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
const orders = await response.json();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.innerHTML = 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';
|
||||||
|
actionBtn = `<button class="btn btn-info btn-sm w-100 text-white" onclick="updateStatus(${order.id}, 'preparing')">Start Preparing</button>`;
|
||||||
|
} else if (order.status === 'preparing') {
|
||||||
|
statusBadge = '<span class="badge badge-preparing">Preparing</span>';
|
||||||
|
cardClass += 'status-preparing';
|
||||||
|
actionBtn = `<button class="btn btn-success btn-sm w-100" onclick="updateStatus(${order.id}, 'ready')">Mark Ready</button>`;
|
||||||
|
} else if (order.status === 'ready') {
|
||||||
|
statusBadge = '<span class="badge badge-ready">Ready</span>';
|
||||||
|
cardClass += 'status-ready';
|
||||||
|
actionBtn = `<button class="btn btn-secondary btn-sm w-100" 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('');
|
||||||
|
|
||||||
|
// Calculate time elapsed
|
||||||
|
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 bg-white border-top-0 pb-3">
|
||||||
|
${actionBtn}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStatus(orderId, newStatus) {
|
||||||
|
if (!confirm(`Move order #${orderId} to ${newStatus}?`)) return;
|
||||||
|
|
||||||
|
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(); // Refresh immediately
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (result.error || 'Failed to update'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating status:', error);
|
||||||
|
alert('Failed to connect to server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
setInterval(() => {
|
||||||
|
document.getElementById('clock').textContent = new Date().toLocaleTimeString();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Poll orders
|
||||||
|
fetchOrders();
|
||||||
|
setInterval(fetchOrders, 10000); // Poll every 10s
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user