feat(pwa): add 5-item page-wise horizontal auto-carousel for left token tracker column

This commit is contained in:
DevSoftTech Server 2026-08-30 02:23:03 +05:30
parent 49267e65ec
commit ac5d2710fa
2 changed files with 166 additions and 65 deletions

View File

@ -823,41 +823,47 @@ class DoctorApi extends BaseController
$allTrackers = array_merge($docTokens, $deptTokens);
// 3. Hospital Advertisements & Doctor Showcase Slides
$ads = [
[
'id' => 1,
$ads = [];
$adId = 1;
// Dynamic Doctor Profile Slides for all doctors on duty
foreach ($docTokens as $dt) {
$ads[] = [
'id' => $adId++,
'type' => 'doctor_profile',
'title' => 'Our Medical Experts',
'doctor_name' => $docTokens[0]['name'] ?? 'Dr. Prakash Chandwani',
'specialization' => $docTokens[0]['specialization'] ?? 'Cardiologist & Physician',
'designation' => 'Chief Managing Director & Senior Consultant',
'doctor_name' => $dt['name'],
'specialization' => $dt['specialization'],
'designation' => $dt['designation'] ?? 'Consultant Doctor',
'image_url' => base_url('assets/img/profile-img.jpg'),
'description' => 'Available for OPD Consultations (10:00 AM - 4:00 PM). Specializing in Advanced Heart Care & Critical Management.'
],
[
'id' => 2,
'type' => 'hospital_ad',
'title' => 'In-House 24/7 Pharmacy',
'tagline' => '100% Genuine Medicines & Fastest Counter Dispense',
'image_url' => base_url('assets/img/slides-1.jpg'),
'description' => 'Discounts on prescription billing. Doorstep delivery available for senior citizens and admitted patients.'
],
[
'id' => 3,
'type' => 'hospital_ad',
'title' => 'Advanced Radiology & Imaging Center',
'tagline' => 'Digital X-Ray, 32-Slice CT Scan & 4D Ultrasound',
'image_url' => base_url('assets/img/slides-2.jpg'),
'description' => 'Accurate high-precision diagnostic imaging with immediate digital report generation.'
],
[
'id' => 4,
'type' => 'hospital_ad',
'title' => '24x7 Emergency & Critical Care ICU',
'tagline' => 'Fully Equipped Ventilators, Cardiac Monitors & Trauma Team',
'image_url' => base_url('assets/img/slides-3.jpg'),
'description' => 'Round-the-clock emergency medical services with dedicated ICU & Operation Theater.'
]
'description' => 'Available for OPD Consultations (' . $dt['room'] . '). Specializing in ' . $dt['specialization'] . '.'
];
}
// Hospital Service Advertisements
$ads[] = [
'id' => $adId++,
'type' => 'hospital_ad',
'title' => 'In-House 24/7 Pharmacy',
'tagline' => '100% Genuine Medicines & Fastest Counter Dispense',
'image_url' => base_url('assets/img/slides-1.jpg'),
'description' => 'Discounts on prescription billing. Doorstep delivery available for senior citizens and admitted patients.'
];
$ads[] = [
'id' => $adId++,
'type' => 'hospital_ad',
'title' => 'Advanced Radiology & Imaging Center',
'tagline' => 'Digital X-Ray, 32-Slice CT Scan & 4D Ultrasound',
'image_url' => base_url('assets/img/slides-2.jpg'),
'description' => 'Accurate high-precision diagnostic imaging with immediate digital report generation.'
];
$ads[] = [
'id' => $adId++,
'type' => 'hospital_ad',
'title' => '24x7 Emergency & Critical Care ICU',
'tagline' => 'Fully Equipped Ventilators, Cardiac Monitors & Trauma Team',
'image_url' => base_url('assets/img/slides-3.jpg'),
'description' => 'Round-the-clock emergency medical services with dedicated ICU & Operation Theater.'
];
return $this->response->setJSON([

View File

@ -67,6 +67,7 @@
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
}
.tracker-header {
@ -79,15 +80,32 @@
font-weight: 800;
font-size: 20px;
letter-spacing: 0.5px;
z-index: 10;
}
.tracker-list {
.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;
flex: 1;
overflow-y: auto;
justify-content: flex-start;
}
/* Individual Token Tracker Item Card */
@ -142,6 +160,16 @@
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;
@ -247,14 +275,23 @@
<!-- Main Grid -->
<main class="tv-main-grid">
<!-- Left: Token Tracker List -->
<!-- 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 class="badge bg-white text-primary rounded-pill px-3 py-1 fs-6">Live Queue</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>
<div class="tracker-list" id="tracker_list_container">
<!-- Dynamically Populated -->
<!-- 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>
<!-- Page Dots Footer for Left Column -->
<div class="tracker-footer" id="tracker_dots_container">
<!-- Dynamically Populated Dots -->
</div>
</div>
@ -277,12 +314,14 @@
</div>
</div>
<!-- Universal Pure Vanilla JS script (Works 100% on Samsung TV Browser, Smart TVs & PCs) -->
<!-- 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
@ -328,35 +367,81 @@
};
}
// Render Token Cards
// Render Paginated Left Token Tracker Slides (Horizontal Carousel)
function renderTrackers() {
var container = document.getElementById('tracker_list_container');
if (!container) return;
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 = 0; i < trackersData.length; 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');
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:19px; font-weight:800;">' + item.name + '</h5>';
html += ' <small class="opacity-90" style="font-size:12px; font-weight:500;">' + item.specialization + ' &bull; <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 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 + ' &bull; <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>';
}
container.innerHTML = html;
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
@ -386,7 +471,7 @@
html += '</div>';
}
// Dot Indicators
// 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';
@ -398,7 +483,7 @@
bodyEl.innerHTML = html;
}
// Fetch Data from API (XMLHttpRequest for 100% Samsung TV Browser Compatibility)
// Fetch Data from API
function loadQueueData() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/v1/opd/queue/live', true);
@ -428,6 +513,16 @@
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;
@ -435,7 +530,7 @@
}
}, 8000);
// Register PWA Service Worker immediately for fast automated PWA auditing
// Register PWA Service Worker immediately
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/app/queue/sw.js', { scope: '/app/queue/' })
.then(function(reg) {