diff --git a/assets/css/custom.css b/assets/css/custom.css
index d603cd2..8e7233e 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -376,8 +376,9 @@ a:hover {
.announcement-card {
display: flex;
+ flex-direction: column;
align-items: center;
- justify-content: space-between;
+ justify-content: center;
gap: 1rem;
padding: 1rem 1.2rem;
border: 1px solid var(--border);
@@ -538,7 +539,6 @@ html[dir="rtl"] .timeline-list::before {
@media (max-width: 767.98px) {
.queue-row-head,
- .announcement-card,
.call-strip,
.doctor-spotlight {
flex-direction: column;
@@ -835,7 +835,6 @@ html[dir="rtl"] .toast-container {
html[dir="rtl"] .admin-list-head,
html[dir="rtl"] .call-strip,
-html[dir="rtl"] .announcement-card,
html[dir="rtl"] .admin-sidebar-link {
flex-direction: row-reverse;
}
diff --git a/display.php b/display.php
index a705cbf..56d946b 100644
--- a/display.php
+++ b/display.php
@@ -82,15 +82,15 @@ qh_page_start(
-
+
= qh_h($ticket['ticket_number']) ?>
-
= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>
-
+
= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>
+
= qh_h(qh_t('Room', 'غرفة')) ?> = qh_h($ticket['doctor_room'] ?? '--') ?>
-
-
diff --git a/patch.php b/patch.php
deleted file mode 100644
index 3e36e38..0000000
--- a/patch.php
+++ /dev/null
@@ -1,117 +0,0 @@
- ['class' => 'warning', 'en' => 'Waiting for vitals', 'ar' => 'بانتظار العلامات الحيوية'],
-EOD;
-
-$replace1 = <<<'EOD'
-'waiting_vitals' => ['class' => 'warning', 'en' => 'Waiting for vitals', 'ar' => 'بانتظار العلامات الحيوية'],
- 'nursing_called' => ['class' => 'primary', 'en' => 'Nursing Call', 'ar' => 'نداء التمريض'],
-EOD;
-$content = str_replace($search1, $replace1, $content);
-
-$search2 = <<<'EOD'
-SUM(CASE WHEN t.status = 'waiting_vitals' THEN 1 ELSE 0 END) AS vitals_waiting,
-EOD;
-
-$replace2 = <<<'EOD'
-SUM(CASE WHEN t.status IN ('waiting_vitals', 'nursing_called') THEN 1 ELSE 0 END) AS vitals_waiting,
-EOD;
-$content = str_replace($search2, $replace2, $content);
-
-$search3 = <<<'EOD'
-'waiting_vitals' => (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND status = 'waiting_vitals'")->fetchColumn(),
-EOD;
-
-$replace3 = <<<'EOD'
-'waiting_vitals' => (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND status IN ('waiting_vitals', 'nursing_called')")->fetchColumn(),
-EOD;
-$content = str_replace($search3, $replace3, $content);
-
-$search4 = <<<'EOD'
-function qh_call_message(array $ticket): array
-{
- $ticketNumber = $ticket['ticket_number'] ?? '---';
- $doctorNameEn = $ticket['doctor_name_en'] ?? 'Doctor';
-EOD;
-
-$replace4 = <<<'EOD'
-function qh_call_message(array $ticket): array
-{
- $ticketNumber = $ticket['ticket_number'] ?? '---';
-
- if (("ticket['status']" ?? '') === 'nursing_called') {
- return [
- 'en' => sprintf('Ticket %s, please proceed to Nursing Station.', $ticketNumber),
- 'ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى محطة التمريض.', $ticketNumber),
- ];
- }
-
- $doctorNameEn = $ticket['doctor_name_en'] ?? 'Doctor';
-EOD;
-$content = str_replace($search4, $replace4, $content);
-
-$searchHandler = <<<'EOD'
- $ticketId = (int) ($_POST['ticket_id'] ?? 0);
- $vitalsNotes = trim((string) ($_POST['vitals_notes'] ?? ''));
- if ($ticketId <= 0 || $vitalsNotes === '') {
- throw new InvalidArgumentException(qh_t('Please add a short vitals note before sending the patient forward.', 'يرجى إضافة ملاحظة قصيرة للعلامات الحيوية قبل إرسال المريض.'));
- }
-
- $stmt = db()->prepare(
- "UPDATE hospital_queue_records
- SET vitals_notes = :vitals_notes,
- status = 'ready_for_doctor',
- display_note = 'Vitals completed. Wait for doctor call.'
- WHERE item_type = 'ticket' AND id = :ticket_id AND status = 'waiting_vitals'"
- );
- $stmt->execute([
- 'vitals_notes' => $vitalsNotes,
- 'ticket_id' => $ticketId,
- ]);
- qh_set_flash('success', qh_t('Vitals captured and patient moved to the doctor queue.', 'تم حفظ العلامات الحيوية ونقل المريض إلى طابور الطبيب.'));
-EOD;
-
-$replaceHandler = <<<'EOD'
- $ticketId = (int) ($_POST['ticket_id'] ?? 0);
- $action = trim((string) ($_POST['action'] ?? 'send'));
-
- $ticket = qh_fetch_ticket($ticketId);
- if (!$ticket) throw new InvalidArgumentException(qh_t('Invalid ticket.', 'تذكرة غير صالحة.'));
-
- if ($action === 'call_ticket') {
- $stmt = db()->prepare(
- "UPDATE hospital_queue_records
- SET status = 'nursing_called', called_at = NOW(), display_note = :display_note
- WHERE item_type = 'ticket' AND id = :ticket_id"
- );
- $stmt->execute([
- 'display_note' => sprintf('Ticket %s, proceed to Nursing Station.', $ticket['ticket_number']),
- 'ticket_id' => $ticketId
- ]);
- qh_set_flash('success', qh_t('Patient call was sent to the public display.', 'تم إرسال نداء المريض إلى الشاشة العامة.'));
- } else {
- $vitalsNotes = trim((string) ($_POST['vitals_notes'] ?? ''));
- if ($vitalsNotes === '') throw new InvalidArgumentException(qh_t('Please add a short vitals note before sending the patient forward.', 'يرجى إضافة ملاحظة قصيرة للعلامات الحيوية قبل إرسال المريض.'));
-
- $stmt = db()->prepare(
- "UPDATE hospital_queue_records
- SET vitals_notes = :vitals_notes,
- status = 'ready_for_doctor',
- display_note = 'Vitals completed. Wait for doctor call.'
- WHERE item_type = 'ticket' AND id = :ticket_id AND status IN ('waiting_vitals', 'nursing_called')"
- );
- $stmt->execute([
- 'vitals_notes' => $vitalsNotes,
- 'ticket_id' => $ticketId,
- ]);
- qh_set_flash('success', qh_t('Vitals captured and patient moved to the doctor queue.', 'تم حفظ العلامات الحيوية ونقل المريض إلى طابور الطبيب.'));
- }
-EOD;
-$content = str_replace($searchHandler, $replaceHandler, $content);
-
-file_put_contents('queue_bootstrap.php', $content);
-echo "SUCCESS!\n";
-
-?>
\ No newline at end of file
diff --git a/patch_audio.js b/patch_audio.js
deleted file mode 100644
index ce05434..0000000
--- a/patch_audio.js
+++ /dev/null
@@ -1,87 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-const jsPath = path.join(__dirname, 'assets/js/main.js');
-let jsCode = fs.readFileSync(jsPath, 'utf8');
-
-// Replace the icon logic
-jsCode = jsCode.replace(
- /audioBtn\.innerHTML = '
<\/i>';/g,
- `audioBtn.innerHTML = '';`
-).replace(
- /audioBtn\.innerHTML = '<\/i>';/g,
- `audioBtn.innerHTML = '';`
-);
-
-// Add logic to test sound when audio is enabled
-const repl1 = `if (nextState) {
- initAudio();
- if ('speechSynthesis' in window) {
- const primeUtterance = new SpeechSynthesisUtterance('');
- window.speechSynthesis.speak(primeUtterance);
- }
- }`;
-const repl2 = `if (nextState) {
- initAudio();
- if ('speechSynthesis' in window) {
- const primeUtterance = new SpeechSynthesisUtterance('');
- window.speechSynthesis.speak(primeUtterance);
- }
-
- // Force latest announcement to replay
- const cards = Array.from(document.querySelectorAll('.announcement-card'));
- const latest = cards[0];
- if (latest) {
- const storageKey =
-`hospitalQueue:lastAnnouncement:${locale}
-`;
- window.localStorage.removeItem(storageKey);
- setTimeout(checkAnnouncements, 200);
- }
- }`;
-jsCode = jsCode.replace(repl1, repl2);
-
-// Make sure speech synthesis gets voices early, and handle fallback properly
-const oldVoiceLogic = `if (langVoices.length > 0) {
- // Try to find a high-quality (Google/Microsoft Natural) voice
- const bestVoice = langVoices.find(v =>
- v.name.includes('Google') ||
- v.name.includes('Natural') ||
- v.name.includes('Premium') ||
- v.name.includes('Online')
- ) || langVoices.find(v => v.name.includes('Microsoft')) || langVoices[0];
-
- utterance.voice = bestVoice;
- }`;
-
-const newVoiceLogic = `if (langVoices.length > 0) {
- // Try to find a high-quality (Google/Microsoft Natural) voice
- const bestVoice = langVoices.find(v =>
- v.name.includes('Google') ||
- v.name.includes('Natural') ||
- v.name.includes('Premium') ||
- v.name.includes('Online')
- ) || langVoices.find(v => v.name.includes('Microsoft')) || langVoices[0];
-
- utterance.voice = bestVoice;
- } else if (voices.length > 0) {
- // Fallback to any available voice to ensure sound plays
- utterance.voice = voices[0];
- }`;
-
-jsCode = jsCode.replace(oldVoiceLogic, newVoiceLogic);
-
-// Call getVoices early
-if (!jsCode.includes('window.speechSynthesis.getVoices();')) {
- jsCode = jsCode.replace(
- `document.addEventListener('DOMContentLoaded', () => {`,
- `document.addEventListener('DOMContentLoaded', () => {
- if ('speechSynthesis' in window) {
- window.speechSynthesis.onvoiceschanged = () => { window.speechSynthesis.getVoices(); };
- window.speechSynthesis.getVoices();
- }`
- );
-}
-
-fs.writeFileSync(jsPath, jsCode);
-console.log('patched');
diff --git a/patch_css.php b/patch_css.php
new file mode 100644
index 0000000..d99c6d1
--- /dev/null
+++ b/patch_css.php
@@ -0,0 +1,38 @@
+
+ = qh_h($ticket['ticket_number']) ?>
+ = qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>
+
+ = qh_h(qh_t('Room', 'غرفة')) ?> = qh_h($ticket['doctor_room'] ?? '--') ?>
+
+
+
+HTML;
+
+$replace = <<<'HTML'
+
+
= qh_h($ticket['ticket_number']) ?>
+
= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>
+
+ = qh_h(qh_t('Room', 'غرفة')) ?> = qh_h($ticket['doctor_room'] ?? '--') ?>
+
+
+ = qh_format_datetime($ticket['called_at'] ?? $ticket['updated_at']) ?>
+
+
+HTML;
+
+$newContent = str_replace($search, $replace, $content);
+if ($newContent !== $content) {
+ file_put_contents('display.php', $newContent);
+ echo "Replaced successfully\n";
+} else {
+ echo "Pattern not found\n";
+}
+
diff --git a/patch_display_w100.php b/patch_display_w100.php
new file mode 100644
index 0000000..1d38b95
--- /dev/null
+++ b/patch_display_w100.php
@@ -0,0 +1,12 @@
+';
+$replace = '
';
+$newContent = str_replace($search, $replace, $content);
+if ($newContent !== $content) {
+ file_put_contents('display.php', $newContent);
+ echo "Added w-100 to card-body\n";
+} else {
+ echo "w-100 already added or not found\n";
+}
+
diff --git a/queue_bootstrap.php b/queue_bootstrap.php
index 305435c..efc18a4 100644
--- a/queue_bootstrap.php
+++ b/queue_bootstrap.php
@@ -972,14 +972,24 @@ function qh_status_badge(string $status): string
function qh_call_message(array $ticket): array
{
$ticketNumber = $ticket['ticket_number'] ?? '---';
- $spokenTicket = trim(preg_replace('/([a-zA-Z])/u', '$1, ', str_replace('-', ' ', $ticketNumber)));
-
+ // For English speech, replacing the hyphen with a space helps it say "DRR 001" instead of "DRR minus 001"
+ $speechEn = strtoupper(str_replace('-', ' ', $ticketNumber));
+
+ // Map English letters to Arabic phonetics so Arabic TTS pronounces them correctly
+ $arMap = [
+ 'A'=>'إيه ', 'B'=>'بي ', 'C'=>'سي ', 'D'=>'دي ', 'E'=>'إي ', 'F'=>'إف ', 'G'=>'جي ', 'H'=>'إتش ',
+ 'I'=>'آي ', 'J'=>'جيه ', 'K'=>'كيه ', 'L'=>'إل ', 'M'=>'إم ', 'N'=>'إن ', 'O'=>'أو ', 'P'=>'بي ',
+ 'Q'=>'كيو ', 'R'=>'آر ', 'S'=>'إس ', 'T'=>'تي ', 'U'=>'يو ', 'V'=>'في ', 'W'=>'دبليو ', 'X'=>'إكس ',
+ 'Y'=>'واي ', 'Z'=>'زِد '
+ ];
+ $speechAr = trim(preg_replace('/ +/', ' ', strtr($speechEn, $arMap)));
+
if (($ticket['status'] ?? '') === 'nursing_called') {
return [
'en' => sprintf('Ticket %s, please proceed to Nursing Station.', $ticketNumber),
'ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى محطة التمريض.', $ticketNumber),
- 'speech_en' => sprintf('Ticket %s, please proceed to Nursing Station.', $spokenTicket),
- 'speech_ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى محطة التمريض.', $spokenTicket),
+ 'speech_en' => sprintf('Ticket %s, please proceed to Nursing Station.', $speechEn),
+ 'speech_ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى محطة التمريض.', $speechAr),
];
}
@@ -990,8 +1000,8 @@ function qh_call_message(array $ticket): array
return [
'en' => sprintf('Ticket %s, please proceed to room %s for %s.', $ticketNumber, $room, $doctorNameEn),
'ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى الغرفة %s إلى %s.', $ticketNumber, $room, $doctorNameAr),
- 'speech_en' => sprintf('Ticket %s, please proceed to room %s for %s.', $spokenTicket, $room, $doctorNameEn),
- 'speech_ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى الغرفة %s إلى %s.', $spokenTicket, $room, $doctorNameAr),
+ 'speech_en' => sprintf('Ticket %s, please proceed to room %s for %s.', $speechEn, $room, $doctorNameEn),
+ 'speech_ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى الغرفة %s إلى %s.', $speechAr, $room, $doctorNameAr),
];
}