Files
2026-09-16 23:20:08 +07:00

286 lines
16 KiB
PHP

<?php
/**
* Standalone Smart TV Queue Display View (Fullscreen Glassmorphism)
* For 4K / HD TV monitors in hospital & spa waiting lobby
*/
$b = defined('BASE_URL') ? BASE_URL : '';
?>
<!DOCTYPE html>
<html lang="th" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>หน้าจอเรียกคิว Smart TV - TTMQMS Enterprise</title>
<!-- Offline-Ready Local Assets (No Internet Required) -->
<script src="<?= $b ?>/assets/js/tailwindcss.min.js"></script>
<link rel="stylesheet" href="<?= $b ?>/assets/css/bootstrap-icons.min.css">
<link rel="stylesheet" href="<?= $b ?>/assets/css/index.css">
<script defer src="<?= $b ?>/assets/js/alpine.min.js"></script>
<script>window.BASE_URL = "<?= $b ?>";</script>
<script src="<?= $b ?>/assets/js/app.js"></script>
</head>
<body x-data="tvApp" class="theme-dark min-h-screen bg-slate-950 text-white overflow-hidden flex flex-col justify-between p-6 md:p-10 select-none relative">
<!-- Ambient Glowing Background Circles -->
<div class="absolute -top-60 -left-60 w-[600px] h-[600px] bg-emerald-500/15 rounded-full blur-3xl pointer-events-none animate-float"></div>
<div class="absolute -bottom-60 -right-60 w-[600px] h-[600px] bg-cyan-500/15 rounded-full blur-3xl pointer-events-none animate-float" style="animation-delay: 3s;"></div>
<!-- Header & Realtime Digital Clock -->
<header class="flex items-center justify-between pb-6 border-b border-emerald-500/20 z-10">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-3xl bg-gradient-to-tr from-emerald-500 to-cyan-500 flex items-center justify-center text-white text-3xl shadow-xl shadow-emerald-500/30 animate-pulse-glow">
<i class="bi bi-flower1"></i>
</div>
<div>
<h1 class="text-3xl lg:text-4xl font-extrabold tracking-tight bg-gradient-to-r from-emerald-400 via-teal-300 to-cyan-400 bg-clip-text text-transparent">
TTMQMS Enterprise
</h1>
<p class="text-base text-slate-400 font-medium">ศูนย์เวชกรรมแผนไทยและสปาเพื่อสุขภาพ (สาขาทองหล่อ)</p>
</div>
</div>
<!-- Digital Clock & Fullscreen Toggle -->
<div class="flex items-center gap-6">
<div class="text-right">
<div class="text-4xl font-extrabold font-mono tracking-wider text-amber-400" x-text="currentTime">10:45:20</div>
<div class="text-sm text-slate-400 font-medium" x-text="currentDate">วันจันทร์ที่ 27 กรกฎาคม 2569</div>
</div>
<button @click="toggleFullscreen()" class="p-4 rounded-2xl bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 hover:scale-105 transition-all text-2xl" title="เปิดเต็มจอ Fullscreen">
<i class="bi" :class="isFullscreen ? 'bi-fullscreen-exit' : 'bi-fullscreen'"></i>
</button>
</div>
</header>
<!-- Main Content Area: Split 2 Columns -->
<main class="flex-1 grid grid-cols-1 lg:grid-cols-12 gap-8 my-6 z-10">
<!-- Left Column (8 cols): Currently Calling Queue (Huge Display) -->
<div class="lg:col-span-7 glass-card p-8 lg:p-12 rounded-3xl border border-emerald-500/30 flex flex-col justify-between relative overflow-hidden shadow-2xl bg-gradient-to-br from-slate-900/90 to-emerald-950/40">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="w-4 h-4 rounded-full bg-amber-400 animate-ping"></span>
<span class="text-xl font-bold uppercase tracking-widest text-amber-400">⚡ กำลังเรียกคิว (Currently Calling)</span>
</div>
<span class="px-4 py-1.5 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-sm font-bold animate-pulse">
🔊 ระบบเสียงอัตโนมัติ (TTS Thai / English)
</span>
</div>
<!-- Main Display Number -->
<div class="text-center my-auto py-8">
<div class="text-[120px] lg:text-[180px] font-black font-mono leading-none tracking-wider bg-gradient-to-b from-amber-300 via-amber-400 to-orange-500 bg-clip-text text-transparent animate-pulse-glow drop-shadow-[0_20px_20px_rgba(245,158,11,0.3)]"
x-text="callingQueue ? callingQueue.queue_no : '---'">
A003
</div>
<!-- Assigned Room Number -->
<div class="mt-6 inline-block px-10 py-4 rounded-3xl bg-gradient-to-r from-emerald-600 to-teal-600 text-white text-3xl lg:text-5xl font-extrabold shadow-2xl shadow-emerald-500/40 border border-emerald-400/50">
ห้อง <span x-text="callingQueue ? (callingQueue.room_no || 'R-101') : 'R-101'">R-101</span>
</div>
</div>
<!-- Patient & Therapist Info -->
<div class="pt-6 border-t border-slate-700/60 flex items-center justify-between text-lg lg:text-xl text-slate-300">
<div>
<span class="text-slate-400">ผู้รับบริการ:</span>
<strong class="text-white ml-2" x-text="callingQueue ? (callingQueue.patient_name || 'ผู้รับบริการทั่วไป') : 'รอเรียกคิวถัดไป'">คุณสมชาย รักสุขภาพ</strong>
</div>
<div>
<span class="text-slate-400">หมอนวดประจำห้อง:</span>
<strong class="text-emerald-400 ml-2" x-text="callingQueue ? (callingQueue.therapist_name || '-') : '-'">หมอสมศรี (T-102)</strong>
</div>
</div>
</div>
<!-- Right Column (5 cols): Next in Line & In Progress List -->
<div class="lg:col-span-5 flex flex-col gap-6">
<!-- Box 1: Next in Line (เตรียมตัวเข้าห้อง) -->
<div class="glass-card p-6 rounded-3xl border border-cyan-500/30 flex-1 flex flex-col bg-slate-900/80">
<div class="flex items-center justify-between pb-4 mb-4 border-b border-cyan-500/20">
<div class="flex items-center gap-2.5">
<i class="bi bi-person-walking text-2xl text-cyan-400"></i>
<h3 class="text-xl font-bold text-cyan-300">คิวถัดไป (Next in Line)</h3>
</div>
<span class="text-xs text-slate-400">เตรียมรับบริการ</span>
</div>
<div class="space-y-3 overflow-y-auto max-h-[220px] pr-2">
<template x-for="(q, idx) in nextQueues" :key="q.id">
<div class="flex items-center justify-between p-4 rounded-2xl bg-cyan-950/30 border border-cyan-500/20 hover:border-cyan-400 transition-all">
<div class="flex items-center gap-3">
<span class="w-8 h-8 rounded-full bg-cyan-500/20 text-cyan-400 flex items-center justify-center font-bold font-mono text-sm" x-text="idx + 1">1</span>
<span class="text-2xl font-bold font-mono text-cyan-300" x-text="q.queue_no">A004</span>
</div>
<div class="text-right">
<span class="text-sm font-semibold block truncate max-w-[150px]" x-text="q.patient_name || 'ผู้รับบริการ'"></span>
<span class="text-xs text-slate-400">เวลารอประมาณ <strong class="text-emerald-400 font-mono" x-text="q.est_wait || '10 นาที'">10 นาที</strong></span>
</div>
</div>
</template>
<div x-show="nextQueues.length === 0" class="text-center py-8 text-slate-500">ไม่มีคิวที่กำลังรอ</div>
</div>
</div>
<!-- Box 2: In Progress (กำลังให้บริการ) -->
<div class="glass-card p-6 rounded-3xl border border-emerald-500/30 flex-1 flex flex-col bg-slate-900/80">
<div class="flex items-center justify-between pb-4 mb-4 border-b border-emerald-500/20">
<div class="flex items-center gap-2.5">
<i class="bi bi-heart-pulse-fill text-2xl text-emerald-400 animate-pulse"></i>
<h3 class="text-xl font-bold text-emerald-400">กำลังให้บริการ (In Progress)</h3>
</div>
<span class="px-2.5 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 font-mono text-xs font-bold" x-text="inProgressQueues.length">0</span>
</div>
<div class="grid grid-cols-2 gap-3 overflow-y-auto max-h-[220px] pr-1">
<template x-for="q in inProgressQueues" :key="q.id">
<div class="p-3 rounded-2xl bg-emerald-950/20 border border-emerald-500/20 flex items-center justify-between">
<div>
<span class="text-xl font-bold font-mono text-emerald-300" x-text="q.queue_no">A002</span>
<span class="text-[11px] text-slate-400 block truncate" x-text="q.therapist_name || 'T-101'"></span>
</div>
<span class="px-2 py-1 rounded-lg bg-emerald-500/20 text-emerald-300 font-mono text-xs font-bold" x-text="`ห้อง ${q.room_no || '101'}`">ห้อง 101</span>
</div>
</template>
</div>
</div>
</div>
</main>
<!-- Footer Banner: Marquee Text & QR Code App Download -->
<footer class="glass-card p-4 rounded-2xl border border-emerald-500/20 flex items-center justify-between gap-6 z-10 bg-slate-900/90">
<div class="flex items-center gap-3 text-amber-400 font-bold text-base flex-shrink-0">
<i class="bi bi-megaphone-fill text-xl animate-bounce"></i>
<span>ประกาศประชาสัมพันธ์:</span>
</div>
<!-- Scrolling Marquee Text -->
<div class="overflow-hidden whitespace-nowrap flex-1 text-slate-300 text-sm md:text-base font-medium">
<div class="inline-block animate-[marquee_25s_linear_infinite]">
✨ ขอความกรุณาผู้รับบริการทุกท่าน เตรียมบัตรคิวและรอรับการเรียกหมายเลขผ่านหน้าจอเมื่อถึงเวลาที่กำหนด • 🌿 ท่านสามารถตรวจสอบสถานะคิวแบบ Realtime ผ่านสมาร์ทโฟนของท่านได้โดยการสแกน QR Code ด้านขวา • 💆‍♂️ เพื่อประสิทธิภาพในการรักษา กรุณาแจ้งประวัติโรคประจำตัวหรือข้อห้ามในการนวดให้หมอนวดทราบก่อนเริ่มรับบริการทุกครั้ง ขอขอบพระคุณค่ะ ✨
</div>
</div>
<div class="flex items-center gap-3 border-l border-slate-700 pl-6 flex-shrink-0">
<div class="w-10 h-10 bg-white p-1 rounded-lg flex items-center justify-center text-slate-900 font-bold text-xs">
<i class="bi bi-qr-code-scan text-2xl"></i>
</div>
<div class="text-xs">
<p class="font-bold text-emerald-400">เช็กคิวมือถือ</p>
<p class="text-slate-400">ttmqms.hospital.go.th</p>
</div>
</div>
</footer>
<style>
@keyframes marquee {
0% { transform: translateX(100%); }
100% { transform: translateX(-100%); }
}
</style>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('tvApp', () => ({
currentTime: '10:00:00',
currentDate: '',
isFullscreen: false,
callingQueue: { id: 1, queue_no: 'A003', room_no: 'R-101 (VIP)', patient_name: 'คุณสมชาย รักสุขภาพ', therapist_name: 'หมอสมศรี (T-102)' },
nextQueues: [
{ id: 2, queue_no: 'A004', patient_name: 'คุณวิภาวี ดีเลิศ', est_wait: '5 นาที' },
{ id: 3, queue_no: 'A005', patient_name: 'คุณกิตติศักดิ์ พึ่งบุญ', est_wait: '15 นาที' },
{ id: 4, queue_no: 'V001', patient_name: 'คุณโสภิตา เสน่ห์จันทร์', est_wait: '20 นาที' }
],
inProgressQueues: [
{ id: 5, queue_no: 'A001', room_no: 'R-103', therapist_name: 'หมอใจดี (T-105)' },
{ id: 6, queue_no: 'A002', room_no: 'R-106', therapist_name: 'หมอสุชาดา (T-101)' }
],
lastCalledQueueNo: 'A003',
init() {
this.updateClock();
setInterval(() => this.updateClock(), 1000);
this.fetchTvQueues();
setInterval(() => this.fetchTvQueues(), 10000);
},
updateClock() {
const now = new Date();
this.currentTime = now.toLocaleTimeString('th-TH');
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
this.currentDate = now.toLocaleDateString('th-TH', options);
},
toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch((err) => {
console.warn('Fullscreen error:', err);
});
this.isFullscreen = true;
} else {
document.exitFullscreen();
this.isFullscreen = false;
}
},
async fetchTvQueues() {
try {
const res = await this.apiFetch('/queues?branch_id=1', { silent: true });
if (res && res.success) {
const all = res.data.queues || [];
const inProg = all.filter(q => q.status === 'In_Progress');
const waiting = all.filter(q => q.status === 'Waiting' || q.status === 'Assigned');
if (inProg.length > 0) {
const latest = inProg[0];
this.callingQueue = latest;
// ถ้าคิวที่เรียกเปลี่ยนไป ให้เล่นเสียงและ TTS
if (latest.queue_no !== this.lastCalledQueueNo) {
this.lastCalledQueueNo = latest.queue_no;
this.triggerCallNotification(latest);
}
}
this.inProgressQueues = inProg;
this.nextQueues = waiting.slice(0, 5);
}
} catch (e) {
// Keep using realistic demo data
}
},
triggerCallNotification(q) {
// 1. Play 4-Tone Web Audio Chime
if (this.playCallSound) this.playCallSound();
// 2. Speak via HTML5 SpeechSynthesis API (Text-To-Speech)
if ('speechSynthesis' in window) {
setTimeout(() => {
const thText = `ขอเชิญหมายเลขคิว ${q.queue_no.split('').join(' ')} ที่ห้อง ${q.room_no || 'หนึ่งศูนย์หนึ่ง'} ค่ะ`;
const enText = `Queue number ${q.queue_no.split('').join(' ')}, please proceed to Room ${q.room_no || '101'}`;
const utteranceTh = new SpeechSynthesisUtterance(thText);
utteranceTh.lang = 'th-TH';
utteranceTh.rate = 0.9;
const utteranceEn = new SpeechSynthesisUtterance(enText);
utteranceEn.lang = 'en-US';
utteranceEn.rate = 0.95;
window.speechSynthesis.speak(utteranceTh);
setTimeout(() => { window.speechSynthesis.speak(utteranceEn); }, 4000);
}, 1000);
}
}
}));
});
</script>
</body>
</html>