594 lines
25 KiB
HTML
594 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>OPD Token Tracker & Queue Display - HMS Mobile</title>
|
|
<link rel="manifest" href="/app/queue/manifest.json">
|
|
<meta name="theme-color" content="#0f172a">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<link rel="icon" href="/assets/img/logo.png" type="image/png">
|
|
<link rel="apple-touch-icon" href="/assets/img/logo.png">
|
|
|
|
<!-- Google Fonts & Bootstrap 5 CSS & Icons -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
|
|
<style>
|
|
body {
|
|
background-color: #0b1329;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
user-select: none;
|
|
overflow: hidden;
|
|
color: #ffffff;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Top TV Header Bar */
|
|
.tv-header {
|
|
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
|
padding: 10px 20px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
z-index: 100;
|
|
}
|
|
|
|
/* Split Columns Grid */
|
|
.tv-main-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
height: calc(100vh - 105px);
|
|
gap: 16px;
|
|
padding: 16px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.tv-main-grid {
|
|
grid-template-columns: 1fr;
|
|
overflow-y: auto;
|
|
height: auto;
|
|
}
|
|
}
|
|
|
|
/* Token List Tracker Column (Left) */
|
|
.tracker-column {
|
|
background: #ffffff;
|
|
border-radius: 16px;
|
|
box-shadow: 0 12px 30px rgba(0,0,0,0.3);
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
}
|
|
|
|
.tracker-header {
|
|
background: #0046ad;
|
|
color: #ffffff;
|
|
padding: 12px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-weight: 800;
|
|
font-size: 20px;
|
|
letter-spacing: 0.5px;
|
|
z-index: 10;
|
|
}
|
|
|
|
.tracker-viewport {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
|
|
.tracker-slider {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100%;
|
|
transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1);
|
|
}
|
|
|
|
.tracker-page-slide {
|
|
flex: 0 0 100%;
|
|
width: 100%;
|
|
padding: 12px;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
/* Individual Token Tracker Item Card */
|
|
.tracker-card {
|
|
border-radius: 12px;
|
|
padding: 12px 18px;
|
|
color: #ffffff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
transition: transform 0.2s ease;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tracker-card:focus, :focus-visible {
|
|
outline: 4px solid #f59e0b !important;
|
|
outline-offset: 2px !important;
|
|
transform: scale(1.02);
|
|
box-shadow: 0 0 20px rgba(245, 158, 11, 0.8) !important;
|
|
}
|
|
|
|
.card-blue { background: linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%); }
|
|
.card-green { background: linear-gradient(90deg, #059669 0%, #047857 100%); }
|
|
.card-cyan { background: linear-gradient(90deg, #0284c7 0%, #0369a1 100%); }
|
|
.card-darkgreen { background: linear-gradient(90deg, #16a34a 0%, #15803d 100%); }
|
|
.card-red { background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%); }
|
|
|
|
.avatar-circle {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background: #ffffff;
|
|
color: #0f172a;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
font-weight: 800;
|
|
flex-shrink: 0;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.token-number-box {
|
|
font-size: 42px;
|
|
font-weight: 900;
|
|
letter-spacing: -1px;
|
|
min-width: 80px;
|
|
text-align: right;
|
|
line-height: 1;
|
|
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.tracker-footer {
|
|
padding: 8px 16px;
|
|
background: #f8fafc;
|
|
border-top: 1px solid #e2e8f0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* Hospital Ads & Doctor Showcase Column (Right) */
|
|
.showcase-column {
|
|
background: #ffffff;
|
|
border-radius: 16px;
|
|
box-shadow: 0 12px 30px rgba(0,0,0,0.3);
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.showcase-header {
|
|
background: #0046ad;
|
|
color: #ffffff;
|
|
padding: 12px 20px;
|
|
font-weight: 800;
|
|
font-size: 20px;
|
|
letter-spacing: 0.5px;
|
|
text-align: center;
|
|
}
|
|
|
|
.showcase-body {
|
|
flex: 1;
|
|
padding: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
background: radial-gradient(circle at center, #ffffff 0%, #f8fafc 100%);
|
|
}
|
|
|
|
.doctor-photo-frame {
|
|
width: 220px;
|
|
height: 250px;
|
|
border-radius: 16px;
|
|
border: 4px solid #ea580c;
|
|
box-shadow: 0 10px 25px rgba(234, 88, 12, 0.25);
|
|
object-fit: cover;
|
|
background: #f1f5f9;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.ad-banner-img {
|
|
max-width: 100%;
|
|
max-height: 260px;
|
|
border-radius: 16px;
|
|
object-fit: cover;
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Bottom Ticker Bar */
|
|
.tv-ticker {
|
|
background: #0f172a;
|
|
color: #f8fafc;
|
|
padding: 8px 16px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.ticker-content {
|
|
display: inline-block;
|
|
animation: marquee 25s linear infinite;
|
|
}
|
|
|
|
@keyframes marquee {
|
|
0% { transform: translateX(100%); }
|
|
100% { transform: translateX(-100%); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Top Header -->
|
|
<header class="tv-header d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<img src="/assets/img/logo.png" alt="Hospital Logo" id="hdr_logo" style="height: 38px; width: auto; filter: brightness(0) invert(1);" onError="this.style.display='none'" />
|
|
<div>
|
|
<h5 class="mb-0 fw-bold text-white" id="hdr_hosp_name" style="font-size: 18px; letter-spacing: -0.3px;">
|
|
E-Atria Multispeciality Hospital
|
|
</h5>
|
|
<small class="text-light opacity-75" style="font-size: 11px;">
|
|
Live OPD Token Tracker & Patient Calling Display
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex align-items-center gap-3">
|
|
<button id="btn_toggle_audio" class="btn btn-sm btn-danger rounded-circle p-2" style="width: 40px; height: 40px;" title="Toggle Voice Announcement">
|
|
<i id="icon_audio" class="bi bi-volume-up-fill fs-5"></i>
|
|
</button>
|
|
|
|
<div class="bg-dark text-white px-3 py-1.5 rounded-3 border border-secondary shadow-sm text-center">
|
|
<div class="fw-bold fs-6" id="tv_clock" style="font-family: monospace; letter-spacing: 1px;">00:00:00</div>
|
|
<div class="small opacity-75" style="font-size: 10px;">LIVE TIME</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Grid -->
|
|
<main class="tv-main-grid">
|
|
<!-- Left: Paginated Token Tracker List -->
|
|
<div class="tracker-column">
|
|
<div class="tracker-header">
|
|
<span><i class="bi bi-person-lines-fill me-2"></i> Token Tracker</span>
|
|
<span id="tracker_page_badge" class="badge bg-white text-primary rounded-pill px-3 py-1 fs-6">Page 1 of 1</span>
|
|
</div>
|
|
|
|
<!-- Horizontal Viewport Slider for Left Token Tracker -->
|
|
<div class="tracker-viewport">
|
|
<div class="tracker-slider" id="tracker_slider">
|
|
<!-- Dynamically Populated Slides (5 items per page) -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Left Bottom Space Ad Banner -->
|
|
<div id="tracker_left_ad_container" class="px-2 py-1" style="display: none; background: #0f172a; color: #ffffff;">
|
|
<div id="tracker_left_ad_content" class="p-2 rounded d-flex align-items-center justify-content-between shadow-sm" style="background: linear-gradient(90deg, #1e293b 0%, #0f172a 100%); border: 1px solid rgba(255,255,255,0.15);">
|
|
<!-- Dynamic Left Bottom Ad Content -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Page Dots Footer for Left Column -->
|
|
<div class="tracker-footer" id="tracker_dots_container">
|
|
<!-- Dynamically Populated Dots -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: Hospital Showcase Carousel -->
|
|
<div class="showcase-column">
|
|
<div class="showcase-header" id="showcase_header">
|
|
<i class="bi bi-award-fill me-2"></i> Our Experts
|
|
</div>
|
|
<div class="showcase-body" id="showcase_body">
|
|
<!-- Dynamically Populated Slide -->
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Bottom Ticker Bar -->
|
|
<div class="tv-ticker">
|
|
<div class="ticker-content" id="ticker_text">
|
|
<i class="bi bi-info-circle-fill text-warning me-2"></i>
|
|
Welcome to E-Atria Multispeciality Hospital. Please observe your token number on the screen. Voice announcements will call your turn. For assistance, contact the help desk. • Emergency Helpline: +91-9876543210 • 24/7 Pharmacy & Ambulance Services Available
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Universal Pure Vanilla JS script (Paginated Left-to-Right Carousel, Samsung TV compatible) -->
|
|
<script>
|
|
var audioEnabled = true;
|
|
var trackersData = [];
|
|
var adsData = [];
|
|
var currentAdIdx = 0;
|
|
var currentPageIdx = 0;
|
|
var itemsPerPage = 5; // 5 cards per page fit perfectly without cutoffs
|
|
var cardColors = ['card-blue', 'card-green', 'card-cyan', 'card-darkgreen', 'card-red'];
|
|
|
|
// Live Clock
|
|
function updateClock() {
|
|
var now = new Date();
|
|
var hours = String(now.getHours()).padStart(2, '0');
|
|
var minutes = String(now.getMinutes()).padStart(2, '0');
|
|
var seconds = String(now.getSeconds()).padStart(2, '0');
|
|
var clockEl = document.getElementById('tv_clock');
|
|
if (clockEl) clockEl.textContent = hours + ':' + minutes + ':' + seconds;
|
|
}
|
|
setInterval(updateClock, 1000);
|
|
updateClock();
|
|
|
|
// Speech Pronunciation Formatter (Expands abbreviations so TTS reads "Doctor", "Department", etc. cleanly)
|
|
function formatSpeechText(text) {
|
|
if (!text) return '';
|
|
var clean = text;
|
|
clean = clean.replace(/\bDr\.\s*/gi, 'Doctor ');
|
|
clean = clean.replace(/\bDr\s+/gi, 'Doctor ');
|
|
clean = clean.replace(/\bDept\.\s*/gi, 'Department ');
|
|
clean = clean.replace(/\bDept\s+/gi, 'Department ');
|
|
clean = clean.replace(/\bNo\.\s*/gi, 'Number ');
|
|
clean = clean.replace(/\bUSG\b/gi, 'Ultrasound');
|
|
return clean;
|
|
}
|
|
|
|
// Audio Announcement
|
|
function speakText(rawText) {
|
|
if (!audioEnabled) return;
|
|
var text = formatSpeechText(rawText);
|
|
try {
|
|
if ('speechSynthesis' in window) {
|
|
window.speechSynthesis.cancel();
|
|
var msg = new SpeechSynthesisUtterance(text);
|
|
msg.rate = 0.9;
|
|
msg.lang = 'en-US';
|
|
window.speechSynthesis.speak(msg);
|
|
}
|
|
} catch (e) {
|
|
console.log('Audio speech not supported');
|
|
}
|
|
}
|
|
|
|
var btnAudio = document.getElementById('btn_toggle_audio');
|
|
var iconAudio = document.getElementById('icon_audio');
|
|
if (btnAudio) {
|
|
btnAudio.onclick = function() {
|
|
audioEnabled = !audioEnabled;
|
|
if (audioEnabled) {
|
|
btnAudio.className = 'btn btn-sm btn-danger rounded-circle p-2';
|
|
if (iconAudio) iconAudio.className = 'bi bi-volume-up-fill fs-5';
|
|
} else {
|
|
btnAudio.className = 'btn btn-sm btn-secondary rounded-circle p-2';
|
|
if (iconAudio) iconAudio.className = 'bi bi-volume-mute-fill fs-5';
|
|
}
|
|
};
|
|
}
|
|
|
|
// Render Paginated Left Token Tracker Slides (Horizontal Carousel)
|
|
function renderTrackers() {
|
|
var sliderEl = document.getElementById('tracker_slider');
|
|
var badgeEl = document.getElementById('tracker_page_badge');
|
|
var dotsEl = document.getElementById('tracker_dots_container');
|
|
if (!sliderEl) return;
|
|
|
|
var totalPages = Math.ceil(trackersData.length / itemsPerPage);
|
|
if (totalPages === 0) totalPages = 1;
|
|
if (currentPageIdx >= totalPages) currentPageIdx = 0;
|
|
|
|
if (badgeEl) {
|
|
badgeEl.textContent = 'Page ' + (currentPageIdx + 1) + ' of ' + totalPages;
|
|
}
|
|
|
|
var html = '';
|
|
for (var p = 0; p < totalPages; p++) {
|
|
html += '<div class="tracker-page-slide">';
|
|
var startIdx = p * itemsPerPage;
|
|
var endIdx = Math.min(startIdx + itemsPerPage, trackersData.length);
|
|
|
|
for (var i = startIdx; i < endIdx; i++) {
|
|
var item = trackersData[i];
|
|
var colorClass = cardColors[i % cardColors.length];
|
|
var iconHtml = item.icon
|
|
? '<i class="bi ' + item.icon + ' text-primary"></i>'
|
|
: (item.name ? item.name.charAt(0) : 'D');
|
|
|
|
html += '<div tabindex="0" class="tracker-card ' + colorClass + '" onclick="speakText(\'Token Number ' + item.token_no + ' for ' + item.name + '\')">';
|
|
html += ' <div class="d-flex align-items-center gap-3">';
|
|
html += ' <div class="avatar-circle">' + iconHtml + '</div>';
|
|
html += ' <div>';
|
|
html += ' <h5 class="mb-0 fw-extrabold" style="font-size:18px; font-weight:800;">' + item.name + '</h5>';
|
|
html += ' <small class="opacity-90" style="font-size:12px; font-weight:500;">' + item.specialization + ' • <span class="badge bg-white bg-opacity-25 text-white">' + item.room + '</span></small>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
html += ' <div class="text-end">';
|
|
html += ' <div class="token-number-box">' + item.token_no + '</div>';
|
|
html += ' <small class="opacity-75" style="font-size:10px;">CURRENT TOKEN</small>';
|
|
html += ' </div>';
|
|
html += '</div>';
|
|
}
|
|
html += '</div>';
|
|
}
|
|
|
|
sliderEl.innerHTML = html;
|
|
sliderEl.style.transform = 'translateX(-' + (currentPageIdx * 100) + '%)';
|
|
|
|
// Render Dots Footer
|
|
if (dotsEl) {
|
|
var dotsHtml = '';
|
|
for (var d = 0; d < totalPages; d++) {
|
|
var activeStyle = (d === currentPageIdx) ? 'bg-primary' : 'bg-secondary opacity-50';
|
|
var widthStyle = (d === currentPageIdx) ? '24px' : '10px';
|
|
dotsHtml += '<button class="btn rounded-circle p-0 ' + activeStyle + '" style="width:' + widthStyle + '; height:10px; border-radius:10px;" onclick="goToTrackerPage(' + d + ')"></button>';
|
|
}
|
|
dotsEl.innerHTML = dotsHtml;
|
|
}
|
|
}
|
|
|
|
function goToTrackerPage(pageIdx) {
|
|
var totalPages = Math.ceil(trackersData.length / itemsPerPage);
|
|
if (totalPages === 0) totalPages = 1;
|
|
currentPageIdx = pageIdx % totalPages;
|
|
|
|
var sliderEl = document.getElementById('tracker_slider');
|
|
var badgeEl = document.getElementById('tracker_page_badge');
|
|
|
|
if (sliderEl) {
|
|
sliderEl.style.transform = 'translateX(-' + (currentPageIdx * 100) + '%)';
|
|
}
|
|
if (badgeEl) {
|
|
badgeEl.textContent = 'Page ' + (currentPageIdx + 1) + ' of ' + totalPages;
|
|
}
|
|
renderTrackers();
|
|
}
|
|
|
|
// Render Showcase Ad Slide
|
|
function renderShowcase() {
|
|
var headerEl = document.getElementById('showcase_header');
|
|
var bodyEl = document.getElementById('showcase_body');
|
|
if (!bodyEl || adsData.length === 0) return;
|
|
|
|
var ad = adsData[currentAdIdx];
|
|
if (headerEl) headerEl.innerHTML = '<i class="bi bi-award-fill me-2"></i> ' + (ad.title || 'Our Experts');
|
|
|
|
var html = '';
|
|
if (ad.type === 'doctor_profile') {
|
|
html += '<div class="d-flex flex-column align-items-center">';
|
|
html += ' <img src="' + ad.image_url + '" alt="Doctor" class="doctor-photo-frame" onerror="this.src=\'/assets/img/profile-img.jpg\'" />';
|
|
html += ' <h3 class="fw-extrabold text-dark mb-1" style="font-weight:800;">' + ad.doctor_name + '</h3>';
|
|
html += ' <h5 class="text-primary fw-bold mb-2" style="font-size:18px;">' + ad.specialization + '</h5>';
|
|
html += ' <h6 class="text-secondary fw-semibold mb-3" style="font-size:15px;">' + ad.designation + '</h6>';
|
|
html += ' <p class="text-muted small" style="font-size:13px; max-width:420px;">' + ad.description + '</p>';
|
|
html += '</div>';
|
|
} else {
|
|
html += '<div class="d-flex flex-column align-items-center">';
|
|
html += ' <img src="' + ad.image_url + '" alt="Ad Banner" class="ad-banner-img" onerror="this.src=\'/assets/img/slides-1.jpg\'" />';
|
|
html += ' <h3 class="fw-extrabold text-dark mb-1" style="font-weight:800;">' + ad.title + '</h3>';
|
|
html += ' <h5 class="text-success fw-bold mb-2" style="font-size:17px;">' + (ad.tagline || '') + '</h5>';
|
|
html += ' <p class="text-muted small" style="font-size:13px; max-width:420px;">' + ad.description + '</p>';
|
|
html += '</div>';
|
|
}
|
|
|
|
// Dot Indicators for Showcase
|
|
html += '<div class="d-flex gap-2 mt-4">';
|
|
for (var k = 0; k < adsData.length; k++) {
|
|
var activeStyle = (k === currentAdIdx) ? 'bg-primary' : 'bg-secondary opacity-50';
|
|
var widthStyle = (k === currentAdIdx) ? '24px' : '10px';
|
|
html += '<button class="btn rounded-circle p-0 ' + activeStyle + '" style="width:' + widthStyle + '; height:10px; border-radius:10px;" onclick="currentAdIdx=' + k + '; renderShowcase();"></button>';
|
|
}
|
|
html += '</div>';
|
|
|
|
bodyEl.innerHTML = html;
|
|
}
|
|
|
|
// Fetch Data from API
|
|
function loadQueueData() {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', '/api/v1/opd/queue/live', true);
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
try {
|
|
var json = JSON.parse(xhr.responseText);
|
|
if (json.status === 1) {
|
|
if (json.trackers) trackersData = json.trackers;
|
|
if (json.ads) adsData = json.ads;
|
|
if (json.hospital_name) {
|
|
var hospEl = document.getElementById('hdr_hosp_name');
|
|
if (hospEl) hospEl.textContent = json.hospital_name;
|
|
}
|
|
if (json.footer_ticker) {
|
|
var tickerEl = document.getElementById('ticker_text');
|
|
if (tickerEl) {
|
|
tickerEl.innerHTML = '<i class="bi bi-info-circle-fill text-warning me-2"></i> ' + json.footer_ticker;
|
|
}
|
|
}
|
|
if (json.left_ad) {
|
|
var leftAdContainer = document.getElementById('tracker_left_ad_container');
|
|
var leftAdContent = document.getElementById('tracker_left_ad_content');
|
|
if (leftAdContainer && leftAdContent && json.left_ad.enabled) {
|
|
leftAdContainer.style.display = 'block';
|
|
if (json.left_ad.image_url) {
|
|
leftAdContent.innerHTML = '<img src="' + json.left_ad.image_url + '" style="max-height: 48px; width: 100%; object-fit: contain; border-radius: 6px;" />';
|
|
} else {
|
|
leftAdContent.innerHTML = '<div class="d-flex align-items-center gap-2 text-warning fw-bold small"><i class="bi bi-megaphone-fill fs-5"></i> <span>' + (json.left_ad.text || '24x7 Emergency Services Available') + '</span></div>';
|
|
}
|
|
} else if (leftAdContainer) {
|
|
leftAdContainer.style.display = 'none';
|
|
}
|
|
}
|
|
renderTrackers();
|
|
renderShowcase();
|
|
}
|
|
} catch (e) {
|
|
console.log('JSON parse error');
|
|
}
|
|
} else if (xhr.status === 403) {
|
|
// Access forbidden: wipe sensitive data and reload to show 403 screen
|
|
trackersData = [];
|
|
adsData = [];
|
|
window.location.reload();
|
|
}
|
|
}
|
|
};
|
|
xhr.send();
|
|
}
|
|
|
|
// Initial Load & Timers
|
|
loadQueueData();
|
|
setInterval(loadQueueData, 5000);
|
|
|
|
// Auto-advance Left Token Tracker Page every 7 seconds
|
|
setInterval(function() {
|
|
var totalPages = Math.ceil(trackersData.length / itemsPerPage);
|
|
if (totalPages > 1) {
|
|
currentPageIdx = (currentPageIdx + 1) % totalPages;
|
|
goToTrackerPage(currentPageIdx);
|
|
}
|
|
}, 7000);
|
|
|
|
// Auto-advance Right Showcase Carousel every 8 seconds
|
|
setInterval(function() {
|
|
if (adsData.length > 0) {
|
|
currentAdIdx = (currentAdIdx + 1) % adsData.length;
|
|
renderShowcase();
|
|
}
|
|
}, 8000);
|
|
|
|
// Register PWA Service Worker immediately
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.register('/app/queue/sw.js', { scope: '/app/queue/' })
|
|
.then(function(reg) {
|
|
console.log('Queue PWA Service Worker Registered', reg);
|
|
})
|
|
.catch(function(err) {
|
|
console.log('Service Worker Registration Failed', err);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|