243 lines
13 KiB
PHP
243 lines
13 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/queue_bootstrap.php';
|
|
qh_boot();
|
|
|
|
$activeCalls = qh_fetch_tickets(['called', 'in_progress'], null, 8);
|
|
$queueOverview = qh_queue_overview();
|
|
|
|
$activeVideos = [];
|
|
try {
|
|
$stmt = db()->query("SELECT video_path FROM hospital_ads WHERE is_active = 1 ORDER BY sort_order ASC, id DESC ");
|
|
if ($stmt) {
|
|
$activeVideos = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
}
|
|
} catch (Throwable $e) {
|
|
// Table might not exist yet, safe to ignore
|
|
}
|
|
|
|
$activeNews = [];
|
|
try {
|
|
$stmt = db()->query("SELECT phrase FROM hospital_news WHERE is_active = 1 ORDER BY sort_order ASC, id DESC");
|
|
if ($stmt) {
|
|
$activeNews = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
}
|
|
} catch (Throwable $e) {
|
|
// Table might not exist yet
|
|
}
|
|
|
|
qh_page_start(
|
|
'display',
|
|
qh_t('General display board', 'لوحة العرض العامة'),
|
|
qh_t('Public queue display.', 'شاشة طوابير عامة.')
|
|
);
|
|
?>
|
|
<style>
|
|
/* Remove top spacing from the shell wrapper */
|
|
main.app-shell {
|
|
padding-top: 0 !important;
|
|
padding-bottom: 0 !important;
|
|
}
|
|
</style>
|
|
<div class="container-fluid px-0 px-lg-0 py-0 m-0" data-auto-refresh="20" style="min-height: 100vh; display: flex; flex-direction: column; ">
|
|
<!-- Top Header for Display Board -->
|
|
<header class="d-flex justify-content-between align-items-center bg-white py-2 px-3 shadow-sm border-0">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<?php if ($logoUrl = qh_hospital_logo_url()): ?>
|
|
<img src="<?= qh_h($logoUrl) ?>" alt="<?= qh_h(qh_hospital_name()) ?>" style="height: 50px;">
|
|
<?php else: ?>
|
|
<div class="bg-primary text-white rounded d-flex align-items-center justify-content-center fs-4 fw-bold" style="width: 50px; height: 50px;">
|
|
<?= qh_h(qh_hospital_brand_initials()) ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div>
|
|
<h1 class="h3 mb-0 fw-bold text-primary"><?= qh_h(qh_hospital_name()) ?></h1>
|
|
<?php if ($tagline = qh_hospital_tagline()): ?>
|
|
<div class="text-muted fw-semibold"><?= qh_h($tagline) ?></div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="fs-5 fw-bold text-dark js-live-clock"><?= qh_h(date('H:i')) ?></div>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm me-2 d-flex align-items-center gap-1 js-audio-toggle" id="globalAudioToggle" title="<?= qh_h(qh_t('Toggle Audio', 'تبديل الصوت')) ?>">
|
|
<i class="bi bi-megaphone"></i> <span class="d-none d-sm-inline"><?= qh_h(qh_t('Sound', 'الصوت')) ?></span>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm shadow-sm d-flex align-items-center gap-1 js-fullscreen-toggle" aria-pressed="false" data-label-enter="<?= qh_h(qh_t('Fullscreen', 'ملء الشاشة')) ?>" data-label-exit="<?= qh_h(qh_t('Exit fullscreen', 'إنهاء ملء الشاشة')) ?>"><i class="bi bi-arrows-fullscreen"></i> <span class="d-none d-sm-inline"><?= qh_h(qh_t('Fullscreen', 'ملء الشاشة')) ?></span></button>
|
|
</div>
|
|
</header>
|
|
|
|
<?php $pb = !empty($activeNews) ? "padding-bottom: 4rem !important;" : "padding-bottom: 1.5rem !important;"; ?>
|
|
<div class="row g-4 m-0 mt-2 px-3 px-lg-4 pt-0 flex-grow-1" style="<?= $pb ?>">
|
|
<div class="col-xl-8 col-lg-7 d-flex flex-column gap-4" id="queueSections">
|
|
<div class="card shadow-sm border-0 flex-grow-1">
|
|
<div class="card-header bg-white border-bottom py-2 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h2 class="h4 mb-0 text-gray-800 fw-bold"><?= qh_h(qh_t('Now Serving', 'يتم الآن النداء')) ?></h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body p-4 bg-light">
|
|
<?php if ($activeCalls): ?>
|
|
<div class="row g-3 row-cols-2 row-cols-lg-5">
|
|
<?php foreach ($activeCalls as $ticket): $speech = qh_call_message($ticket); ?>
|
|
<div class="col">
|
|
<div class="card border-0 shadow-sm h-100 announcement-card" data-announcement-key="<?= qh_h((string) $ticket['id']) ?>-<?= qh_h((string) strtotime((string) $ticket['called_at'])) ?>" data-announcement-en="<?= qh_h($speech['speech_en'] ?? $speech['en']) ?>" data-announcement-ar="<?= qh_h($speech['speech_ar'] ?? $speech['ar']) ?>">
|
|
<div class="card-body w-100 text-center p-2 d-flex flex-column align-items-center justify-content-center">
|
|
<div class="fs-2 fw-bold text-primary mb-1"><?= qh_h($ticket['ticket_number']) ?></div>
|
|
<div class="small text-muted mb-1 text-truncate w-100" title="<?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>"><?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?></div>
|
|
<div class="bg-primary text-white rounded px-2 py-1 small fw-bold mt-1 mb-2">
|
|
<?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?>
|
|
</div>
|
|
<div class="text-muted mt-auto" style="font-size: 0.65rem;">
|
|
<?= qh_format_datetime($ticket['called_at'] ?? $ticket['updated_at']) ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="d-flex align-items-center justify-content-center h-100 min-vh-50 text-muted">
|
|
<div class="text-center">
|
|
<i class="bi bi-display display-1 mb-3 opacity-50"></i>
|
|
<h2><?= qh_h(qh_t('No active calls right now.', 'لا توجد نداءات نشطة حالياً.')) ?></h2>
|
|
<p class="lead opacity-75"><?= qh_h(qh_t('Please wait for your ticket number.', 'يرجى الانتظار حتى يتم نداء رقم تذكرتك.')) ?></p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm border-0">
|
|
<div class="card-header bg-white border-bottom py-2">
|
|
<h5 class="mb-0 font-weight-bold text-dark"><?= qh_h(qh_t('Queue by Clinic', 'الإنتظار حسب العيادة')) ?></h5>
|
|
</div>
|
|
<div class="card-body p-4 bg-light">
|
|
<div class="row g-3 row-cols-2 row-cols-lg-5">
|
|
<?php foreach ($queueOverview as $row): ?>
|
|
<div class="col">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-2 d-flex flex-column justify-content-center">
|
|
<h6 class="fw-bold text-primary text-center mb-2 text-truncate" title="<?= qh_h(qh_name($row)) ?>"><?= qh_h(qh_name($row)) ?></h6>
|
|
<?php if ((int) ($row['requires_vitals'] ?? 0) === 1): ?>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span class="text-muted fw-semibold lh-sm" style="font-size: 0.7rem;"><?= qh_h(qh_t('Vitals Wait', 'غرفة المعاينة الأولية')) ?></span>
|
|
<span class="badge bg-warning text-dark rounded-pill px-2"><?= qh_h((string) $row['vitals_waiting']) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span class="text-muted fw-semibold lh-sm" style="font-size: 0.7rem;"><?= qh_h(qh_t('Doctor Wait', 'انتظار الطبيب')) ?></span>
|
|
<span class="badge bg-info text-dark rounded-pill px-2"><?= qh_h((string) $row['doctor_waiting']) ?></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4 col-lg-5">
|
|
<div class="sticky-top" style="top: 1rem; height: 60vh;">
|
|
<?php if (!empty($activeVideos)): ?>
|
|
<div class="card shadow-sm border-0 h-100 bg-black overflow-hidden d-flex justify-content-center align-items-center">
|
|
<video id="adsVideoPlayer" class="w-100 h-100 object-fit-contain" src="<?= qh_h($activeVideos[0]) ?>" autoplay muted playsinline>
|
|
|
|
</video>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const videos = <?= json_encode($activeVideos, JSON_UNESCAPED_SLASHES) ?>;
|
|
const player = document.getElementById('adsVideoPlayer');
|
|
|
|
if (videos.length === 0) return;
|
|
|
|
let currentIdx = parseInt(sessionStorage.getItem('adsVideoIndex') || '0', 10);
|
|
let currentTime = parseFloat(sessionStorage.getItem('adsVideoTime') || '0');
|
|
|
|
if (isNaN(currentIdx) || currentIdx < 0 || currentIdx >= videos.length) {
|
|
currentIdx = 0;
|
|
currentTime = 0;
|
|
}
|
|
|
|
player.src = videos[currentIdx];
|
|
player.load();
|
|
player.addEventListener('loadedmetadata', function() {
|
|
if (currentTime > 0) {
|
|
player.currentTime = isNaN(currentTime) ? 0 : currentTime;
|
|
}
|
|
}, { once: true });
|
|
|
|
if (window.localStorage.getItem('hospitalQueue:audioEnabled') === 'true') {
|
|
player.muted = false;
|
|
}
|
|
|
|
player.play().catch(function(e) {
|
|
console.error("Error playing video:", e);
|
|
if (!player.muted) {
|
|
player.muted = true;
|
|
player.play().catch(e => console.error("Fallback play failed:", e));
|
|
}
|
|
});
|
|
|
|
if (videos.length > 1) {
|
|
player.addEventListener('ended', function() {
|
|
currentIdx = (currentIdx + 1) % videos.length;
|
|
player.src = videos[currentIdx];
|
|
player.load();
|
|
player.play().catch(function(e) { console.error("Error playing video:", e); });
|
|
});
|
|
} else {
|
|
player.loop = true;
|
|
}
|
|
|
|
window.addEventListener('beforeunload', function() {
|
|
sessionStorage.setItem('adsVideoIndex', currentIdx);
|
|
sessionStorage.setItem('adsVideoTime', player.currentTime);
|
|
});
|
|
});
|
|
</script>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="card shadow-sm border-0 h-100 bg-primary text-white">
|
|
<div class="card-header border-bottom border-light border-opacity-25 py-2 bg-transparent d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 font-weight-bold text-white"><i class="bi bi-info-circle me-2"></i><?= qh_h(qh_t('Information', 'معلومات')) ?></h5>
|
|
<span class="badge bg-white text-primary rounded-pill small"><?= qh_h(qh_t('Notices', 'تنبيهات')) ?></span>
|
|
</div>
|
|
<div class="card-body p-4 d-flex flex-column gap-4">
|
|
<div class="border border-light border-opacity-25 rounded p-4 bg-white bg-opacity-10">
|
|
<div class="badge bg-light text-primary mb-2 px-2 py-1"><?= qh_h(qh_t('Service', 'خدمة')) ?></div>
|
|
<h4 class="text-white fw-bold"><?= qh_h(qh_t('Lab packages & checks', 'باقات المختبر والفحوصات')) ?></h4>
|
|
<p class="mb-0 text-white text-opacity-75"><?= qh_h(qh_t('Ask reception about bundled blood tests, diabetes follow-up, and annual screenings.', 'اسأل الاستقبال عن باقات تحاليل الدم، ومتابعة السكري، والفحوصات السنوية.')) ?></p>
|
|
</div>
|
|
|
|
<div class="border border-light border-opacity-25 rounded p-4 bg-white bg-opacity-10">
|
|
<div class="badge bg-light text-primary mb-2 px-2 py-1"><?= qh_h(qh_t('Reminder', 'تذكير')) ?></div>
|
|
<h4 class="text-white fw-bold"><?= qh_h(qh_t('Keep your ticket visible', 'احتفظ بتذكرتك ظاهرة')) ?></h4>
|
|
<p class="mb-0 text-white text-opacity-75"><?= qh_h(qh_t('We announce ticket numbers on this screen and by voice. Stay near your department area.', 'نعلن أرقام التذاكر على هذه الشاشة وبالصوت. يرجى البقاء قرب منطقة القسم الخاص بك.')) ?></p>
|
|
</div>
|
|
|
|
<div class="border border-light border-opacity-25 rounded p-4 bg-white bg-opacity-10">
|
|
<div class="badge bg-light text-primary mb-2 px-2 py-1"><?= qh_h(qh_t('Wayfinding', 'الإرشاد')) ?></div>
|
|
<h4 class="text-white fw-bold"><?= qh_h(qh_t('Pharmacy & billing', 'الصيدلية والمحاسبة')) ?></h4>
|
|
<p class="mb-0 text-white text-opacity-75"><?= qh_h(qh_t('Completed visits can proceed to the pharmacy and billing desk near the main exit.', 'بعد انتهاء الزيارة يمكن التوجه إلى الصيدلية ومكتب المحاسبة قرب المخرج الرئيسي.')) ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($activeNews)): ?>
|
|
<div class="news-ticker-container">
|
|
<div class="news-ticker-content">
|
|
<?php foreach ($activeNews as $news): ?>
|
|
<span class="news-ticker-item"><?= qh_h($news) ?></span>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php qh_page_end(); ?>
|