File manager - Edit - /home/webapp69.cm.in.th/u69319090029/Project SME/queue-tv.php
Back
<?php header('Content-Type: text/html; charset=utf-8'); /** * CMTC Tech Solution - Central Queue TV Display Dashboard * Theme: Private CMTC Tech Solution Theme * Features: High-End Hospital-Style Queue Display, Real-time JSON Polling, Web Speech API (Text-to-Speech), Light/Dark Theme. */ require_once 'db.php'; $user_role_home = 'index.php'; if (!empty($_SESSION['super_admin_logged_in'])) { $user_role_home = 'platform-admin.php'; } elseif (!empty($_SESSION['store_id']) || !empty($_SESSION['store_admin_logged_in'])) { $user_role_home = 'store-admin.php'; } elseif (!empty($_GET['shop_id']) || !empty($_GET['store_id'])) { $current_s_id = !empty($_GET['shop_id']) ? intval($_GET['shop_id']) : intval($_GET['store_id']); $user_role_home = 'menu.php?shop_id=' . $current_s_id; } $store_id = isset($_GET['store_id']) ? (int)$_GET['store_id'] : (isset($_GET['shop_id']) ? (int)$_GET['shop_id'] : 0); // JSON API Endpoint for AJAX Polling if (isset($_GET['ajax']) && $_GET['ajax'] === '1') { header('Content-Type: application/json; charset=utf-8'); try { if ($store_id <= 0) { throw new Exception("กรุณาระบุ store_id"); } // Fetch pending orders (FIFO) $stmt_pending = $pdo->prepare("SELECT id, table_number, status, DATE_FORMAT(created_at, '%H:%i') as time_str FROM orders WHERE status IN ('pending', 'preparing') AND store_id = :store_id ORDER BY created_at ASC"); $stmt_pending->execute([':store_id' => $store_id]); $pending = $stmt_pending->fetchAll(); // Fetch ready orders (LIFO) $stmt_ready = $pdo->prepare("SELECT id, table_number, status, DATE_FORMAT(created_at, '%H:%i') as time_str FROM orders WHERE status IN ('ready', 'served') AND store_id = :store_id ORDER BY created_at DESC LIMIT 12"); $stmt_ready->execute([':store_id' => $store_id]); $ready = $stmt_ready->fetchAll(); echo json_encode([ 'success' => true, 'pending' => $pending, 'ready' => $ready ]); } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => $e->getMessage() ]); } exit; } $shop_info = null; $active_shops = []; if ($store_id > 0) { try { $stmt_shop = $pdo->prepare("SELECT store_id as id, store_name as name, address, category FROM tenants WHERE store_id = :id AND status = 'active'"); $stmt_shop->execute([':id' => $store_id]); $shop_info = $stmt_shop->fetch(); } catch (PDOException $e) { $shop_info = null; } } if (!$shop_info) { try { $stmt_active = $pdo->query("SELECT store_id as id, store_name as name, category, address FROM tenants WHERE status = 'active' ORDER BY store_id DESC"); $active_shops = $stmt_active->fetchAll(); } catch (PDOException $e) { $active_shops = []; } } ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CMTC Tech Solution - จอทีวีแสดงคิวส่วนกลาง (Central Queue TV Display)</title> <link rel="stylesheet" href="style.css"> <!-- SweetAlert2 CDN --> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <!-- Navigation Sound & SweetAlert Engine --> <script src="js/nav_sound_swal.js"></script> <script> // Pre-check theme mode to prevent flashing if (localStorage.getItem('theme-mode') === 'light') { document.documentElement.classList.add('light-theme-init'); } </script> </head> <body class="qtv-body"> <script> if (localStorage.getItem('theme-mode') === 'light') { document.body.classList.add('light-theme'); } </script> <?php if (!$shop_info): ?> <!-- Shop Directory Layout --> <div style="max-width: 900px; margin: 60px auto; padding: 30px; font-family: var(--font-official); color: #fff; background: rgba(15, 23, 42, 0.6); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.05); box-shadow: 0 10px 30px rgba(0,0,0,0.3);"> <div style="text-align: center; margin-bottom: 45px;"> <span style="font-size: 55px;">📺</span> <h2 style="font-size: 28px; font-weight: 800; color: #38bdf8; margin-top: 15px;">เลือกเปิดจอทีวีแสดงคิว (Queue TV Screen)</h2> <p style="color: #94a3b8; font-size: 14.5px; margin-top: 6px;">โปรดเลือกร้านอาหารที่ท่านต้องการเปิดการแจ้งเตือนเสียงและคิวอัพเดตเรียลไทม์บนหน้าจอทีวี</p> </div> <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 25px;"> <?php if (empty($active_shops)): ?> <div style="grid-column: 1/-1; text-align: center; padding: 60px; background: rgba(255,255,255,0.02); border: 1px dashed rgba(255,255,255,0.1); border-radius: 12px; color: #94a3b8;"> 📭 ขณะนี้ยังไม่มีร้านค้าเปิดให้บริการในระบบ </div> <?php else: ?> <?php foreach ($active_shops as $ash): ?> <div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05); border-radius: 12px; padding: 25px; display: flex; flex-direction: column; justify-content: space-between; transition: transform 0.2s ease;"> <div> <h3 style="font-size: 19px; font-weight: 800; color: #38bdf8; margin-bottom: 8px;"><?php echo htmlspecialchars($ash['name']); ?></h3> <?php if (!empty($ash['address'])): ?> <p style="font-size: 13.5px; color: #94a3b8; margin-bottom: 12px; line-height: 1.5;">📍 <?php echo htmlspecialchars($ash['address']); ?></p> <?php endif; ?> </div> <a href="queue-tv.php?store_id=<?php echo $ash['id']; ?>" class="btn btn-primary" style="text-align: center; text-decoration: none; display: block; font-weight: 800; font-size: 14px; padding: 12px 15px; border-radius: 6px; background: #38bdf8; border-color: #38bdf8; color: #0f172a;"> 📺 เปิดหน้าจอทีวี </a> </div> <?php endforeach; ?> <?php endif; ?> </div> <div style="margin-top: 40px; text-align: center;"> <a href="<?php echo htmlspecialchars($user_role_home); ?>" style="color: #94a3b8; font-weight: 700; text-decoration: none; font-size: 14px;">🏠 ย้อนกลับหน้าหลัก CMTC Tech Solution Cloud</a> </div> </div> <?php else: ?> <!-- Central Queue TV Header --> <header class="qtv-header"> <a href="<?php echo htmlspecialchars($user_role_home); ?>" title="กลับสู่หน้าแรกหลัก" style="text-decoration: none; color: inherit; display: inline-block;"> <div class="qtv-brand" style="cursor: pointer;"> <img src="logo.png" class="navbar-logo" alt="Logo" style="width: 38px; height: 38px; object-fit: contain; border-radius: 50%;"> <div> <span class="qtv-title">คิวทีวี - <?php echo htmlspecialchars($shop_info['name']); ?></span> <div style="font-size: 11px; color: #94a3b8; font-weight: 700; margin-top: 1px;">ระบบหน้าจอคิวทีวีประจำร้านค้า CMTC Tech Solution</div> </div> </div> </a> <!-- Clock and Connection Control --> <div style="display: flex; align-items: center; gap: 20px;"> <button class="btn btn-ghost btn-sm" onclick="testVoice()" style="border-radius: 4px; border-color: rgba(255,255,255,0.15);"> 🔊 ทดสอบเสียงพูด </button> <button class="btn btn-secondary btn-sm" onclick="toggleThemeMode()" style="border-radius: 4px;"> 🌗 สลับโหมด </button> <div id="qtvStatus" style="font-size: 13.5px; color: #10b981; font-weight: 700; display: flex; align-items: center; gap: 6px;"> <span style="width: 8px; height: 8px; background: #10b981; border-radius: 50%; display: inline-block;"></span> เชื่อมต่อเรียลไทม์ </div> <div class="qtv-clock" id="qtvClock">00:00:00</div> </div> </header> <!-- Main Layout Grid --> <div class="qtv-container"> <!-- 1. Left Panel: Pending (กำลังปรุงอาหาร) --> <div class="qtv-panel"> <div class="qtv-panel-title"> <span>🔥 กำลังเตรียมปรุงอาหาร</span> <span class="qtv-badge-counter" id="counterPending">0</span> </div> <div class="qtv-pending-list" id="pendingContainer"> <!-- Simulated Pending items populated via AJAX --> </div> </div> <!-- 2. Right Panel: Ready (เสร็จพร้อมบริการ) --> <div class="qtv-panel" style="border-color: rgba(16,185,129,0.15);"> <div class="qtv-panel-title" style="color: #10b981;"> <span>🛎️ เสร็จสิ้นพร้อมรับอาหาร (Ready)</span> <span class="qtv-badge-counter" style="background: #10b981;" id="counterReady">0</span> </div> <div class="qtv-ready-grid" id="readyContainer"> <!-- Simulated Ready items populated via AJAX --> </div> </div> </div> <!-- Audio Permission Request Modal (Required due to browser policies) --> <div class="qtv-audio-prompt" id="audioPromptOverlay"> <div class="qtv-audio-card"> <span style="font-size: 64px;">🔊</span> <h2 class="glow-text-orange" style="margin-top: 15px; font-weight: 800;">ระบบเสียงเรียกคิวอัตโนมัติ</h2> <p style="color: #94a3b8; font-size: 14px; line-height: 1.6; margin-bottom: 25px;"> เบราว์เซอร์กำหนดให้เปิดใช้งานเสียงผ่านการคลิกเพื่อความปลอดภัย กรุณากดปุ่มด้านล่างเพื่อเปิดระบบเสียงสังเคราะห์ (Text-to-Speech) ภาษาไทย </p> <button class="btn btn-primary" onclick="enableAudioAndStart()" style="width: 100%; padding: 12px 20px; font-weight: 800; border-radius: 6px;"> 🔔 เปิดใช้งานเสียงพูดคิว </button> </div> </div> <!-- Giant Announcement Fullscreen Call Overlay --> <div class="qtv-announce-overlay" id="announceOverlay"> <div class="qtv-announce-card" id="announceCard"> <div class="qtv-announce-icon">🛎️</div> <div class="qtv-announce-title">อาหารเสร็จเรียบร้อยแล้วค่ะ!</div> <div class="qtv-announce-number" id="announceOrderNumber">ออเดอร์ #--</div> <div class="qtv-announce-table" id="announceTableNumber">โต๊ะ --</div> </div> </div> <script> // Global variables let activeOrderIds = []; let audioEnabled = false; let pollingInterval = null; let lastAnnouncedOrderId = null; // Clock updates function updateClock() { const now = new Date(); const hrs = String(now.getHours()).padStart(2, '0'); const mins = String(now.getMinutes()).padStart(2, '0'); const secs = String(now.getSeconds()).padStart(2, '0'); document.getElementById('qtvClock').textContent = `${hrs}:${mins}:${secs}`; } setInterval(updateClock, 1000); updateClock(); // Theme toggle function toggleThemeMode() { document.body.classList.toggle('light-theme'); const isLight = document.body.classList.contains('light-theme'); localStorage.setItem('theme-mode', isLight ? 'light' : 'dark'); } // Enable voice and hide overlay function enableAudioAndStart() { audioEnabled = true; document.getElementById('audioPromptOverlay').style.display = 'none'; // Voice test speakText("ยินดีต้อนรับสู่ระบบเรียกคิวอัจฉริยะค่ะ"); // Start polling startPolling(); } // Thai Text-to-Speech voice engine function speakText(text) { if (!audioEnabled || !window.speechSynthesis) return; // Cancel currently speaking voices to avoid cue backup window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'th-TH'; utterance.rate = 1.0; utterance.pitch = 1.0; // Select standard Thai voice if available const voices = window.speechSynthesis.getVoices(); const thaiVoice = voices.find(v => v.lang.includes('TH') || v.lang.includes('th')); if (thaiVoice) { utterance.voice = thaiVoice; } window.speechSynthesis.speak(utterance); } // Test voice button function testVoice() { if (!audioEnabled) { alert("กรุณาคลิกเปิดใช้เสียงในหน้าต่างเริ่มต้นก่อนครับ"); return; } speakText("ทดสอบเสียงเรียกคิวอัตโนมัติภาษาไทย เสร็จเรียบร้อยดีค่ะ"); } // AJAX Polling from database function fetchQueueData() { if (document.hidden) return; const statusText = document.getElementById('qtvStatus'); fetch('queue-tv.php?ajax=1&shop_id=<?php echo $shop_id; ?>') .then(response => response.json()) .then(data => { if (!data.success) { console.error("AJAX Error:", data.error); return; } statusText.innerHTML = '<span style="width: 8px; height: 8px; background: #10b981; border-radius: 50%; display: inline-block;"></span> เชื่อมต่อเรียลไทม์'; statusText.style.color = '#10b981'; updatePendingList(data.pending); updateReadyGrid(data.ready); }) .catch(err => { console.error("Fetch failure:", err); statusText.innerHTML = '<span style="width: 8px; height: 8px; background: #ef4444; border-radius: 50%; display: inline-block; animation: qtv-pulse-pending 1s infinite alternate;"></span> ขัดข้อง'; statusText.style.color = '#ef4444'; }); } function updatePendingList(pendingOrders) { const container = document.getElementById('pendingContainer'); const counter = document.getElementById('counterPending'); counter.textContent = pendingOrders.length; container.innerHTML = ''; if (pendingOrders.length === 0) { container.innerHTML = '<div style="grid-column:1/-1; padding:25px; text-align:center; color:rgba(255,255,255,0.3); font-size:14px;">ไม่มีอาหารที่กำลังปรุงขณะนี้</div>'; return; } pendingOrders.forEach(order => { const card = document.createElement('div'); card.className = 'qtv-pending-card'; card.innerHTML = ` <div class="qtv-pending-number">#${order.id}</div> <div class="qtv-pending-table">โต๊ะ ${order.table_number}</div> <div style="font-size: 11px; opacity:0.6; margin-top:2px;">${order.time_str}</div> `; container.appendChild(card); }); } function updateReadyGrid(readyOrders) { const container = document.getElementById('readyContainer'); const counter = document.getElementById('counterReady'); counter.textContent = readyOrders.length; container.innerHTML = ''; if (readyOrders.length === 0) { container.innerHTML = '<div style="grid-column:1/-1; padding:40px; text-align:center; color:rgba(255,255,255,0.3); font-size:16px;">ยังไม่มีรายการพร้อมรับ</div>'; return; } // Generate active ID list for voice announcements let isInitialLoad = (activeOrderIds.length === 0); let newReadyFound = null; readyOrders.forEach(order => { const orderId = parseInt(order.id); // Detect if this is a newly ready order if (!isInitialLoad && !activeOrderIds.includes(orderId) && orderId !== lastAnnouncedOrderId) { newReadyFound = order; } const card = document.createElement('div'); card.className = 'qtv-ready-card'; card.innerHTML = ` <div class="qtv-ready-number">#${order.id}</div> <div class="qtv-ready-table">โต๊ะ ${order.table_number}</div> <div style="font-size: 12px; opacity:0.8; margin-top:10px; font-weight:700;">พร้อมรับประทาน • ${order.time_str}</div> `; container.appendChild(card); }); // Sync state activeOrderIds = readyOrders.map(o => parseInt(o.id)); // If new ready order is detected, call the overlay and voice announcement! if (newReadyFound) { triggerQueueAnnouncement(newReadyFound); } } // Trigger Fullscreen Overlay and Text-to-Speech function triggerQueueAnnouncement(order) { lastAnnouncedOrderId = parseInt(order.id); const overlay = document.getElementById('announceOverlay'); const numberText = document.getElementById('announceOrderNumber'); const tableText = document.getElementById('announceTableNumber'); numberText.textContent = `ออเดอร์ #${order.id}`; tableText.textContent = `โต๊ะ ${order.table_number}`; overlay.classList.add('active'); // Announce via TTS const announcementText = `โต๊ะหมายเลข ${order.table_number} อาหารของคุณพร้อมเสิร์ฟแล้วค่ะ`; speakText(announcementText); // Automatically close overlay after 6 seconds setTimeout(() => { overlay.classList.remove('active'); }, 6000); } function startPolling() { fetchQueueData(); pollingInterval = setInterval(fetchQueueData, 2000); } // Load voices beforehand (Chrome issue fix) if (window.speechSynthesis) { window.speechSynthesis.getVoices(); window.speechSynthesis.onvoiceschanged = function() { window.speechSynthesis.getVoices(); }; } </script> <?php endif; ?> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings