286 lines
16 KiB
PHP
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>
|