diff --git a/admin/order_view.php b/admin/order_view.php
index adb4672..debcb03 100644
--- a/admin/order_view.php
+++ b/admin/order_view.php
@@ -14,7 +14,7 @@ if (!$id) {
// Fetch Order Details
$stmt = $pdo->prepare("SELECT o.*, ot.name as outlet_name, pt.name as payment_type_name,
- c.name as customer_name, c.phone as customer_phone, c.email as customer_email, c.address as customer_address,
+ c.name as customer_name, c.phone as customer_phone, c.email as customer_email, c.address as customer_address, o.car_plate,
u.username as created_by_username
FROM orders o
LEFT JOIN outlets ot ON o.outlet_id = ot.id
@@ -293,6 +293,13 @@ $vat_rate = (float)($company_settings['vat_rate'] ?? 0);
Customer ID: #= $order['customer_id'] ?>
+
+
diff --git a/admin/orders.php b/admin/orders.php
index fa9ad9f..9422f25 100644
--- a/admin/orders.php
+++ b/admin/orders.php
@@ -282,6 +282,7 @@ include 'includes/header.php';
Cashier
Customer
Type
+
Car Plate
VAT
Total
@@ -309,6 +310,9 @@ include 'includes/header.php';
= htmlspecialchars((string)($order['customer_name'] ?? '')) ?>
+
+ = htmlspecialchars((string)($order['car_plate'] ?? '')) ?>
+
= htmlspecialchars((string)($order['customer_phone'] ?? '')) ?>
diff --git a/api/kitchen.php b/api/kitchen.php
index fca76e8..19a2acb 100644
--- a/api/kitchen.php
+++ b/api/kitchen.php
@@ -14,7 +14,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// 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,
+ o.id, o.table_number, o.order_type, o.status, o.created_at, o.ready_time, o.customer_name, o.car_plate, o.user_id,
oi.quantity, COALESCE(p.name, oi.product_name) as product_name, COALESCE(v.name, oi.variant_name) as variant_name
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
@@ -38,6 +38,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'status' => $row['status'],
'created_at' => $row['created_at'],
'customer_name' => $row['customer_name'],
+ 'car_plate' => $row['car_plate'],
+ 'ready_time' => $row['ready_time'],
+'user_id' => $row['user_id'], 'is_online_order' => ($row['user_id'] === null && $row['order_type'] !== 'dine-in'), 'is_table_order' => ($row['user_id'] === null && $row['order_type'] === 'dine-in'),
'items' => []
];
}
diff --git a/api/order.php b/api/order.php
index e316b43..7f47608 100644
--- a/api/order.php
+++ b/api/order.php
@@ -70,9 +70,37 @@ try {
}
// Customer Handling
+ $register_customer = !empty($data['register_customer']);
$customer_id = !empty($data['customer_id']) ? intval($data['customer_id']) : null;
$customer_name = $data['customer_name'] ?? null;
$customer_phone = $data['customer_phone'] ?? null;
+ $car_plate = $data['car_plate'] ?? null;
+ $prep_time_minutes = isset($data['prep_time_minutes']) ? intval($data['prep_time_minutes']) : null;
+ $ready_time = null;
+ if ($prep_time_minutes > 0) {
+ $ready_time = date("Y-m-d H:i:s", strtotime("+$prep_time_minutes minutes"));
+ }
+
+ $register_customer = !empty($data['register_customer']);
+ if (!$customer_id && $customer_phone && $register_customer) {
+ $stmt = $pdo->prepare("SELECT id FROM customers WHERE phone = ?");
+ $stmt->execute([$customer_phone]);
+ $existing = $stmt->fetch();
+ if ($existing) {
+ $customer_id = $existing['id'];
+ } else {
+ $stmt = $pdo->prepare("INSERT INTO customers (name, phone) VALUES (?, ?)");
+ $stmt->execute([$customer_name, $customer_phone]);
+ $customer_id = $pdo->lastInsertId();
+ }
+ } else if (!$customer_id && $customer_phone && !$register_customer) {
+ $stmt = $pdo->prepare("SELECT id FROM customers WHERE phone = ?");
+ $stmt->execute([$customer_phone]);
+ $existing = $stmt->fetch();
+ if ($existing) {
+ $customer_id = $existing['id'];
+ }
+ }
// Fetch Loyalty Settings
$settingsStmt = $pdo->query("SELECT is_enabled, points_per_order, points_for_free_meal FROM loyalty_settings WHERE id = 1");
@@ -309,13 +337,13 @@ try {
if ($is_update) {
$stmt = $pdo->prepare("UPDATE orders SET
outlet_id = ?, table_id = ?, table_number = ?, order_type = ?,
- customer_id = ?, customer_name = ?, customer_phone = ?,
+ customer_id = ?, customer_name = ?, customer_phone = ?, car_plate = ?, ready_time = ?,
payment_type_id = ?, total_amount = ?, discount = ?, vat = ?, user_id = ?,
commission_amount = ?, status = 'pending'
WHERE id = ?");
$stmt->execute([
$outlet_id, $table_id, $table_number, $order_type,
- $customer_id, $customer_name, $customer_phone,
+ $customer_id, $customer_name, $customer_phone, $car_plate, $ready_time,
$payment_type_id, $final_total, 0, $calculated_vat, $user_id,
$commission_amount, $order_id
]);
@@ -323,8 +351,8 @@ try {
$delStmt = $pdo->prepare("DELETE FROM order_items WHERE order_id = ?");
$delStmt->execute([$order_id]);
} else {
- $stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, payment_type_id, total_amount, discount, vat, user_id, commission_amount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
- $stmt->execute([$outlet_id, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $payment_type_id, $final_total, 0, $calculated_vat, $user_id, $commission_amount]);
+ $stmt = $pdo->prepare("INSERT INTO orders (outlet_id, table_id, table_number, order_type, customer_id, customer_name, customer_phone, car_plate, ready_time, payment_type_id, total_amount, discount, vat, user_id, commission_amount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')");
+ $stmt->execute([$outlet_id, $table_id, $table_number, $order_type, $customer_id, $customer_name, $customer_phone, $car_plate, $ready_time, $payment_type_id, $final_total, 0, $calculated_vat, $user_id, $commission_amount]);
$order_id = $pdo->lastInsertId();
}
diff --git a/assets/js/main.js b/assets/js/main.js
index 232d351..5f0b592 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}) : null;
+ window.showToast = showToast;
function showToast(msg, type = 'primary') {
if (!Toast) {
console.log(`Toast: ${msg} (${type})`);
diff --git a/customer_profile.php b/customer_profile.php
new file mode 100644
index 0000000..d62439b
--- /dev/null
+++ b/customer_profile.php
@@ -0,0 +1,143 @@
+prepare("SELECT * FROM customers WHERE phone = ?");
+ $stmt->execute([$phone]);
+ $customer = $stmt->fetch(PDO::FETCH_ASSOC);
+ if ($customer) {
+ $stmt_orders = $pdo->prepare("SELECT o.id, o.created_at, o.total_amount, o.status, o.car_plate, o.ready_time,
+ (SELECT GROUP_CONCAT(CONCAT(oi.quantity, 'x ', oi.product_name) SEPARATOR ', ') FROM order_items oi WHERE oi.order_id = o.id) as items_summary
+ FROM orders o WHERE o.customer_id = ? ORDER BY o.created_at DESC");
+ $stmt_orders->execute([$customer['id']]);
+ $orders = $stmt_orders->fetchAll(PDO::FETCH_ASSOC);
+ $stmt_points = $pdo->prepare("SELECT * FROM loyalty_points_history WHERE customer_id = ? ORDER BY created_at DESC");
+ $stmt_points->execute([$customer['id']]);
+ $points_history = $stmt_points->fetchAll(PDO::FETCH_ASSOC);
+ } else {
+ $error = "Customer not found with this phone number.";
+ }
+}
+?>
+
+
+
+
+
+ My Profile - = htmlspecialchars($settings['company_name'] ?? 'Order Online') ?>
+
+
+
+
+
+
+
+
+
+
+
Login to view profile
+
+
= htmlspecialchars($error) ?>
+
+
+
+
+
+
Welcome back,
+
= htmlspecialchars($customer['name']) ?>
+
+
Available Points
= (int)$customer['points'] ?>
+
+
+
+
Order History
+
+
No orders found.
+
+
+
+
+ Order #= $order['id'] ?>
+ = ucfirst($order['status']) ?>
+
+
= date('M d, Y h:i A', strtotime($order['created_at'])) ?>
+
= htmlspecialchars($order['items_summary']) ?>
+
= htmlspecialchars($order['car_plate']) ?>
+
+
+
+
= number_format($order['total_amount'], 3) ?> OMR
+
+
+
+
Points History
+
+
No points history found.
+
+
+
+
+
+
+
+
= htmlspecialchars($hist['reason']) ?>
+
= date('M d, Y h:i A', strtotime($hist['created_at'])) ?>
+
+
+ = $hist['points_change'] > 0 ? '+' : '' ?>= $hist['points_change'] ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/db/migrations/047_add_car_plate_to_orders.sql b/db/migrations/047_add_car_plate_to_orders.sql
new file mode 100644
index 0000000..4391e9b
--- /dev/null
+++ b/db/migrations/047_add_car_plate_to_orders.sql
@@ -0,0 +1,18 @@
+-- Add car_plate column to orders table
+SET @dbname = DATABASE();
+SET @tablename = "orders";
+SET @columnname = "car_plate";
+SET @preparedStatement = (SELECT IF(
+ (
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE
+ (table_name = @tablename)
+ AND (table_schema = @dbname)
+ AND (column_name = @columnname)
+ ) > 0,
+ "SELECT 1",
+ "ALTER TABLE orders ADD COLUMN car_plate VARCHAR(50) DEFAULT NULL AFTER customer_phone;"
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
diff --git a/db/migrations/048_add_ready_time_to_orders.sql b/db/migrations/048_add_ready_time_to_orders.sql
new file mode 100644
index 0000000..bf4395a
--- /dev/null
+++ b/db/migrations/048_add_ready_time_to_orders.sql
@@ -0,0 +1 @@
+ALTER TABLE orders ADD COLUMN ready_time DATETIME NULL AFTER created_at;
\ No newline at end of file
diff --git a/includes/PrinterService.php b/includes/PrinterService.php
index 92fc9f8..9707195 100644
--- a/includes/PrinterService.php
+++ b/includes/PrinterService.php
@@ -59,6 +59,15 @@ class PrinterService {
$out .= $esc . "a" . "\x00"; // Left align
$out .= "Order ID: #" . $order['id'] . "\n";
$out .= "Date: " . ($order['created_at'] ?? date('Y-m-d H:i:s')) . "\n";
+ if (!empty($order['customer_name'])) {
+ $out .= "Customer: " . $order['customer_name'] . "\n";
+ }
+ if (!empty($order['customer_phone'])) {
+ $out .= "Phone: " . $order['customer_phone'] . "\n";
+ }
+ if (!empty($order['car_plate'])) {
+ $out .= "Car Plate: " . $order['car_plate'] . "\n";
+ }
$out .= $line;
foreach ($items as $item) {
diff --git a/index.php b/index.php
index a943053..3b818a6 100644
--- a/index.php
+++ b/index.php
@@ -230,6 +230,11 @@ $baseUrl = get_base_url();
+
+
+ Online Order
+ Place an order for pickup or delivery.
+
diff --git a/kitchen.php b/kitchen.php
index 6d21885..c57994d 100644
--- a/kitchen.php
+++ b/kitchen.php
@@ -147,12 +147,74 @@ const COMPANY_SETTINGS = = json_encode($settings) ?>;
const BASE_URL = '= get_base_url() ?>';
let activeOrders = [];
+let seenOrderIds = new Set();
+let isFirstLoad = true;
+const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+
+document.addEventListener('click', () => {
+ if (audioCtx && audioCtx.state === 'suspended') {
+ audioCtx.resume();
+ }
+});
+
+function playOnlineSound() {
+ if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
+ [0, 0.15, 0.3].forEach(startTime => {
+ const osc = audioCtx.createOscillator();
+ const gainNode = audioCtx.createGain();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(800, audioCtx.currentTime + startTime);
+ gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime + startTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + startTime + 0.1);
+ osc.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+ osc.start(audioCtx.currentTime + startTime);
+ osc.stop(audioCtx.currentTime + startTime + 0.15);
+ });
+}
+
+function playTableSound() {
+ if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
+ [0, 0.3].forEach(startTime => {
+ const osc = audioCtx.createOscillator();
+ const gainNode = audioCtx.createGain();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(500, audioCtx.currentTime + startTime);
+ gainNode.gain.setValueAtTime(0.2, audioCtx.currentTime + startTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + startTime + 0.25);
+ osc.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+ osc.start(audioCtx.currentTime + startTime);
+ osc.stop(audioCtx.currentTime + startTime + 0.3);
+ });
+}
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();
+
+ let newOnlineCount = 0;
+ let newTableCount = 0;
+
+ orders.forEach(order => {
+ if (!seenOrderIds.has(order.id)) {
+ if (!isFirstLoad) {
+ if (order.is_online_order) newOnlineCount++;
+ else if (order.is_table_order) newTableCount++;
+ }
+ seenOrderIds.add(order.id);
+ }
+ });
+
+ if (newOnlineCount > 0) {
+ playOnlineSound();
+ } else if (newTableCount > 0) {
+ playTableSound();
+ }
+
+ isFirstLoad = false;
activeOrders = orders;
renderOrders(orders);
} catch (error) {
@@ -160,6 +222,21 @@ async function fetchOrders() {
}
}
+function updateTimers() {
+ document.querySelectorAll('.countdown-timer').forEach(el => { const readyTime = parseInt(el.getAttribute('data-ready-time'));
+ const now = new Date().getTime();
+ const diff = readyTime - now;
+ if (diff > 0) {
+ const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
+ const s = Math.floor((diff % (1000 * 60)) / 1000);
+ el.querySelector('span').innerText = `Ready in: ${m}m ${s}s`;
+ } else {
+ el.querySelector('span').innerText = 'Should be ready!';
+ }
+ });
+}
+setInterval(updateTimers, 1000);
+
function renderOrders(orders) {
const grid = document.getElementById('kitchen-grid');
@@ -207,6 +284,8 @@ function renderOrders(orders) {
${order.order_type === 'dine-in' ? `Table ${order.table_number}` : order.order_type.toUpperCase()}
${order.customer_name ? `${order.customer_name} ` : ''}
+ ${order.car_plate ? ` ${order.car_plate} ` : ''}
+ ${order.ready_time ? ` ` : ''}
${itemsHtml}
@@ -273,6 +352,7 @@ function printKitchenTicket(orderId) {
${order.order_type === 'dine-in' ? `Table: ${order.table_number}
` : ''}
Time: ${new Date(order.created_at).toLocaleString()}
${order.customer_name ? `Cust: ${order.customer_name}
` : ''}
+ ${order.car_plate ? `Car: ${order.car_plate}
` : ''}
diff --git a/online_order.php b/online_order.php
new file mode 100644
index 0000000..97daad1
--- /dev/null
+++ b/online_order.php
@@ -0,0 +1,894 @@
+ [
+ 'order_online' => 'اطلب عبر الإنترنت',
+ 'my_profile_points' => 'ملفي الشخصي والنقاط',
+ 'table' => 'طاولة',
+ 'all' => 'الكل',
+ 'add_to_cart' => 'أضف إلى السلة',
+ 'options' => 'الخيارات',
+ 'view_cart' => 'عرض السلة',
+ 'review_order' => 'مراجعة الطلب',
+ 'name' => 'الاسم',
+ 'your_name' => 'اسمك',
+ 'phone' => 'رقم الهاتف',
+ 'your_phone' => 'رقم هاتفك',
+ 'car_plate' => 'رقم لوحة السيارة (اختياري)',
+ 'car_plate_placeholder' => 'مثال: 1234 أ',
+ 'ready_in' => 'وقت التحضير',
+ 'ready_asap' => 'في أسرع وقت',
+ 'ready_10m' => 'بعد 10 دقائق',
+ 'ready_15m' => 'بعد 15 دقيقة',
+ 'ready_20m' => 'بعد 20 دقيقة',
+ 'ready_30m' => 'بعد 30 دقيقة',
+ 'register_to_earn' => 'سجل لكسب النقاط وعرض السجل',
+ 'subtotal' => 'المجموع الفرعي',
+ 'total' => 'المجموع',
+ 'place_order' => 'تأكيد الطلب',
+ 'please_select_option' => 'يرجى تحديد خيار',
+ 'please_enter_details' => 'يرجى إدخال اسمك ورقم هاتفك.',
+ 'order_placed_success' => 'تم الطلب بنجاح!',
+ 'error' => 'خطأ:',
+ 'failed_to_place' => 'فشل في إتمام الطلب. يرجى المحاولة مرة أخرى.',
+ 'all_rights_reserved' => 'جميع الحقوق محفوظة.',
+ 'not_found' => 'لم يتم العثور على الطاولة. يرجى الاتصال بالموظفين.',
+ 'language' => 'English'
+ ],
+ 'en' => [
+ 'order_online' => 'Order Online',
+ 'my_profile_points' => 'My Profile & Points',
+ 'table' => 'Table',
+ 'all' => 'All',
+ 'add_to_cart' => 'Add to Cart',
+ 'options' => 'Options',
+ 'view_cart' => 'View Cart',
+ 'review_order' => 'Review Order',
+ 'name' => 'Name',
+ 'your_name' => 'Your Name',
+ 'phone' => 'Phone',
+ 'your_phone' => 'Your Phone Number',
+ 'car_plate' => 'Car Plate No. (Optional)',
+ 'car_plate_placeholder' => 'e.g. 1234 A',
+ 'ready_in' => 'Ready In',
+ 'ready_asap' => 'ASAP',
+ 'ready_10m' => 'After 10 mins',
+ 'ready_15m' => 'After 15 mins',
+ 'ready_20m' => 'After 20 mins',
+ 'ready_30m' => 'After 30 mins',
+ 'register_to_earn' => 'Register to earn points and view history',
+ 'subtotal' => 'Subtotal',
+ 'total' => 'Total',
+ 'place_order' => 'Place Order',
+ 'please_select_option' => 'Please select an option',
+ 'please_enter_details' => 'Please enter your name and phone number.',
+ 'order_placed_success' => 'Order placed successfully!',
+ 'error' => 'Error:',
+ 'failed_to_place' => 'Failed to place order. Please try again.',
+ 'all_rights_reserved' => 'All rights reserved.',
+ 'not_found' => 'Table not found. Please contact staff.',
+ 'language' => 'عربي'
+ ]
+];
+
+function tt($key) {
+ global $translations, $lang;
+ return $translations[$lang][$key] ?? $key;
+}
+
+$pdo = db();
+$settings = get_company_settings();
+
+$table_id = isset($_GET['table_id']) ? (int)$_GET['table_id'] : (isset($_GET['table']) ? (int)$_GET['table'] : 0);
+$table_info = null;
+
+if ($table_id === 0 && isset($_GET['table_number'])) {
+ $stmt = $pdo->prepare('SELECT id FROM `tables` WHERE table_number = ? AND is_deleted = 0 LIMIT 1');
+ $stmt->execute([$_GET['table_number']]);
+ $table_id = (int)$stmt->fetchColumn();
+}
+if ($table_id > 0) {
+ try {
+ $stmt = $pdo->prepare("
+ SELECT t.id, t.table_number AS table_name, a.outlet_id, o.name AS outlet_name, o.name_ar AS outlet_name_ar
+ FROM `tables` t
+ JOIN areas a ON t.area_id = a.id
+ JOIN outlets o ON a.outlet_id = o.id
+ WHERE t.id = ?
+ ");
+ $stmt->execute([$table_id]);
+ $table_info = $stmt->fetch();
+
+ if (!$table_info) {
+ die(tt('not_found'));
+ }
+ } catch (PDOException $e) {
+ die("Database error: " . $e->getMessage());
+ }
+}
+
+$outlet_id = (int)($table_info['outlet_id'] ?? 0);
+$categories = $pdo->query("SELECT * FROM categories WHERE is_deleted = 0 ORDER BY sort_order")->fetchAll();
+$all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id WHERE p.is_deleted = 0 AND p.show_in_qorder = 1 AND c.is_deleted = 0")->fetchAll();
+
+// Fetch variants
+$variants_raw = $pdo->query("SELECT * FROM product_variants WHERE is_deleted = 0 ORDER BY price_adjustment ASC")->fetchAll();
+$variants_by_product = [];
+foreach ($variants_raw as $v) {
+ $variants_by_product[$v['product_id']][] = $v;
+}
+
+// Build translated structures for JS
+$js_products = array_map(function($p) use ($is_ar) {
+ $p['display_name'] = ($is_ar && !empty($p['name_ar'])) ? $p['name_ar'] : $p['name'];
+ return $p;
+}, $all_products);
+
+$js_variants = [];
+foreach ($variants_by_product as $pid => $vars) {
+ $js_variants[$pid] = array_map(function($v) use ($is_ar) {
+ $v['display_name'] = ($is_ar && !empty($v['name_ar'])) ? $v['name_ar'] : $v['name'];
+ return $v;
+ }, $vars);
+}
+?>
+
+
+
+
+
+ = htmlspecialchars($settings['company_name'] ?? tt('order_online')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = tt('language') ?>
+
+
+
+
+
+
= htmlspecialchars($settings['company_name'] ?? 'Restaurant') ?>
+
+
+
+
+
+ = tt('table') ?> = htmlspecialchars($table_info['table_name'] ?? 'N/A') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($p['display_name']) ?>
+
+ = number_format($p['price'], 2) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= tt('add_to_cart') ?>
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/patch_badge.php b/patch_badge.php
deleted file mode 100644
index 2b45e29..0000000
--- a/patch_badge.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
- Loyalty
-
-
-HTML;
-
-$replace = $search . <<
-
- QR Menu
-
-
-HTML;
-
-$content = str_replace($search, $replace, $content);
-file_put_contents('admin/products.php', $content);
-echo "Badge added.\n";
\ No newline at end of file
diff --git a/pos.php b/pos.php
index 8ae9e51..ecf1d4d 100644
--- a/pos.php
+++ b/pos.php
@@ -486,6 +486,87 @@ $vat_rate = (float)($settings['vat_rate'] ?? 0);
}
};
const t = (key) => translations[LANG][key] || key;
+
+ // --- Audio Notifications Polling ---
+ let seenOrderIds = new Set();
+ let isFirstLoad = true;
+ const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+
+ document.addEventListener('click', () => {
+ if (audioCtx && audioCtx.state === 'suspended') {
+ audioCtx.resume();
+ }
+ });
+
+ function playOnlineSound() {
+ if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
+ [0, 0.15, 0.3].forEach(startTime => {
+ const osc = audioCtx.createOscillator();
+ const gainNode = audioCtx.createGain();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(800, audioCtx.currentTime + startTime);
+ gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime + startTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + startTime + 0.1);
+ osc.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+ osc.start(audioCtx.currentTime + startTime);
+ osc.stop(audioCtx.currentTime + startTime + 0.15);
+ });
+ }
+
+ function playTableSound() {
+ if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
+ [0, 0.3].forEach(startTime => {
+ const osc = audioCtx.createOscillator();
+ const gainNode = audioCtx.createGain();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(500, audioCtx.currentTime + startTime);
+ gainNode.gain.setValueAtTime(0.2, audioCtx.currentTime + startTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + startTime + 0.25);
+ osc.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+ osc.start(audioCtx.currentTime + startTime);
+ osc.stop(audioCtx.currentTime + startTime + 0.3);
+ });
+ }
+
+ async function pollForNewOrders() {
+ if (!CURRENT_OUTLET || !CURRENT_OUTLET.id) return;
+ try {
+ const response = await fetch('api/kitchen.php?outlet_id=' + CURRENT_OUTLET.id);
+ if (!response.ok) return;
+ const orders = await response.json();
+
+ let newOnlineCount = 0;
+ let newTableCount = 0;
+
+ orders.forEach(order => {
+ if (!seenOrderIds.has(order.id)) {
+ if (!isFirstLoad) {
+ if (order.is_online_order) newOnlineCount++;
+ else if (order.is_table_order) newTableCount++;
+ }
+ seenOrderIds.add(order.id);
+ }
+ });
+
+ if (newOnlineCount > 0) {
+ playOnlineSound();
+ if (typeof showToast === 'function') showToast('New Online Order!', 'warning');
+ } else if (newTableCount > 0) {
+ playTableSound();
+ if (typeof showToast === 'function') showToast('New Table Order!', 'info');
+ }
+
+ isFirstLoad = false;
+ } catch (e) {
+ // Ignore polling errors
+ }
+ }
+
+ setInterval(pollForNewOrders, 10000);
+ pollForNewOrders();
+ // ------------------------------------