Autosave: 20260331-103916
This commit is contained in:
parent
fd3feb7878
commit
0c7ba978a8
257
admin.php
257
admin.php
@ -4,163 +4,158 @@ require_once __DIR__ . '/queue_bootstrap.php';
|
||||
qh_boot();
|
||||
qh_admin_handle_request();
|
||||
|
||||
$stats = qh_admin_stats();
|
||||
$clinics = qh_fetch_clinics();
|
||||
$doctors = qh_fetch_doctors();
|
||||
$recentClinics = array_slice($clinics, 0, 4);
|
||||
$recentDoctors = array_slice($doctors, 0, 5);
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
|
||||
qh_page_start('admin', 'Admin configuration', 'Configure clinics, doctors, room numbers, and vitals requirements for the queue workflow.');
|
||||
qh_page_start(
|
||||
'admin',
|
||||
qh_t('Admin overview', 'نظرة عامة للإدارة'),
|
||||
qh_t('Structured admin overview with separate pages for clinics and doctors.', 'نظرة عامة منظمة للإدارة مع صفحات مستقلة للعيادات والأطباء.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<section class="page-header-panel mb-4">
|
||||
<div class="admin-layout">
|
||||
<aside class="admin-sidebar-column">
|
||||
<?php qh_render_admin_sidebar('admin.php', $stats); ?>
|
||||
</aside>
|
||||
|
||||
<div class="admin-content-stack">
|
||||
<section class="page-header-panel admin-hero-panel mb-0">
|
||||
<div>
|
||||
<span class="section-kicker">Admin / الإدارة</span>
|
||||
<h1 class="section-title-xl mt-2">Clinic and doctor setup.</h1>
|
||||
<p class="section-copy mb-0">This first iteration supports bilingual clinic names, a vitals-required flag, doctor-room assignments, and immediate use by reception.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Admin overview', 'نظرة عامة للإدارة')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2 mb-2"><?= qh_h(qh_t('A cleaner admin center with separate management pages.', 'مركز إدارة أنظف مع صفحات إدارة منفصلة.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Use the left sidebar to open focused pages for clinics and doctors instead of working in one long page.', 'استخدم الشريط الجانبي لفتح صفحات مركزة للعيادات والأطباء بدلاً من العمل داخل صفحة واحدة طويلة.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-xl-5">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Add clinic / إضافة عيادة</h2>
|
||||
<form method="post" class="vstack gap-3">
|
||||
<input type="hidden" name="action" value="add_clinic">
|
||||
<section class="panel-card admin-section-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<label class="form-label">Code</label>
|
||||
<input class="form-control" type="text" name="code" maxlength="10" placeholder="GEN" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Name (English)</label>
|
||||
<input class="form-control" type="text" name="name_en" placeholder="General Medicine" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Name (Arabic)</label>
|
||||
<input class="form-control" type="text" name="name_ar" placeholder="الطب العام" required>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label">Sort order</label>
|
||||
<input class="form-control" type="number" name="sort_order" value="50" min="1">
|
||||
</div>
|
||||
<div class="col-sm-6 d-flex align-items-end">
|
||||
<div class="form-check form-switch border rounded p-3 w-100 bg-light-subtle">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="requiresVitalsCreate" name="requires_vitals" checked>
|
||||
<label class="form-check-label" for="requiresVitalsCreate">Requires vitals first / يتطلب العلامات الحيوية أولاً</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-dark" type="submit">Save clinic</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-7">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Add doctor / إضافة طبيب</h2>
|
||||
<form method="post" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="action" value="add_doctor">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Name (English)</label>
|
||||
<input class="form-control" type="text" name="name_en" placeholder="Dr. Sarah Malik" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Name (Arabic)</label>
|
||||
<input class="form-control" type="text" name="name_ar" placeholder="د. سارة مالك" required>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Clinic</label>
|
||||
<select class="form-select" name="clinic_id" required>
|
||||
<option value="">Choose clinic</option>
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<option value="<?= qh_h((string) $clinic['id']) ?>"><?= qh_h($clinic['name_en']) ?> / <?= qh_h($clinic['name_ar']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Room</label>
|
||||
<input class="form-control" type="text" name="room_number" placeholder="201" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Order</label>
|
||||
<input class="form-control" type="number" name="sort_order" value="50" min="1">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-dark w-100" type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('System snapshot', 'ملخص النظام')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Current queue configuration', 'إعداد نظام الطوابير الحالي')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Quick numbers and shortcuts for the main admin tasks.', 'أرقام سريعة واختصارات لمهام الإدارة الأساسية.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-xl-5">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Clinics / العيادات</h2>
|
||||
<div class="vstack gap-3">
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<form method="post" class="list-row-form">
|
||||
<input type="hidden" name="action" value="update_clinic">
|
||||
<input type="hidden" name="clinic_id" value="<?= qh_h((string) $clinic['id']) ?>">
|
||||
<div class="admin-overview-grid mt-4">
|
||||
<article class="mini-overview-card admin-overview-card"><span class="workflow-step"><?= qh_h(qh_t('Clinics', 'العيادات')) ?></span><strong><?= qh_h((string) $stats['clinics']) ?></strong><p><?= qh_h(qh_t('Configured destinations used by reception and routing.', 'الوجهات المهيأة التي يستخدمها الاستقبال ومسار المريض.')) ?></p></article>
|
||||
<article class="mini-overview-card admin-overview-card"><span class="workflow-step"><?= qh_h(qh_t('Doctors', 'الأطباء')) ?></span><strong><?= qh_h((string) $stats['doctors']) ?></strong><p><?= qh_h(qh_t('Doctors currently assigned to rooms and clinics.', 'الأطباء المعينون حالياً للغرف والعيادات.')) ?></p></article>
|
||||
<article class="mini-overview-card admin-overview-card"><span class="workflow-step"><?= qh_h(qh_t('Vitals first', 'العلامات أولاً')) ?></span><strong><?= qh_h((string) $stats['vitals_clinics']) ?></strong><p><?= qh_h(qh_t('Clinics that must pass through nursing before the doctor.', 'العيادات التي تمر على التمريض قبل الطبيب.')) ?></p></article>
|
||||
<article class="mini-overview-card admin-overview-card"><span class="workflow-step"><?= qh_h(qh_t('Direct doctor', 'الطبيب مباشرة')) ?></span><strong><?= qh_h((string) $stats['direct_clinics']) ?></strong><p><?= qh_h(qh_t('Clinics that route patients directly to the doctor queue.', 'العيادات التي توجه المرضى مباشرة إلى انتظار الطبيب.')) ?></p></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="admin-card-grid admin-card-grid-triple">
|
||||
<article class="panel-card admin-link-card">
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Branding', 'الهوية')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-2"><?= qh_h(qh_t('Hospital profile', 'ملف المستشفى')) ?></h2>
|
||||
<p class="section-copy mb-4"><?= qh_h(qh_t('Update the hospital logo, favicon, contact details, working hours, and brand colors from a dedicated profile page.', 'حدّث شعار المستشفى والأيقونة وبيانات التواصل وساعات العمل وألوان الهوية من صفحة ملف مستقلة.')) ?></p>
|
||||
<a class="btn btn-dark" href="<?= qh_h(qh_url('admin_hospital.php')) ?>"><?= qh_h(qh_t('Open profile page', 'فتح صفحة الملف')) ?></a>
|
||||
</article>
|
||||
|
||||
<article class="panel-card admin-link-card">
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Directory', 'الدليل')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-2"><?= qh_h(qh_t('Manage clinics', 'إدارة العيادات')) ?></h2>
|
||||
<p class="section-copy mb-4"><?= qh_h(qh_t('Create clinics, define codes, set routing, and edit the display order from a dedicated page.', 'أنشئ العيادات وحدد الرموز ومسار العمل وعدّل ترتيب العرض من صفحة مستقلة.')) ?></p>
|
||||
<a class="btn btn-dark" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('Open clinics page', 'فتح صفحة العيادات')) ?></a>
|
||||
</article>
|
||||
|
||||
<article class="panel-card admin-link-card">
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Directory', 'الدليل')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-2"><?= qh_h(qh_t('Manage doctors', 'إدارة الأطباء')) ?></h2>
|
||||
<p class="section-copy mb-4"><?= qh_h(qh_t('Search the doctor list, update room assignments, and manage records with edit and delete actions.', 'ابحث في قائمة الأطباء وحدّث الغرف والتعيينات وأدر السجلات بخيارات التعديل والحذف.')) ?></p>
|
||||
<a class="btn btn-dark" href="<?= qh_h(qh_url('admin_doctors.php')) ?>"><?= qh_h(qh_t('Open doctors page', 'فتح صفحة الأطباء')) ?></a>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<div class="admin-card-grid admin-card-grid-secondary admin-card-grid-triple">
|
||||
<section class="panel-card admin-section-card hospital-overview-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h($clinic['name_en']) ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($clinic['name_ar']) ?></div>
|
||||
<div class="small text-secondary mt-1">Code <?= qh_h($clinic['code']) ?></div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Hospital profile', 'ملف المستشفى')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_hospital_name()) ?></h2>
|
||||
</div>
|
||||
<div class="row g-2 align-items-center mt-1">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label small mb-1">Sort order</label>
|
||||
<input class="form-control form-control-sm" type="number" name="sort_order" value="<?= qh_h((string) $clinic['sort_order']) ?>" min="1">
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('admin_hospital.php')) ?>"><?= qh_h(qh_t('Edit profile', 'تعديل الملف')) ?></a>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-check form-switch mt-4">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="clinicSwitch<?= qh_h((string) $clinic['id']) ?>" name="requires_vitals" <?= (int) $clinic['requires_vitals'] === 1 ? 'checked' : '' ?>>
|
||||
<label class="form-check-label small" for="clinicSwitch<?= qh_h((string) $clinic['id']) ?>">Vitals required</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-dark mt-3" type="submit">Update clinic</button>
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-7">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Doctors & rooms / الأطباء والغرف</h2>
|
||||
<div class="vstack gap-3">
|
||||
<?php foreach ($doctors as $doctor): ?>
|
||||
<form method="post" class="list-row-form">
|
||||
<input type="hidden" name="action" value="update_doctor">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $doctor['id']) ?>">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
|
||||
<div class="vstack gap-3 mt-4">
|
||||
<div class="admin-summary-row">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h($doctor['name_en']) ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($doctor['name_ar']) ?></div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_t('Short name', 'الاسم المختصر')) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h((string) ($profile['short_name'] ?? qh_t('Not set yet', 'غير محدد بعد'))) ?></div>
|
||||
</div>
|
||||
<span class="room-badge">Current room <?= qh_h($doctor['room_number']) ?></span>
|
||||
<span class="table-pill info"><?= qh_h(qh_t('Brand ready', 'الهوية جاهزة')) ?></span>
|
||||
</div>
|
||||
<div class="row g-3 align-items-end mt-1">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label small">Clinic</label>
|
||||
<select class="form-select form-select-sm" name="clinic_id" required>
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<option value="<?= qh_h((string) $clinic['id']) ?>" <?= (int) $clinic['id'] === (int) $doctor['clinic_id'] ? 'selected' : '' ?>><?= qh_h($clinic['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="admin-summary-row">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_t('Phone', 'الهاتف')) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h((string) (($profile['phone'] ?? '') !== '' ? $profile['phone'] : qh_t('Add a contact number', 'أضف رقم تواصل'))) ?></div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small">Room</label>
|
||||
<input class="form-control form-control-sm" type="text" name="room_number" value="<?= qh_h($doctor['room_number']) ?>" required>
|
||||
<span class="room-badge"><?= qh_h(qh_t('Hours', 'الساعات')) ?></span>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small">Order</label>
|
||||
<input class="form-control form-control-sm" type="number" name="sort_order" value="<?= qh_h((string) $doctor['sort_order']) ?>" min="1">
|
||||
<div class="admin-summary-row">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_t('Working hours', 'ساعات العمل')) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_name($profile, 'working_hours', qh_t('Set your opening hours', 'حدّد ساعات العمل'))) ?></div>
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button class="btn btn-sm btn-outline-dark" type="submit">Save</button>
|
||||
<span class="table-pill dark"><?= qh_h(qh_t('Shared app branding', 'هوية مشتركة للتطبيق')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-section-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Clinic preview', 'معاينة العيادات')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Latest clinic setup', 'أحدث إعدادات العيادات')) ?></h2>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('View all', 'عرض الكل')) ?></a>
|
||||
</div>
|
||||
<?php if ($recentClinics === []): ?>
|
||||
<div class="empty-state compact mt-4"><strong><?= qh_h(qh_t('No clinics configured yet.', 'لم يتم إعداد أي عيادات بعد.')) ?></strong><span><?= qh_h(qh_t('Open the clinics page to add your first clinic.', 'افتح صفحة العيادات لإضافة أول عيادة.')) ?></span></div>
|
||||
<?php else: ?>
|
||||
<div class="vstack gap-3 mt-4">
|
||||
<?php foreach ($recentClinics as $clinic): ?>
|
||||
<div class="admin-summary-row">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($clinic)) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_t('Code', 'الرمز')) ?>: <?= qh_h((string) $clinic['code']) ?></div>
|
||||
</div>
|
||||
<span class="table-pill <?= (int) $clinic['requires_vitals'] === 1 ? 'warning' : 'info' ?>"><?= qh_h((int) $clinic['requires_vitals'] === 1 ? qh_t('Vitals first', 'العلامات أولاً') : qh_t('Direct doctor', 'الطبيب مباشرة')) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-section-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Doctor preview', 'معاينة الأطباء')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Latest doctor assignments', 'أحدث تعيينات الأطباء')) ?></h2>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('admin_doctors.php')) ?>"><?= qh_h(qh_t('View all', 'عرض الكل')) ?></a>
|
||||
</div>
|
||||
<?php if ($recentDoctors === []): ?>
|
||||
<div class="empty-state compact mt-4"><strong><?= qh_h(qh_t('No doctors configured yet.', 'لم يتم إعداد أي أطباء بعد.')) ?></strong><span><?= qh_h(qh_t('Open the doctors page to add your first doctor profile.', 'افتح صفحة الأطباء لإضافة أول ملف طبيب.')) ?></span></div>
|
||||
<?php else: ?>
|
||||
<div class="vstack gap-3 mt-4">
|
||||
<?php foreach ($recentDoctors as $doctor): ?>
|
||||
<div class="admin-summary-row">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($doctor)) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_name($doctor, 'clinic_name', qh_t('Unassigned clinic', 'عيادة غير محددة'))) ?></div>
|
||||
</div>
|
||||
<span class="room-badge"><?= qh_h(qh_t('Room', 'الغرفة')) ?> <?= qh_h((string) $doctor['room_number']) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
188
admin_clinics.php
Normal file
188
admin_clinics.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/queue_bootstrap.php';
|
||||
qh_boot();
|
||||
qh_admin_handle_request();
|
||||
|
||||
$stats = qh_admin_stats();
|
||||
$clinics = qh_fetch_clinics();
|
||||
$search = trim((string) ($_GET['q'] ?? ''));
|
||||
$editId = (int) ($_GET['edit'] ?? 0);
|
||||
$editClinic = $editId > 0 ? qh_fetch_clinic($editId) : null;
|
||||
|
||||
if ($editId > 0 && $editClinic === null) {
|
||||
qh_set_flash('warning', qh_t('The requested clinic record was not found.', 'لم يتم العثور على سجل العيادة المطلوب.'));
|
||||
qh_redirect('admin_clinics.php');
|
||||
}
|
||||
|
||||
$toLower = static function (string $value): string {
|
||||
return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
|
||||
};
|
||||
|
||||
if ($search !== '') {
|
||||
$needle = $toLower($search);
|
||||
$clinics = array_values(array_filter($clinics, static function (array $clinic) use ($needle, $toLower): bool {
|
||||
$haystack = implode(' ', [
|
||||
(string) ($clinic['code'] ?? ''),
|
||||
(string) ($clinic['name_en'] ?? ''),
|
||||
(string) ($clinic['name_ar'] ?? ''),
|
||||
(int) ($clinic['requires_vitals'] ?? 0) === 1 ? 'vitals first' : 'direct doctor',
|
||||
]);
|
||||
return str_contains($toLower($haystack), $needle);
|
||||
}));
|
||||
}
|
||||
|
||||
qh_page_start(
|
||||
'admin',
|
||||
qh_t('Clinic management', 'إدارة العيادات'),
|
||||
qh_t('Professional clinic directory with search, edit, and delete actions.', 'دليل احترافي للعيادات مع البحث وخيارات التعديل والحذف.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<div class="admin-layout">
|
||||
<aside class="admin-sidebar-column">
|
||||
<?php qh_render_admin_sidebar('admin_clinics.php', $stats); ?>
|
||||
</aside>
|
||||
|
||||
<div class="admin-content-stack">
|
||||
<section class="page-header-panel admin-hero-panel mb-0">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Clinic directory', 'دليل العيادات')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2 mb-2"><?= qh_h(qh_t('Dedicated clinic management with cleaner routing control.', 'إدارة مستقلة للعيادات مع تحكم أنظف في مسار العمل.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Search clinics, adjust codes and routing, and keep the front desk workflow organized.', 'ابحث في العيادات وعدّل الرموز ومسار العمل وحافظ على تنظيم سير العمل في الاستقبال.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-toolbar-card">
|
||||
<div class="admin-toolbar">
|
||||
<form method="get" class="admin-search-form" role="search">
|
||||
<input type="hidden" name="lang" value="<?= qh_h(qh_locale()) ?>">
|
||||
<label class="visually-hidden" for="clinicSearch"><?= qh_h(qh_t('Search clinics', 'البحث عن العيادات')) ?></label>
|
||||
<input id="clinicSearch" class="form-control" type="search" name="q" value="<?= qh_h($search) ?>" placeholder="<?= qh_h(qh_t('Search by clinic name or code', 'ابحث باسم العيادة أو الرمز')) ?>">
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h(qh_t('Search', 'بحث')) ?></button>
|
||||
<?php if ($search !== ''): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('Reset', 'إعادة ضبط')) ?></a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php if ($editClinic !== null): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('Add new clinic', 'إضافة عيادة جديدة')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="admin-directory-layout">
|
||||
<section class="panel-card admin-table-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Clinic list', 'قائمة العيادات')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Clinics and routing rules', 'العيادات وقواعد مسار العمل')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h($search !== ''
|
||||
? qh_t('Filtered results based on your search.', 'نتائج مفلترة بناءً على البحث.')
|
||||
: qh_t('All configured clinic records.', 'جميع سجلات العيادات المهيأة.')) ?></p>
|
||||
</div>
|
||||
<div class="admin-count-chip"><?= qh_h((string) count($clinics)) ?> <?= qh_h(qh_t('records', 'سجلات')) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($clinics === []): ?>
|
||||
<div class="empty-state compact mt-4"><strong><?= qh_h(qh_t('No clinic records match this view.', 'لا توجد سجلات عيادات مطابقة لهذا العرض.')) ?></strong><span><?= qh_h(qh_t('Try another search or create a new clinic.', 'جرّب بحثاً آخر أو أنشئ عيادة جديدة.')) ?></span></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table align-middle admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= qh_h(qh_t('Code', 'الرمز')) ?></th>
|
||||
<th><?= qh_h(qh_t('Clinic', 'العيادة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Routing', 'المسار')) ?></th>
|
||||
<th><?= qh_h(qh_t('Order', 'الترتيب')) ?></th>
|
||||
<th><?= qh_h(qh_t('Actions', 'الإجراءات')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<?php $editUrl = qh_url('admin_clinics.php', ['edit' => (int) $clinic['id'], 'q' => $search]); ?>
|
||||
<tr>
|
||||
<td><span class="room-badge"><?= qh_h((string) $clinic['code']) ?></span></td>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($clinic)) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_is_ar() ? (string) ($clinic['name_en'] ?? '') : (string) ($clinic['name_ar'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td><span class="table-pill <?= (int) $clinic['requires_vitals'] === 1 ? 'warning' : 'info' ?>"><?= qh_h((int) $clinic['requires_vitals'] === 1 ? qh_t('Vitals first', 'العلامات أولاً') : qh_t('Direct doctor', 'الطبيب مباشرة')) ?></span></td>
|
||||
<td><?= qh_h((string) $clinic['sort_order']) ?></td>
|
||||
<td>
|
||||
<div class="admin-table-actions">
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h($editUrl) ?>"><?= qh_h(qh_t('Edit', 'تعديل')) ?></a>
|
||||
<form method="post" onsubmit="return confirm('<?= qh_h(qh_t('Delete this clinic record?', 'هل تريد حذف سجل هذه العيادة؟')) ?>');">
|
||||
<input type="hidden" name="action" value="delete_clinic">
|
||||
<input type="hidden" name="clinic_id" value="<?= qh_h((string) $clinic['id']) ?>">
|
||||
<input type="hidden" name="return_to" value="admin_clinics.php">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit"><?= qh_h(qh_t('Delete', 'حذف')) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-form-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h($editClinic ? qh_t('Edit clinic', 'تعديل العيادة') : qh_t('New clinic', 'عيادة جديدة')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h($editClinic ? qh_t('Update clinic profile', 'تحديث ملف العيادة') : qh_t('Create clinic profile', 'إنشاء ملف العيادة')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Define the code, both names, routing type, and display order in one focused form.', 'حدّد الرمز والاسمين ونوع المسار وترتيب العرض في نموذج واحد مركز.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="vstack gap-3 mt-4">
|
||||
<input type="hidden" name="action" value="<?= qh_h($editClinic ? 'update_clinic' : 'add_clinic') ?>">
|
||||
<input type="hidden" name="return_to" value="admin_clinics.php">
|
||||
<?php if ($editClinic): ?>
|
||||
<input type="hidden" name="clinic_id" value="<?= qh_h((string) $editClinic['id']) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label" for="clinicCode"><?= qh_h(qh_t('Clinic code', 'رمز العيادة')) ?></label>
|
||||
<input id="clinicCode" class="form-control" type="text" maxlength="10" name="code" value="<?= qh_h((string) ($editClinic['code'] ?? '')) ?>" required>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<label class="form-label" for="clinicOrder"><?= qh_h(qh_t('Display order', 'ترتيب العرض')) ?></label>
|
||||
<input id="clinicOrder" class="form-control" type="number" min="1" name="sort_order" value="<?= qh_h((string) ($editClinic['sort_order'] ?? 50)) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="clinicNameEn"><?= qh_h(qh_t('Clinic name (English)', 'اسم العيادة بالإنجليزية')) ?></label>
|
||||
<input id="clinicNameEn" class="form-control" type="text" name="name_en" value="<?= qh_h((string) ($editClinic['name_en'] ?? '')) ?>" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="clinicNameAr"><?= qh_h(qh_t('Clinic name (Arabic)', 'اسم العيادة بالعربية')) ?></label>
|
||||
<input id="clinicNameAr" class="form-control" type="text" name="name_ar" value="<?= qh_h((string) ($editClinic['name_ar'] ?? '')) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="admin-switch-card">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="clinicVitals" name="requires_vitals" <?= (int) ($editClinic['requires_vitals'] ?? 0) === 1 ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="clinicVitals"><?= qh_h(qh_t('Require nursing vitals before doctor', 'يتطلب العلامات الحيوية في التمريض قبل الطبيب')) ?></label>
|
||||
</div>
|
||||
<div class="admin-form-note mt-2"><?= qh_h(qh_t('Turn this on for clinics that must stop at nursing before the doctor queue.', 'فعّل هذا الخيار للعيادات التي يجب أن تمر على التمريض قبل قائمة انتظار الطبيب.')) ?></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 pt-2">
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h($editClinic ? qh_t('Save changes', 'حفظ التعديلات') : qh_t('Add clinic', 'إضافة عيادة')) ?></button>
|
||||
<?php if ($editClinic): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('Cancel edit', 'إلغاء التعديل')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php qh_page_end(); ?>
|
||||
196
admin_doctors.php
Normal file
196
admin_doctors.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/queue_bootstrap.php';
|
||||
qh_boot();
|
||||
qh_admin_handle_request();
|
||||
|
||||
$stats = qh_admin_stats();
|
||||
$clinics = qh_fetch_clinics();
|
||||
$doctors = qh_fetch_doctors();
|
||||
$search = trim((string) ($_GET['q'] ?? ''));
|
||||
$editId = (int) ($_GET['edit'] ?? 0);
|
||||
$editDoctor = $editId > 0 ? qh_fetch_doctor($editId) : null;
|
||||
|
||||
if ($editId > 0 && $editDoctor === null) {
|
||||
qh_set_flash('warning', qh_t('The requested doctor record was not found.', 'لم يتم العثور على سجل الطبيب المطلوب.'));
|
||||
qh_redirect('admin_doctors.php');
|
||||
}
|
||||
|
||||
$toLower = static function (string $value): string {
|
||||
return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
|
||||
};
|
||||
|
||||
if ($search !== '') {
|
||||
$needle = $toLower($search);
|
||||
$doctors = array_values(array_filter($doctors, static function (array $doctor) use ($needle, $toLower): bool {
|
||||
$haystack = implode(' ', [
|
||||
(string) ($doctor['name_en'] ?? ''),
|
||||
(string) ($doctor['name_ar'] ?? ''),
|
||||
(string) ($doctor['clinic_name_en'] ?? ''),
|
||||
(string) ($doctor['clinic_name_ar'] ?? ''),
|
||||
(string) ($doctor['room_number'] ?? ''),
|
||||
]);
|
||||
return str_contains($toLower($haystack), $needle);
|
||||
}));
|
||||
}
|
||||
|
||||
qh_page_start(
|
||||
'admin',
|
||||
qh_t('Doctor management', 'إدارة الأطباء'),
|
||||
qh_t('Professional doctor directory with search, edit, and delete actions.', 'دليل احترافي للأطباء مع البحث وخيارات التعديل والحذف.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<div class="admin-layout">
|
||||
<aside class="admin-sidebar-column">
|
||||
<?php qh_render_admin_sidebar('admin_doctors.php', $stats); ?>
|
||||
</aside>
|
||||
|
||||
<div class="admin-content-stack">
|
||||
<section class="page-header-panel admin-hero-panel mb-0">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Doctor directory', 'دليل الأطباء')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2 mb-2"><?= qh_h(qh_t('Dedicated doctor management with clear actions.', 'إدارة مستقلة للأطباء مع إجراءات واضحة.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Search doctors, edit assignments, and manage room records from a focused page.', 'ابحث عن الأطباء وعدّل التعيينات وأدر سجلات الغرف من صفحة مركزة.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-toolbar-card">
|
||||
<div class="admin-toolbar">
|
||||
<form method="get" class="admin-search-form" role="search">
|
||||
<input type="hidden" name="lang" value="<?= qh_h(qh_locale()) ?>">
|
||||
<label class="visually-hidden" for="doctorSearch"><?= qh_h(qh_t('Search doctors', 'البحث عن الأطباء')) ?></label>
|
||||
<input id="doctorSearch" class="form-control" type="search" name="q" value="<?= qh_h($search) ?>" placeholder="<?= qh_h(qh_t('Search by doctor, clinic, or room', 'ابحث باسم الطبيب أو العيادة أو الغرفة')) ?>">
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h(qh_t('Search', 'بحث')) ?></button>
|
||||
<?php if ($search !== ''): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_doctors.php')) ?>"><?= qh_h(qh_t('Reset', 'إعادة ضبط')) ?></a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php if ($editDoctor !== null): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_doctors.php')) ?>"><?= qh_h(qh_t('Add new doctor', 'إضافة طبيب جديد')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="admin-directory-layout">
|
||||
<section class="panel-card admin-table-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Doctor list', 'قائمة الأطباء')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Doctors and room assignments', 'الأطباء وتعيينات الغرف')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h($search !== ''
|
||||
? qh_t('Filtered results based on your search.', 'نتائج مفلترة بناءً على البحث.')
|
||||
: qh_t('All configured doctor records.', 'جميع سجلات الأطباء المهيأة.')) ?></p>
|
||||
</div>
|
||||
<div class="admin-count-chip"><?= qh_h((string) count($doctors)) ?> <?= qh_h(qh_t('records', 'سجلات')) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($doctors === []): ?>
|
||||
<div class="empty-state compact mt-4"><strong><?= qh_h(qh_t('No doctor records match this view.', 'لا توجد سجلات أطباء مطابقة لهذا العرض.')) ?></strong><span><?= qh_h(qh_t('Try another search or create a new doctor profile.', 'جرّب بحثاً آخر أو أنشئ ملف طبيب جديد.')) ?></span></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table align-middle admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= qh_h(qh_t('Doctor', 'الطبيب')) ?></th>
|
||||
<th><?= qh_h(qh_t('Clinic', 'العيادة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Room', 'الغرفة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Order', 'الترتيب')) ?></th>
|
||||
<th><?= qh_h(qh_t('Actions', 'الإجراءات')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($doctors as $doctor): ?>
|
||||
<?php $editUrl = qh_url('admin_doctors.php', ['edit' => (int) $doctor['id'], 'q' => $search]); ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($doctor)) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_is_ar() ? (string) ($doctor['name_en'] ?? '') : (string) ($doctor['name_ar'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td><?= qh_h(qh_name($doctor, 'clinic_name', qh_t('Unassigned', 'غير محدد'))) ?></td>
|
||||
<td><span class="room-badge"><?= qh_h((string) $doctor['room_number']) ?></span></td>
|
||||
<td><?= qh_h((string) $doctor['sort_order']) ?></td>
|
||||
<td>
|
||||
<div class="admin-table-actions">
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h($editUrl) ?>"><?= qh_h(qh_t('Edit', 'تعديل')) ?></a>
|
||||
<form method="post" onsubmit="return confirm('<?= qh_h(qh_t('Delete this doctor record?', 'هل تريد حذف سجل هذا الطبيب؟')) ?>');">
|
||||
<input type="hidden" name="action" value="delete_doctor">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $doctor['id']) ?>">
|
||||
<input type="hidden" name="return_to" value="admin_doctors.php">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit"><?= qh_h(qh_t('Delete', 'حذف')) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="panel-card admin-form-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h($editDoctor ? qh_t('Edit doctor', 'تعديل الطبيب') : qh_t('New doctor', 'طبيب جديد')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h($editDoctor ? qh_t('Update doctor profile', 'تحديث ملف الطبيب') : qh_t('Create doctor profile', 'إنشاء ملف الطبيب')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Store both English and Arabic names while keeping the page language separate.', 'احفظ الاسمين الإنجليزي والعربي مع إبقاء لغة الصفحة منفصلة.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($clinics === []): ?>
|
||||
<div class="empty-state compact mt-4"><strong><?= qh_h(qh_t('Clinics are required first.', 'يجب إعداد العيادات أولاً.')) ?></strong><span><?= qh_h(qh_t('Create at least one clinic before assigning doctors.', 'أنشئ عيادة واحدة على الأقل قبل تعيين الأطباء.')) ?></span></div>
|
||||
<a class="btn btn-dark mt-3" href="<?= qh_h(qh_url('admin_clinics.php')) ?>"><?= qh_h(qh_t('Open clinics page', 'فتح صفحة العيادات')) ?></a>
|
||||
<?php else: ?>
|
||||
<form method="post" class="vstack gap-3 mt-4">
|
||||
<input type="hidden" name="action" value="<?= qh_h($editDoctor ? 'update_doctor' : 'add_doctor') ?>">
|
||||
<input type="hidden" name="return_to" value="admin_doctors.php">
|
||||
<?php if ($editDoctor): ?>
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $editDoctor['id']) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="doctorNameEn"><?= qh_h(qh_t('Doctor name (English)', 'اسم الطبيب بالإنجليزية')) ?></label>
|
||||
<input id="doctorNameEn" class="form-control" type="text" name="name_en" value="<?= qh_h((string) ($editDoctor['name_en'] ?? '')) ?>" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="doctorNameAr"><?= qh_h(qh_t('Doctor name (Arabic)', 'اسم الطبيب بالعربية')) ?></label>
|
||||
<input id="doctorNameAr" class="form-control" type="text" name="name_ar" value="<?= qh_h((string) ($editDoctor['name_ar'] ?? '')) ?>" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="form-label" for="doctorClinic"><?= qh_h(qh_t('Clinic', 'العيادة')) ?></label>
|
||||
<select id="doctorClinic" class="form-select" name="clinic_id" required>
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<option value="<?= qh_h((string) $clinic['id']) ?>" <?= (int) ($editDoctor['clinic_id'] ?? 0) === (int) $clinic['id'] ? 'selected' : '' ?>><?= qh_h(qh_name($clinic)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="doctorRoom"><?= qh_h(qh_t('Room number', 'رقم الغرفة')) ?></label>
|
||||
<input id="doctorRoom" class="form-control" type="text" name="room_number" value="<?= qh_h((string) ($editDoctor['room_number'] ?? '')) ?>" required>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="doctorOrder"><?= qh_h(qh_t('Display order', 'ترتيب العرض')) ?></label>
|
||||
<input id="doctorOrder" class="form-control" type="number" min="1" name="sort_order" value="<?= qh_h((string) ($editDoctor['sort_order'] ?? 50)) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 pt-2">
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h($editDoctor ? qh_t('Save changes', 'حفظ التعديلات') : qh_t('Add doctor', 'إضافة طبيب')) ?></button>
|
||||
<?php if ($editDoctor): ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin_doctors.php')) ?>"><?= qh_h(qh_t('Cancel edit', 'إلغاء التعديل')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php qh_page_end(); ?>
|
||||
243
admin_hospital.php
Normal file
243
admin_hospital.php
Normal file
@ -0,0 +1,243 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/queue_bootstrap.php';
|
||||
qh_boot();
|
||||
qh_admin_handle_request();
|
||||
|
||||
$stats = qh_admin_stats();
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
$logoUrl = qh_hospital_logo_url();
|
||||
$faviconUrl = qh_hospital_favicon_url();
|
||||
$tagline = qh_hospital_tagline();
|
||||
$website = trim((string) ($profile['website'] ?? ''));
|
||||
$websiteIsValid = $website !== '' && filter_var($website, FILTER_VALIDATE_URL) !== false;
|
||||
|
||||
qh_page_start(
|
||||
'admin',
|
||||
qh_t('Hospital profile', 'ملف المستشفى'),
|
||||
qh_t('Manage the hospital brand, logo, favicon, contact details, and presentation settings.', 'إدارة هوية المستشفى والشعار والأيقونة وبيانات التواصل وإعدادات العرض.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<div class="admin-layout">
|
||||
<aside class="admin-sidebar-column">
|
||||
<?php qh_render_admin_sidebar('admin_hospital.php', $stats); ?>
|
||||
</aside>
|
||||
|
||||
<div class="admin-content-stack">
|
||||
<section class="page-header-panel admin-hero-panel mb-0 hospital-hero-panel">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Brand settings', 'إعدادات الهوية')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2 mb-2"><?= qh_h(qh_t('Hospital profile and branding.', 'ملف المستشفى والهوية البصرية.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Set the hospital name, logo, favicon, contact details, working hours, and brand colors from one dedicated page.', 'اضبط اسم المستشفى والشعار والأيقونة وبيانات التواصل وساعات العمل وألوان الهوية من صفحة مخصصة واحدة.')) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="hospital-brand-preview">
|
||||
<div class="hospital-brand-mark">
|
||||
<?php if ($logoUrl !== ''): ?>
|
||||
<img src="<?= qh_h($logoUrl) ?>" alt="<?= qh_h(qh_hospital_name()) ?>">
|
||||
<?php else: ?>
|
||||
<span><?= qh_h(qh_hospital_brand_initials()) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div class="section-kicker mb-2"><?= qh_h(qh_t('Live preview', 'معاينة مباشرة')) ?></div>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_hospital_name()) ?></h2>
|
||||
<?php if ($tagline !== ''): ?>
|
||||
<p class="section-copy mb-2"><?= qh_h($tagline) ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="hospital-color-pills">
|
||||
<span class="hospital-color-pill"><span class="hospital-color-swatch" style="background: <?= qh_h(qh_hospital_primary_color()) ?>"></span><?= qh_h(qh_hospital_primary_color()) ?></span>
|
||||
<span class="hospital-color-pill"><span class="hospital-color-swatch" style="background: <?= qh_h(qh_hospital_secondary_color()) ?>"></span><?= qh_h(qh_hospital_secondary_color()) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="admin-directory-layout hospital-profile-layout">
|
||||
<section class="panel-card admin-form-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Profile editor', 'تحرير الملف')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Update hospital details', 'تحديث بيانات المستشفى')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Use image URLs for the logo and favicon. The saved branding is reused in the top navigation and browser tab.', 'استخدم روابط صور للشعار والأيقونة. سيتم استخدام الهوية المحفوظة في شريط التنقل العلوي وتبويب المتصفح.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="vstack gap-4 mt-4">
|
||||
<input type="hidden" name="action" value="save_hospital_profile">
|
||||
<input type="hidden" name="return_to" value="admin_hospital.php">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalNameEn"><?= qh_h(qh_t('Hospital name (English)', 'اسم المستشفى بالإنجليزية')) ?></label>
|
||||
<input id="hospitalNameEn" class="form-control" type="text" name="name_en" value="<?= qh_h((string) ($profile['name_en'] ?? '')) ?>" required>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalNameAr"><?= qh_h(qh_t('Hospital name (Arabic)', 'اسم المستشفى بالعربية')) ?></label>
|
||||
<input id="hospitalNameAr" class="form-control" type="text" name="name_ar" value="<?= qh_h((string) ($profile['name_ar'] ?? '')) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label" for="hospitalShortName"><?= qh_h(qh_t('Short name', 'الاسم المختصر')) ?></label>
|
||||
<input id="hospitalShortName" class="form-control" type="text" maxlength="40" name="short_name" value="<?= qh_h((string) ($profile['short_name'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label" for="hospitalPhone"><?= qh_h(qh_t('Phone number', 'رقم الهاتف')) ?></label>
|
||||
<input id="hospitalPhone" class="form-control" type="text" name="phone" value="<?= qh_h((string) ($profile['phone'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<label class="form-label" for="hospitalEmail"><?= qh_h(qh_t('Email address', 'البريد الإلكتروني')) ?></label>
|
||||
<input id="hospitalEmail" class="form-control" type="email" name="email" value="<?= qh_h((string) ($profile['email'] ?? '')) ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalTaglineEn"><?= qh_h(qh_t('Tagline (English)', 'الشعار النصي بالإنجليزية')) ?></label>
|
||||
<input id="hospitalTaglineEn" class="form-control" type="text" name="tagline_en" value="<?= qh_h((string) ($profile['tagline_en'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalTaglineAr"><?= qh_h(qh_t('Tagline (Arabic)', 'الشعار النصي بالعربية')) ?></label>
|
||||
<input id="hospitalTaglineAr" class="form-control" type="text" name="tagline_ar" value="<?= qh_h((string) ($profile['tagline_ar'] ?? '')) ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalAddressEn"><?= qh_h(qh_t('Address (English)', 'العنوان بالإنجليزية')) ?></label>
|
||||
<textarea id="hospitalAddressEn" class="form-control" name="address_en" rows="3"><?= qh_h((string) ($profile['address_en'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalAddressAr"><?= qh_h(qh_t('Address (Arabic)', 'العنوان بالعربية')) ?></label>
|
||||
<textarea id="hospitalAddressAr" class="form-control" name="address_ar" rows="3"><?= qh_h((string) ($profile['address_ar'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalHoursEn"><?= qh_h(qh_t('Working hours (English)', 'ساعات العمل بالإنجليزية')) ?></label>
|
||||
<input id="hospitalHoursEn" class="form-control" type="text" name="working_hours_en" value="<?= qh_h((string) ($profile['working_hours_en'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalHoursAr"><?= qh_h(qh_t('Working hours (Arabic)', 'ساعات العمل بالعربية')) ?></label>
|
||||
<input id="hospitalHoursAr" class="form-control" type="text" name="working_hours_ar" value="<?= qh_h((string) ($profile['working_hours_ar'] ?? '')) ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalWebsite"><?= qh_h(qh_t('Website URL', 'رابط الموقع الإلكتروني')) ?></label>
|
||||
<input id="hospitalWebsite" class="form-control" type="url" name="website" placeholder="https://example.com" value="<?= qh_h((string) ($profile['website'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalLogoUrl"><?= qh_h(qh_t('Logo image URL', 'رابط صورة الشعار')) ?></label>
|
||||
<input id="hospitalLogoUrl" class="form-control" type="url" name="logo_url" placeholder="https://.../logo.png" value="<?= qh_h((string) ($profile['logo_url'] ?? '')) ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<label class="form-label" for="hospitalFaviconUrl"><?= qh_h(qh_t('Favicon URL', 'رابط الأيقونة')) ?></label>
|
||||
<input id="hospitalFaviconUrl" class="form-control" type="url" name="favicon_url" placeholder="https://.../favicon.png" value="<?= qh_h((string) ($profile['favicon_url'] ?? '')) ?>">
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label" for="hospitalPrimaryColor"><?= qh_h(qh_t('Primary color', 'اللون الأساسي')) ?></label>
|
||||
<input id="hospitalPrimaryColor" class="form-control form-control-color w-100" type="color" name="primary_color" value="<?= qh_h(qh_hospital_primary_color()) ?>">
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label" for="hospitalSecondaryColor"><?= qh_h(qh_t('Secondary color', 'اللون الثانوي')) ?></label>
|
||||
<input id="hospitalSecondaryColor" class="form-control form-control-color w-100" type="color" name="secondary_color" value="<?= qh_h(qh_hospital_secondary_color()) ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 pt-2">
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h(qh_t('Save hospital profile', 'حفظ ملف المستشفى')) ?></button>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('admin.php')) ?>"><?= qh_h(qh_t('Back to overview', 'العودة إلى النظرة العامة')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="vstack gap-4">
|
||||
<section class="panel-card hospital-preview-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Brand preview', 'معاينة الهوية')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('What staff will see', 'ما الذي سيراه الطاقم')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('This preview mirrors the saved logo, brand colors, and hospital details used by the app shell.', 'تعكس هذه المعاينة الشعار والألوان وبيانات المستشفى المستخدمة في واجهة التطبيق.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hospital-preview-stack mt-4">
|
||||
<div class="hospital-preview-header">
|
||||
<div class="hospital-preview-logo">
|
||||
<?php if ($logoUrl !== ''): ?>
|
||||
<img src="<?= qh_h($logoUrl) ?>" alt="<?= qh_h(qh_hospital_name()) ?>">
|
||||
<?php else: ?>
|
||||
<span><?= qh_h(qh_hospital_brand_initials()) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong><?= qh_h(qh_hospital_name()) ?></strong>
|
||||
<?php if ($tagline !== ''): ?>
|
||||
<p class="section-copy mb-0"><?= qh_h($tagline) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="hospital-detail-list mb-0">
|
||||
<?php if (qh_hospital_contact_value('phone') !== ''): ?>
|
||||
<div><dt><?= qh_h(qh_t('Phone', 'الهاتف')) ?></dt><dd><?= qh_h(qh_hospital_contact_value('phone')) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (qh_hospital_contact_value('email') !== ''): ?>
|
||||
<div><dt><?= qh_h(qh_t('Email', 'البريد الإلكتروني')) ?></dt><dd><?= qh_h(qh_hospital_contact_value('email')) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($websiteIsValid): ?>
|
||||
<div><dt><?= qh_h(qh_t('Website', 'الموقع الإلكتروني')) ?></dt><dd><a href="<?= qh_h($website) ?>" target="_blank" rel="noreferrer"><?= qh_h($website) ?></a></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (qh_hospital_contact_value(qh_is_ar() ? 'address_ar' : 'address_en') !== ''): ?>
|
||||
<div><dt><?= qh_h(qh_t('Address', 'العنوان')) ?></dt><dd><?= nl2br(qh_h(qh_hospital_contact_value(qh_is_ar() ? 'address_ar' : 'address_en'))) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (qh_hospital_contact_value(qh_is_ar() ? 'working_hours_ar' : 'working_hours_en') !== ''): ?>
|
||||
<div><dt><?= qh_h(qh_t('Working hours', 'ساعات العمل')) ?></dt><dd><?= qh_h(qh_hospital_contact_value(qh_is_ar() ? 'working_hours_ar' : 'working_hours_en')) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-card hospital-preview-card">
|
||||
<div class="admin-section-head">
|
||||
<div>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Assets', 'الأصول')) ?></span>
|
||||
<h2 class="section-title mt-2 mb-1"><?= qh_h(qh_t('Logo and favicon status', 'حالة الشعار والأيقونة')) ?></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hospital-asset-grid mt-4">
|
||||
<div class="hospital-asset-box">
|
||||
<span class="workflow-step"><?= qh_h(qh_t('Logo', 'الشعار')) ?></span>
|
||||
<?php if ($logoUrl !== ''): ?>
|
||||
<img class="hospital-asset-image" src="<?= qh_h($logoUrl) ?>" alt="<?= qh_h(qh_t('Hospital logo preview', 'معاينة شعار المستشفى')) ?>">
|
||||
<?php else: ?>
|
||||
<div class="empty-state compact mt-3"><strong><?= qh_h(qh_t('No logo yet.', 'لا يوجد شعار حتى الآن.')) ?></strong><span><?= qh_h(qh_t('Add a logo URL to replace the initials badge in the top header.', 'أضف رابط شعار لاستبدال شارة الأحرف في الترويسة العلوية.')) ?></span></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="hospital-asset-box">
|
||||
<span class="workflow-step"><?= qh_h(qh_t('Favicon', 'الأيقونة')) ?></span>
|
||||
<?php if ($faviconUrl !== ''): ?>
|
||||
<img class="hospital-asset-image hospital-asset-favicon" src="<?= qh_h($faviconUrl) ?>" alt="<?= qh_h(qh_t('Favicon preview', 'معاينة الأيقونة')) ?>">
|
||||
<?php else: ?>
|
||||
<div class="empty-state compact mt-3"><strong><?= qh_h(qh_t('No custom favicon yet.', 'لا توجد أيقونة مخصصة حتى الآن.')) ?></strong><span><?= qh_h(qh_t('If left empty, the app will reuse the logo as the browser icon when available.', 'إذا تُرك الحقل فارغاً، سيعيد التطبيق استخدام الشعار كأيقونة للمتصفح عند توفره.')) ?></span></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php qh_page_end(); ?>
|
||||
@ -1,18 +1,20 @@
|
||||
:root {
|
||||
--bg: #f3f4f6;
|
||||
--bg: #eef6f7;
|
||||
--surface: #ffffff;
|
||||
--surface-muted: #f8fafc;
|
||||
--border: #d7dde6;
|
||||
--border-strong: #c4ccd7;
|
||||
--text: #111827;
|
||||
--muted: #6b7280;
|
||||
--accent: #1f4f78;
|
||||
--accent-soft: #e8f0f6;
|
||||
--surface-muted: #f7fbfc;
|
||||
--border: #d5e3e8;
|
||||
--border-strong: #b7ccd5;
|
||||
--text: #183b4d;
|
||||
--ink: #183b4d;
|
||||
--muted: #61788a;
|
||||
--accent: #0f8b8d;
|
||||
--accent-strong: #16697a;
|
||||
--accent-soft: #e1f4f3;
|
||||
--warning-soft: #fff4d6;
|
||||
--info-soft: #dceef8;
|
||||
--success-soft: #dff3e7;
|
||||
--danger-soft: #fde4e4;
|
||||
--shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||
--shadow: 0 18px 40px rgba(15, 64, 75, 0.08);
|
||||
--radius-sm: 0.45rem;
|
||||
--radius-md: 0.7rem;
|
||||
--radius-lg: 0.95rem;
|
||||
@ -54,6 +56,11 @@ a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -61,11 +68,12 @@ a:hover {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--text);
|
||||
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
||||
color: #fff;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
box-shadow: 0 12px 20px rgba(15, 139, 141, 0.22);
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
@ -73,6 +81,36 @@ a:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
.brand-copy {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 0.05rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-subtext {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.2;
|
||||
max-width: 34ch;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.brand-mark-image {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.brand-mark-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
@ -82,8 +120,8 @@ a:hover {
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
background: #eef2f6;
|
||||
color: var(--text);
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
@ -491,3 +529,633 @@ a:hover {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.admin-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 248px) minmax(0, 1fr);
|
||||
gap: 1.25rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.admin-sidebar-column {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
position: sticky;
|
||||
top: 5.9rem;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.admin-sidebar-top {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.admin-sidebar-nav {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
padding: 0.35rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fbfc 100%);
|
||||
}
|
||||
|
||||
.admin-sidebar-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.7rem;
|
||||
padding: 0.72rem 0.8rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.95rem;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.admin-sidebar-link:hover {
|
||||
color: var(--accent-strong);
|
||||
border-color: rgba(15, 139, 141, 0.14);
|
||||
background: rgba(15, 139, 141, 0.08);
|
||||
}
|
||||
|
||||
.admin-sidebar-link-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-muted);
|
||||
color: var(--accent-strong);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.admin-sidebar-link-icon svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.admin-sidebar-link-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-sidebar-meta {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.admin-mini-stat {
|
||||
padding: 0.95rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
}
|
||||
|
||||
.admin-mini-stat-value {
|
||||
display: block;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.admin-mini-stat-label {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.admin-content-stack {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-hero-panel {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #eef4f9 100%);
|
||||
}
|
||||
|
||||
.admin-section-card {
|
||||
scroll-margin-top: 6.75rem;
|
||||
}
|
||||
|
||||
.admin-section-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.admin-overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.admin-overview-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.admin-overview-card strong {
|
||||
display: block;
|
||||
margin-top: 0.9rem;
|
||||
font-size: clamp(1.8rem, 3vw, 2.35rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.admin-overview-card p {
|
||||
margin-top: 0.65rem;
|
||||
}
|
||||
|
||||
.admin-switch-card {
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.admin-form-note {
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.admin-list-form {
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.admin-list-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-inline-switch {
|
||||
padding: 0.65rem 0.85rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.admin-overview-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.admin-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.admin-overview-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-list-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.admin-sidebar-link {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* language split updates */
|
||||
.lang-switcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.lang-switch-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.45rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border-strong);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: var(--ink);
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.lang-switch-link.active {
|
||||
background: var(--ink);
|
||||
border-color: var(--ink);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.locale-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(17, 24, 39, 0.08);
|
||||
color: var(--ink);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
html[dir="rtl"] body {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .section-title,
|
||||
html[dir="rtl"] .section-title-xl,
|
||||
html[dir="rtl"] .section-copy,
|
||||
html[dir="rtl"] .display-title,
|
||||
html[dir="rtl"] .form-label,
|
||||
html[dir="rtl"] th,
|
||||
html[dir="rtl"] td,
|
||||
html[dir="rtl"] .navbar-brand,
|
||||
html[dir="rtl"] .admin-sidebar,
|
||||
html[dir="rtl"] .empty-state,
|
||||
html[dir="rtl"] .mini-overview-card,
|
||||
html[dir="rtl"] .ticket-card,
|
||||
html[dir="rtl"] .timeline-item,
|
||||
html[dir="rtl"] .detail-list {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .navbar-nav {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .toast-container {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .timeline-item {
|
||||
grid-template-columns: 1fr 18px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .timeline-dot {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
|
||||
.btn-dark {
|
||||
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
||||
border-color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.btn-dark:hover,
|
||||
.btn-dark:focus {
|
||||
background: linear-gradient(135deg, #135464 0%, #0b7f81 100%);
|
||||
border-color: #135464;
|
||||
}
|
||||
|
||||
.btn-outline-dark {
|
||||
color: var(--accent-strong);
|
||||
border-color: rgba(22, 105, 122, 0.28);
|
||||
}
|
||||
|
||||
.btn-outline-dark:hover,
|
||||
.btn-outline-dark:focus,
|
||||
.btn-outline-dark:active {
|
||||
color: #fff;
|
||||
background: var(--accent-strong);
|
||||
border-color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.admin-sidebar-link.active {
|
||||
color: var(--accent-strong);
|
||||
border-color: rgba(15, 139, 141, 0.24);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #ebf8f8 100%);
|
||||
box-shadow: inset 0 0 0 1px rgba(15, 139, 141, 0.08);
|
||||
}
|
||||
|
||||
.admin-sidebar-link.active .admin-sidebar-link-icon {
|
||||
border-color: rgba(15, 139, 141, 0.18);
|
||||
background: rgba(15, 139, 141, 0.12);
|
||||
}
|
||||
|
||||
.admin-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.admin-card-grid-triple {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-card-grid-secondary {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.admin-link-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.admin-summary-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.95rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.admin-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1 1 32rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-search-form .form-control {
|
||||
flex: 1 1 16rem;
|
||||
}
|
||||
|
||||
.admin-directory-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 0.95fr);
|
||||
gap: 1.25rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.admin-table-card,
|
||||
.admin-form-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-table-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-table-actions form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-count-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent-strong);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:hover {
|
||||
background: rgba(15, 139, 141, 0.04);
|
||||
}
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.admin-card-grid,
|
||||
.admin-directory-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.admin-summary-row,
|
||||
.admin-toolbar,
|
||||
.admin-search-form,
|
||||
.admin-table-actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
html[dir="rtl"] .admin-summary-row,
|
||||
html[dir="rtl"] .admin-toolbar,
|
||||
html[dir="rtl"] .admin-table-actions {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
|
||||
.hospital-hero-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hospital-brand-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(225,244,243,0.9) 100%);
|
||||
min-width: min(100%, 24rem);
|
||||
}
|
||||
|
||||
.hospital-brand-mark,
|
||||
.hospital-preview-logo {
|
||||
width: 4.5rem;
|
||||
height: 4.5rem;
|
||||
border-radius: 1.1rem;
|
||||
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 12px 26px rgba(15, 139, 141, 0.22);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hospital-brand-mark img,
|
||||
.hospital-preview-logo img,
|
||||
.hospital-asset-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.hospital-color-pills {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hospital-color-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.75rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.hospital-color-swatch {
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.hospital-profile-layout {
|
||||
grid-template-columns: minmax(0, 1.45fr) minmax(300px, 0.95fr);
|
||||
}
|
||||
|
||||
.hospital-preview-card,
|
||||
.hospital-overview-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hospital-preview-stack {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hospital-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.hospital-detail-list {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.hospital-detail-list div {
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hospital-detail-list dt {
|
||||
margin-bottom: 0.25rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hospital-detail-list dd {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.hospital-asset-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hospital-asset-box {
|
||||
padding: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.hospital-asset-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
margin-top: 0.9rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hospital-asset-favicon {
|
||||
max-width: 6rem;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.admin-card-grid-triple,
|
||||
.hospital-profile-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.hospital-brand-preview,
|
||||
.hospital-preview-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hospital-asset-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.brand-subtext {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
html[dir="rtl"] .hospital-brand-preview,
|
||||
html[dir="rtl"] .hospital-preview-header {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .hospital-detail-list dt,
|
||||
html[dir="rtl"] .hospital-detail-list dd {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -36,23 +36,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const locale = document.body.dataset.locale || 'en';
|
||||
const liveClock = document.querySelector('.js-live-clock');
|
||||
if (liveClock) {
|
||||
const renderClock = () => {
|
||||
const now = new Date();
|
||||
liveClock.textContent = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
liveClock.textContent = now.toLocaleTimeString(locale === 'ar' ? 'ar-SA' : 'en-US', { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
renderClock();
|
||||
window.setInterval(renderClock, 1000 * 30);
|
||||
}
|
||||
|
||||
const page = document.body.dataset.page;
|
||||
if (page === 'display') {
|
||||
if (document.body.dataset.page === 'display') {
|
||||
const fullscreenButton = document.querySelector('.js-fullscreen-toggle');
|
||||
const syncFullscreenButton = () => {
|
||||
if (!fullscreenButton) return;
|
||||
const isFullscreen = !!document.fullscreenElement;
|
||||
fullscreenButton.textContent = isFullscreen ? 'Exit full display / إنهاء العرض الكامل' : 'Full display / عرض كامل';
|
||||
fullscreenButton.textContent = isFullscreen
|
||||
? (fullscreenButton.dataset.labelExit || 'Exit full display')
|
||||
: (fullscreenButton.dataset.labelEnter || 'Full display');
|
||||
fullscreenButton.setAttribute('aria-pressed', isFullscreen ? 'true' : 'false');
|
||||
};
|
||||
|
||||
@ -78,22 +80,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const latest = cards[0];
|
||||
if (latest && 'speechSynthesis' in window) {
|
||||
const announcementKey = latest.dataset.announcementKey || '';
|
||||
const storedKey = window.localStorage.getItem('hospitalQueue:lastAnnouncement') || '';
|
||||
const storageKey = `hospitalQueue:lastAnnouncement:${locale}`;
|
||||
const storedKey = window.localStorage.getItem(storageKey) || '';
|
||||
if (announcementKey && announcementKey !== storedKey) {
|
||||
const speakText = (text, lang) => {
|
||||
if (!text) return;
|
||||
const text = locale === 'ar' ? (latest.dataset.announcementAr || '') : (latest.dataset.announcementEn || '');
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = lang;
|
||||
utterance.lang = locale === 'ar' ? 'ar-SA' : 'en-US';
|
||||
const voices = window.speechSynthesis.getVoices();
|
||||
const preferredVoice = voices.find((voice) => voice.lang.toLowerCase().startsWith(lang.slice(0, 2)));
|
||||
const preferredVoice = voices.find((voice) => voice.lang.toLowerCase().startsWith(locale === 'ar' ? 'ar' : 'en'));
|
||||
if (preferredVoice) utterance.voice = preferredVoice;
|
||||
window.speechSynthesis.speak(utterance);
|
||||
};
|
||||
|
||||
window.speechSynthesis.cancel();
|
||||
speakText(latest.dataset.announcementEn || '', 'en-US');
|
||||
window.setTimeout(() => speakText(latest.dataset.announcementAr || '', 'ar-SA'), 1750);
|
||||
window.localStorage.setItem('hospitalQueue:lastAnnouncement', announcementKey);
|
||||
if (text) {
|
||||
window.speechSynthesis.speak(utterance);
|
||||
window.localStorage.setItem(storageKey, announcementKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
display.php
53
display.php
@ -6,19 +6,23 @@ qh_boot();
|
||||
$activeCalls = qh_fetch_tickets(['called', 'in_progress'], null, 6);
|
||||
$queueOverview = qh_queue_overview();
|
||||
|
||||
qh_page_start('display', 'General display board', 'Public hospital queue display with bilingual callouts, text-to-speech, and an ads pane.');
|
||||
qh_page_start(
|
||||
'display',
|
||||
qh_t('General display board', 'لوحة العرض العامة'),
|
||||
qh_t('Public queue display with separated English and Arabic modes.', 'شاشة طوابير عامة مع وضعي عرض منفصلين بالعربية والإنجليزية.')
|
||||
);
|
||||
?>
|
||||
<div class="container-fluid px-3 px-lg-4 py-2" data-auto-refresh="20">
|
||||
<section class="display-shell">
|
||||
<div class="display-main panel-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-4">
|
||||
<div>
|
||||
<div class="section-kicker">General display / الشاشة العامة</div>
|
||||
<h1 class="section-title-xl mt-2 mb-1">Now serving</h1>
|
||||
<p class="section-copy mb-0">Latest calls are read aloud in English and Arabic when supported by the browser.</p>
|
||||
<div class="section-kicker"><?= qh_h(qh_t('General display', 'الشاشة العامة')) ?></div>
|
||||
<h1 class="section-title-xl mt-2 mb-1"><?= qh_h(qh_t('Now serving', 'يتم الآن النداء')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('This page shows a single language view. Use the language switch in the main app to open the English or Arabic display mode.', 'تعرض هذه الصفحة لغة واحدة فقط. استخدم مبدل اللغة في التطبيق لفتح وضع العرض العربي أو الإنجليزي.')) ?></p>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap justify-content-end">
|
||||
<button type="button" class="btn btn-dark btn-sm js-fullscreen-toggle" aria-pressed="false">Full display / عرض كامل</button>
|
||||
<button type="button" class="btn btn-dark btn-sm js-fullscreen-toggle" aria-pressed="false" data-label-enter="<?= qh_h(qh_t('Full display', 'عرض كامل')) ?>" data-label-exit="<?= qh_h(qh_t('Exit full display', 'إنهاء العرض الكامل')) ?>"><?= qh_h(qh_t('Full display', 'عرض كامل')) ?></button>
|
||||
<div class="live-clock js-live-clock"><?= qh_h(date('H:i')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,7 +33,7 @@ qh_page_start('display', 'General display board', 'Public hospital queue display
|
||||
<article class="announcement-card" data-announcement-key="<?= qh_h((string) $ticket['id']) ?>-<?= qh_h((string) strtotime((string) $ticket['called_at'])) ?>" data-announcement-en="<?= qh_h($speech['en']) ?>" data-announcement-ar="<?= qh_h($speech['ar']) ?>">
|
||||
<div>
|
||||
<div class="ticket-number large"><?= qh_h($ticket['ticket_number']) ?></div>
|
||||
<div class="display-meta"><?= qh_h($ticket['doctor_name_en'] ?? 'Doctor') ?> · Room <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
<div class="display-meta"><?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<?= qh_status_badge($ticket['status']) ?>
|
||||
@ -40,22 +44,21 @@ qh_page_start('display', 'General display board', 'Public hospital queue display
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state display-empty">
|
||||
<strong>No live calls right now.</strong>
|
||||
<span>When a doctor presses “Call patient”, the ticket will appear here and play on TTS.</span>
|
||||
<strong><?= qh_h(qh_t('No live calls right now.', 'لا توجد نداءات مباشرة حالياً.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('When a doctor presses “Call patient”, the ticket will appear here.', 'عندما يضغط الطبيب على زر نداء المريض ستظهر التذكرة هنا.')) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel-subsection mt-4">
|
||||
<h2 class="section-title mb-3">Queue by clinic / الطابور حسب العيادة</h2>
|
||||
<h2 class="section-title mb-3"><?= qh_h(qh_t('Queue by clinic', 'الطابور حسب العيادة')) ?></h2>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($queueOverview as $row): ?>
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="mini-overview-card">
|
||||
<div class="fw-semibold"><?= qh_h($row['name_en']) ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($row['name_ar']) ?></div>
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($row)) ?></div>
|
||||
<div class="d-flex gap-2 mt-3 flex-wrap">
|
||||
<span class="table-pill warning">Vitals <?= qh_h((string) $row['vitals_waiting']) ?></span>
|
||||
<span class="table-pill info">Doctor <?= qh_h((string) $row['doctor_waiting']) ?></span>
|
||||
<span class="table-pill warning"><?= qh_h(qh_t('Vitals', 'العلامات')) ?> <?= qh_h((string) $row['vitals_waiting']) ?></span>
|
||||
<span class="table-pill info"><?= qh_h(qh_t('Doctor', 'الطبيب')) ?> <?= qh_h((string) $row['doctor_waiting']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,26 +70,26 @@ qh_page_start('display', 'General display board', 'Public hospital queue display
|
||||
<aside class="display-ads panel-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<div class="section-kicker">Ads & notices / الإعلانات</div>
|
||||
<h2 class="section-title mb-1">Patient information</h2>
|
||||
<div class="section-kicker"><?= qh_h(qh_t('Ads and notices', 'الإعلانات والتنبيهات')) ?></div>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Patient information', 'معلومات المرضى')) ?></h2>
|
||||
</div>
|
||||
<span class="badge rounded-pill text-bg-dark">Looping</span>
|
||||
<span class="badge rounded-pill text-bg-dark"><?= qh_h(qh_t('Looping', 'متكرر')) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="ad-card mb-3">
|
||||
<div class="ad-tag">Service</div>
|
||||
<h3>Lab packages & wellness checks</h3>
|
||||
<p>Ask reception about bundled blood tests, diabetes follow-up, and annual screenings.</p>
|
||||
<div class="ad-tag"><?= qh_h(qh_t('Service', 'خدمة')) ?></div>
|
||||
<h3><?= qh_h(qh_t('Lab packages and wellness checks', 'باقات المختبر والفحوصات الوقائية')) ?></h3>
|
||||
<p><?= qh_h(qh_t('Ask reception about bundled blood tests, diabetes follow-up, and annual screenings.', 'اسأل الاستقبال عن باقات تحاليل الدم، ومتابعة السكري، والفحوصات السنوية.')) ?></p>
|
||||
</div>
|
||||
<div class="ad-card mb-3">
|
||||
<div class="ad-tag">Reminder</div>
|
||||
<h3>Keep your ticket visible</h3>
|
||||
<p>We announce ticket numbers on this screen and by voice. Stay near your department area.</p>
|
||||
<div class="ad-tag"><?= qh_h(qh_t('Reminder', 'تذكير')) ?></div>
|
||||
<h3><?= qh_h(qh_t('Keep your ticket visible', 'احتفظ بتذكرتك ظاهرة')) ?></h3>
|
||||
<p><?= qh_h(qh_t('We announce ticket numbers on this screen and by voice. Stay near your department area.', 'نعلن أرقام التذاكر على هذه الشاشة وبالصوت. يرجى البقاء قرب منطقة القسم الخاص بك.')) ?></p>
|
||||
</div>
|
||||
<div class="ad-card">
|
||||
<div class="ad-tag">Wayfinding</div>
|
||||
<h3>Pharmacy & billing</h3>
|
||||
<p>Completed visits can proceed to the pharmacy and billing desk near the main exit.</p>
|
||||
<div class="ad-tag"><?= qh_h(qh_t('Wayfinding', 'الإرشاد')) ?></div>
|
||||
<h3><?= qh_h(qh_t('Pharmacy and billing', 'الصيدلية والمحاسبة')) ?></h3>
|
||||
<p><?= qh_h(qh_t('Completed visits can proceed to the pharmacy and billing desk near the main exit.', 'بعد انتهاء الزيارة يمكن التوجه إلى الصيدلية ومكتب المحاسبة قرب المخرج الرئيسي.')) ?></p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
104
doctor.php
104
doctor.php
@ -5,104 +5,92 @@ qh_boot();
|
||||
qh_doctor_handle_request();
|
||||
|
||||
$doctors = qh_fetch_doctors();
|
||||
$selectedDoctorId = isset($_GET['doctor_id']) ? (int) $_GET['doctor_id'] : ($doctors[0]['id'] ?? 0);
|
||||
$selectedDoctor = $selectedDoctorId ? qh_fetch_doctor($selectedDoctorId) : null;
|
||||
$doctorQueue = $selectedDoctorId ? qh_fetch_tickets(['ready_for_doctor', 'called', 'in_progress'], $selectedDoctorId, 20) : [];
|
||||
$selectedDoctorId = isset($_GET['doctor_id']) ? (int) $_GET['doctor_id'] : (int) ($doctors[0]['id'] ?? 0);
|
||||
$selectedDoctor = $selectedDoctorId > 0 ? qh_fetch_doctor($selectedDoctorId) : null;
|
||||
$doctorQueue = $selectedDoctorId > 0 ? qh_fetch_tickets(['ready_for_doctor', 'called', 'in_progress'], $selectedDoctorId, 20) : [];
|
||||
|
||||
qh_page_start('doctor', 'Doctor room control', 'Doctor queue page for calling patients, starting visits, and closing tickets.');
|
||||
qh_page_start(
|
||||
'doctor',
|
||||
qh_t('Doctor room queue', 'طابور غرفة الطبيب'),
|
||||
qh_t('Doctor workspace with separate English and Arabic page views.', 'مساحة عمل الطبيب مع صفحات عربية وإنجليزية منفصلة.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<section class="page-header-panel mb-4">
|
||||
<div>
|
||||
<span class="section-kicker">Doctor / الطبيب</span>
|
||||
<h1 class="section-title-xl mt-2">Call the next patient and update status.</h1>
|
||||
<p class="section-copy mb-0">The display page announces called tickets in English and Arabic using the browser’s text-to-speech engine.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Doctor', 'الطبيب')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2"><?= qh_h(qh_t('Call the next patient and manage room flow.', 'نادِ المريض التالي وأدر حركة الغرفة.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Select a doctor room, then call, start, complete, or mark no-show for the assigned patients.', 'اختر غرفة الطبيب ثم قم بنداء المرضى المخصصين أو بدء الزيارة أو إنهائها أو تسجيل عدم الحضور.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="panel-card mb-4">
|
||||
<form method="get" class="row g-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label">Doctor room / غرفة الطبيب</label>
|
||||
<div class="row g-4">
|
||||
<div class="col-xl-4">
|
||||
<div class="panel-card h-100">
|
||||
<label class="form-label"><?= qh_h(qh_t('Doctor room', 'غرفة الطبيب')) ?></label>
|
||||
<form method="get" class="vstack gap-3">
|
||||
<input type="hidden" name="lang" value="<?= qh_h(qh_locale()) ?>">
|
||||
<select class="form-select" name="doctor_id" onchange="this.form.submit()">
|
||||
<?php foreach ($doctors as $doctor): ?>
|
||||
<option value="<?= qh_h((string) $doctor['id']) ?>" <?= (int) $doctor['id'] === $selectedDoctorId ? 'selected' : '' ?>><?= qh_h($doctor['name_en']) ?> · <?= qh_h($doctor['clinic_name_en'] ?? '') ?> · Room <?= qh_h($doctor['room_number']) ?></option>
|
||||
<option value="<?= qh_h((string) $doctor['id']) ?>" <?= (int) $doctor['id'] === $selectedDoctorId ? 'selected' : '' ?>><?= qh_h(qh_name($doctor)) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($doctor['room_number']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($selectedDoctor): ?>
|
||||
<div class="col-lg-7">
|
||||
<div class="doctor-spotlight">
|
||||
<div>
|
||||
<div class="fw-semibold"><?= qh_h($selectedDoctor['name_en']) ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($selectedDoctor['name_ar']) ?></div>
|
||||
</div>
|
||||
<span class="room-badge">Room <?= qh_h($selectedDoctor['room_number']) ?></span>
|
||||
</div>
|
||||
<div class="mini-overview-card">
|
||||
<div class="fw-semibold"><?= qh_h(qh_name($selectedDoctor)) ?></div>
|
||||
<div class="small text-secondary mt-1"><?= qh_h(qh_t('Room', 'الغرفة')) ?> <?= qh_h($selectedDoctor['room_number'] ?? '--') ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('display.php')) ?>" target="_blank" rel="noopener"><?= qh_h(qh_t('Preview public display', 'معاينة الشاشة العامة')) ?></a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-card">
|
||||
<div class="col-xl-8">
|
||||
<div class="panel-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Doctor queue / طابور الطبيب</h2>
|
||||
<p class="section-copy mb-0">Ready patients, live calls, and in-room consultations for the selected doctor.</p>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Doctor queue', 'طابور الطبيب')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Patients currently assigned to this room.', 'المرضى المخصصون حالياً لهذه الغرفة.')) ?></p>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-dark" href="display.php" target="_blank" rel="noopener">Preview public display</a>
|
||||
<?php if ($selectedDoctor): ?>
|
||||
<span class="room-badge"><?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($selectedDoctor['room_number'] ?? '--') ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($doctorQueue): ?>
|
||||
<div class="vstack gap-3">
|
||||
<?php foreach ($doctorQueue as $ticket): ?>
|
||||
<div class="queue-row">
|
||||
<div class="queue-row-head">
|
||||
<article class="list-row-form">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
|
||||
<div>
|
||||
<div class="ticket-code"><?= qh_h($ticket['ticket_number']) ?></div>
|
||||
<div class="fw-semibold"><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="small text-secondary">Vitals: <?= qh_h($ticket['vitals_notes'] ?: 'Not recorded / غير مسجل') ?></div>
|
||||
<div class="fw-semibold mt-1"><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="small text-secondary mt-1"><?= qh_h(qh_name($ticket, 'clinic_name')) ?> · <?= qh_h(qh_t('Created', 'أُنشئت')) ?> <?= qh_format_datetime($ticket['created_at']) ?></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-end gap-2">
|
||||
<div class="d-flex gap-2 flex-wrap align-items-center">
|
||||
<?= qh_status_badge($ticket['status']) ?>
|
||||
<a class="btn btn-sm btn-outline-dark" href="ticket.php?id=<?= qh_h((string) $ticket['id']) ?>">Ticket detail</a>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('ticket.php', ['id' => (int) $ticket['id']])) ?>"><?= qh_h(qh_t('Ticket detail', 'تفاصيل التذكرة')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<form method="post">
|
||||
<input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>">
|
||||
<input type="hidden" name="action" value="call_ticket">
|
||||
<button class="btn btn-dark btn-sm" type="submit">Call patient</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>">
|
||||
<input type="hidden" name="action" value="start_visit">
|
||||
<button class="btn btn-outline-dark btn-sm" type="submit">Start visit</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>">
|
||||
<input type="hidden" name="action" value="complete_ticket">
|
||||
<button class="btn btn-outline-success btn-sm" type="submit">Mark done</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>">
|
||||
<input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>">
|
||||
<input type="hidden" name="action" value="mark_no_show">
|
||||
<button class="btn btn-outline-danger btn-sm" type="submit">No-show</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post"><input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>"><input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>"><input type="hidden" name="action" value="call_ticket"><button class="btn btn-dark btn-sm" type="submit"><?= qh_h(qh_t('Call patient', 'نداء المريض')) ?></button></form>
|
||||
<form method="post"><input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>"><input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>"><input type="hidden" name="action" value="start_visit"><button class="btn btn-outline-dark btn-sm" type="submit"><?= qh_h(qh_t('Start visit', 'بدء الزيارة')) ?></button></form>
|
||||
<form method="post"><input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>"><input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>"><input type="hidden" name="action" value="complete_ticket"><button class="btn btn-outline-success btn-sm" type="submit"><?= qh_h(qh_t('Mark done', 'إنهاء الزيارة')) ?></button></form>
|
||||
<form method="post"><input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>"><input type="hidden" name="doctor_id" value="<?= qh_h((string) $selectedDoctorId) ?>"><input type="hidden" name="action" value="mark_no_show"><button class="btn btn-outline-danger btn-sm" type="submit"><?= qh_h(qh_t('No-show', 'عدم حضور')) ?></button></form>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<strong>No patients in this doctor queue.</strong>
|
||||
<span>Issue a ticket at reception or complete vitals in nursing to fill this room.</span>
|
||||
<strong><?= qh_h(qh_t('No patients are in this doctor queue.', 'لا يوجد مرضى في طابور هذا الطبيب.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('Issue a ticket at reception or complete vitals in nursing to fill this room.', 'أصدر تذكرة من الاستقبال أو أكمل العلامات الحيوية في التمريض لإضافة مرضى لهذه الغرفة.')) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php qh_page_end(); ?>
|
||||
|
||||
159
index.php
159
index.php
@ -8,48 +8,33 @@ $overview = qh_queue_overview();
|
||||
$recentTickets = qh_fetch_tickets(['waiting_vitals', 'ready_for_doctor', 'called', 'in_progress'], null, 8);
|
||||
$calledTickets = qh_fetch_tickets(['called', 'in_progress'], null, 4);
|
||||
|
||||
qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue dashboard with queue status, staff entry points, and public display controls.');
|
||||
qh_page_start(
|
||||
'home',
|
||||
qh_t('Hospital queue operations', 'عمليات طابور المستشفى'),
|
||||
qh_t('English operations dashboard for the hospital queue workflow.', 'لوحة عمليات عربية لمسار طابور المستشفى.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<section class="hero-panel mb-4 mb-lg-5">
|
||||
<div class="row g-4 align-items-center">
|
||||
<div class="col-lg-7">
|
||||
<span class="section-kicker">Hospital queue system / نظام الطوابير</span>
|
||||
<h1 class="display-title mt-3 mb-3">One bilingual workflow for reception, nursing, doctors, and the public screen.</h1>
|
||||
<p class="lead text-secondary mb-4">Issue one ticket, route it through optional vitals, call patients to the right room, and announce the latest calls with browser text-to-speech on the general display.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Hospital queue system', 'نظام طوابير المستشفى')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="display-title mt-3 mb-3"><?= qh_h(qh_t('One clear workflow for reception, nursing, doctors, and the public display.', 'مسار واضح للاستقبال والتمريض والأطباء والشاشة العامة.')) ?></h1>
|
||||
<p class="lead text-secondary mb-4"><?= qh_h(qh_t('Track each patient with one ticket, route them to vitals only when needed, and keep staff and patients aligned in real time.', 'تابع كل مريض بتذكرة واحدة، ووجّهه إلى العلامات الحيوية عند الحاجة فقط، وحافظ على تنسيق العمل بين الطاقم والمرضى لحظياً.')) ?></p>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-dark" href="reception.php">Issue ticket / إصدار تذكرة</a>
|
||||
<a class="btn btn-outline-dark" href="display.php">Open TV display / فتح الشاشة العامة</a>
|
||||
<a class="btn btn-dark" href="<?= qh_h(qh_url('reception.php')) ?>"><?= qh_h(qh_t('Issue ticket', 'إصدار تذكرة')) ?></a>
|
||||
<a class="btn btn-outline-dark" href="<?= qh_h(qh_url('display.php')) ?>"><?= qh_h(qh_t('Open public display', 'فتح الشاشة العامة')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="hero-card stack-card h-100">
|
||||
<div class="small text-uppercase text-secondary fw-semibold mb-2">Today / اليوم</div>
|
||||
<div class="small text-uppercase text-secondary fw-semibold mb-2"><?= qh_h(qh_t('Today', 'اليوم')) ?></div>
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value"><?= qh_h((string) $stats['issued_today']) ?></div>
|
||||
<div class="metric-label">Issued / المُصدرة</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value"><?= qh_h((string) $stats['waiting_vitals']) ?></div>
|
||||
<div class="metric-label">Vitals / الحيوية</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value"><?= qh_h((string) $stats['ready_for_doctor']) ?></div>
|
||||
<div class="metric-label">Ready / جاهز</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value"><?= qh_h((string) $stats['active_rooms']) ?></div>
|
||||
<div class="metric-label">Active rooms / الغرف النشطة</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6"><div class="metric-card"><div class="metric-value"><?= qh_h((string) $stats['issued_today']) ?></div><div class="metric-label"><?= qh_h(qh_t('Issued tickets', 'التذاكر الصادرة')) ?></div></div></div>
|
||||
<div class="col-6"><div class="metric-card"><div class="metric-value"><?= qh_h((string) $stats['waiting_vitals']) ?></div><div class="metric-label"><?= qh_h(qh_t('Waiting vitals', 'بانتظار العلامات')) ?></div></div></div>
|
||||
<div class="col-6"><div class="metric-card"><div class="metric-value"><?= qh_h((string) $stats['ready_for_doctor']) ?></div><div class="metric-label"><?= qh_h(qh_t('Ready for doctor', 'جاهز للطبيب')) ?></div></div></div>
|
||||
<div class="col-6"><div class="metric-card"><div class="metric-value"><?= qh_h((string) $stats['active_rooms']) ?></div><div class="metric-label"><?= qh_h(qh_t('Active rooms', 'الغرف النشطة')) ?></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -61,29 +46,26 @@ qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue das
|
||||
<div class="panel-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Live queue overview / نظرة مباشرة على الطابور</h2>
|
||||
<p class="section-copy mb-0">Clinic-level demand for vitals, doctor readiness, and active calls.</p>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Live queue overview', 'نظرة مباشرة على الطابور')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('See clinic demand, waiting load, and active calls at a glance.', 'اطلع بسرعة على ضغط العيادات والانتظار والنداءات النشطة.')) ?></p>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-dark" href="admin.php">Manage clinics & doctors</a>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('admin.php')) ?>"><?= qh_h(qh_t('Manage clinics and doctors', 'إدارة العيادات والأطباء')) ?></a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Clinic / العيادة</th>
|
||||
<th>Vitals wait</th>
|
||||
<th>Doctor wait</th>
|
||||
<th>Active calls</th>
|
||||
<th>Total today</th>
|
||||
<th><?= qh_h(qh_t('Clinic', 'العيادة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Vitals wait', 'انتظار العلامات')) ?></th>
|
||||
<th><?= qh_h(qh_t('Doctor wait', 'انتظار الطبيب')) ?></th>
|
||||
<th><?= qh_h(qh_t('Active calls', 'النداءات النشطة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Today total', 'إجمالي اليوم')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($overview as $row): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= qh_h($row['name_en']) ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($row['name_ar']) ?></div>
|
||||
</td>
|
||||
<td class="fw-semibold"><?= qh_h(qh_name($row)) ?></td>
|
||||
<td><span class="table-pill warning"><?= qh_h((string) $row['vitals_waiting']) ?></span></td>
|
||||
<td><span class="table-pill info"><?= qh_h((string) $row['doctor_waiting']) ?></span></td>
|
||||
<td><span class="table-pill dark"><?= qh_h((string) $row['active_calls']) ?></span></td>
|
||||
@ -97,19 +79,15 @@ qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue das
|
||||
</div>
|
||||
<div class="col-xl-4">
|
||||
<div class="panel-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Current calls / النداءات الحالية</h2>
|
||||
<p class="section-copy mb-0">Patients already called to a doctor room.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Current calls', 'النداءات الحالية')) ?></h2>
|
||||
<p class="section-copy mb-3"><?= qh_h(qh_t('Patients already called into active doctor rooms.', 'المرضى الذين تم استدعاؤهم بالفعل إلى غرف الأطباء.')) ?></p>
|
||||
<?php if ($calledTickets): ?>
|
||||
<div class="vstack gap-3">
|
||||
<?php foreach ($calledTickets as $ticket): ?>
|
||||
<div class="call-strip">
|
||||
<div>
|
||||
<div class="ticket-code"><?= qh_h($ticket['ticket_number']) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h($ticket['doctor_name_en'] ?? 'Unassigned') ?> · Room <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
<div class="small text-secondary"><?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Unassigned', 'غير محدد'))) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
<?= qh_status_badge($ticket['status']) ?>
|
||||
</div>
|
||||
@ -117,8 +95,8 @@ qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue das
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state compact">
|
||||
<strong>No active calls yet.</strong>
|
||||
<span>Use the doctor page to call the next patient.</span>
|
||||
<strong><?= qh_h(qh_t('No active calls yet.', 'لا توجد نداءات نشطة حالياً.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('Use the doctor page to call the next patient.', 'استخدم صفحة الطبيب لنداء المريض التالي.')) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -126,54 +104,43 @@ qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue das
|
||||
</section>
|
||||
|
||||
<section class="row g-4 mb-4 mb-lg-5">
|
||||
<?php
|
||||
$workflow = [
|
||||
['step' => '01', 'title' => qh_t('Reception issues one ticket', 'الاستقبال يصدر تذكرة واحدة'), 'copy' => qh_t('Choose the clinic and doctor once at the front desk.', 'يتم اختيار العيادة والطبيب مرة واحدة من مكتب الاستقبال.')],
|
||||
['step' => '02', 'title' => qh_t('Optional vitals step', 'خطوة العلامات الحيوية عند الحاجة'), 'copy' => qh_t('Only clinics that require vitals route through nursing first.', 'فقط العيادات التي تتطلب العلامات الحيوية تمر أولاً على التمريض.')],
|
||||
['step' => '03', 'title' => qh_t('Doctor calls the patient', 'الطبيب ينادي المريض'), 'copy' => qh_t('The doctor room page pushes the next call to the display.', 'صفحة غرفة الطبيب ترسل النداء التالي إلى الشاشة.')],
|
||||
['step' => '04', 'title' => qh_t('Display announces the call', 'الشاشة تعلن النداء'), 'copy' => qh_t('Patients see the latest ticket and room assignment immediately.', 'يرى المرضى آخر تذكرة ورقم الغرفة مباشرة.')],
|
||||
];
|
||||
?>
|
||||
<?php foreach ($workflow as $card): ?>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<a class="workflow-card h-100" href="admin.php">
|
||||
<span class="workflow-step">01</span>
|
||||
<h3>Admin config</h3>
|
||||
<p>Manage clinics, vitals requirement, doctors, and room assignments.</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<a class="workflow-card h-100" href="reception.php">
|
||||
<span class="workflow-step">02</span>
|
||||
<h3>Reception issue</h3>
|
||||
<p>Create a single bilingual ticket and route it to vitals or directly to the doctor.</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<a class="workflow-card h-100" href="nursing.php">
|
||||
<span class="workflow-step">03</span>
|
||||
<h3>Nursing handoff</h3>
|
||||
<p>Capture vitals, note key measurements, and release the patient to the doctor queue.</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<a class="workflow-card h-100" href="doctor.php">
|
||||
<span class="workflow-step">04</span>
|
||||
<h3>Doctor call</h3>
|
||||
<p>Call, start, and complete visits while the TV display announces the latest ticket.</p>
|
||||
</a>
|
||||
<article class="panel-card workflow-card h-100">
|
||||
<span class="workflow-step"><?= qh_h($card['step']) ?></span>
|
||||
<h3><?= qh_h($card['title']) ?></h3>
|
||||
<p><?= qh_h($card['copy']) ?></p>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<section class="panel-card">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Recent patient flow / آخر حركة للمرضى</h2>
|
||||
<p class="section-copy mb-0">Each ticket is linked to a detail page showing its current stage.</p>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Recent patient flow', 'آخر حركة للمرضى')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('The newest in-progress tickets across the workflow.', 'أحدث التذاكر النشطة عبر مسار العمل.')) ?></p>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-dark" href="reception.php">Create new ticket</a>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('reception.php')) ?>"><?= qh_h(qh_t('Create new ticket', 'إنشاء تذكرة جديدة')) ?></a>
|
||||
</div>
|
||||
<?php if ($recentTickets): ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Patient</th>
|
||||
<th>Clinic</th>
|
||||
<th>Doctor / Room</th>
|
||||
<th>Status</th>
|
||||
<th><?= qh_h(qh_t('Ticket', 'التذكرة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Patient', 'المريض')) ?></th>
|
||||
<th><?= qh_h(qh_t('Clinic', 'العيادة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Doctor', 'الطبيب')) ?></th>
|
||||
<th><?= qh_h(qh_t('Status', 'الحالة')) ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -181,26 +148,20 @@ qh_page_start('home', 'Hospital queue operations', 'Bilingual hospital queue das
|
||||
<?php foreach ($recentTickets as $ticket): ?>
|
||||
<tr>
|
||||
<td class="fw-semibold"><?= qh_h($ticket['ticket_number']) ?></td>
|
||||
<td>
|
||||
<div><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="small text-secondary"><?= strtoupper(qh_h($ticket['language_pref'])) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><?= qh_h($ticket['clinic_name_en'] ?? '—') ?></div>
|
||||
<div class="small text-secondary" lang="ar" dir="rtl"><?= qh_h($ticket['clinic_name_ar'] ?? '') ?></div>
|
||||
</td>
|
||||
<td><?= qh_h($ticket['doctor_name_en'] ?? '—') ?> <span class="text-secondary">· <?= qh_h($ticket['doctor_room'] ?? '--') ?></span></td>
|
||||
<td><?= qh_h($ticket['patient_name']) ?></td>
|
||||
<td><?= qh_h(qh_name($ticket, 'clinic_name')) ?></td>
|
||||
<td><?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Unassigned', 'غير محدد'))) ?></td>
|
||||
<td><?= qh_status_badge($ticket['status']) ?></td>
|
||||
<td class="text-end"><a class="btn btn-sm btn-outline-dark" href="ticket.php?id=<?= qh_h((string) $ticket['id']) ?>">View detail</a></td>
|
||||
<td class="text-end"><a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('ticket.php', ['id' => (int) $ticket['id']])) ?>"><?= qh_h(qh_t('View detail', 'عرض التفاصيل')) ?></a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<strong>No active tickets yet.</strong>
|
||||
<span>Start from the reception desk to issue the first patient ticket.</span>
|
||||
<div class="empty-state compact">
|
||||
<strong><?= qh_h(qh_t('No recent tickets yet.', 'لا توجد تذاكر حديثة حتى الآن.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('Start from reception to populate the workflow.', 'ابدأ من صفحة الاستقبال لبدء تعبئة مسار العمل.')) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
46
nursing.php
46
nursing.php
@ -5,58 +5,64 @@ qh_boot();
|
||||
qh_nursing_handle_request();
|
||||
|
||||
$waitingTickets = qh_fetch_tickets(['waiting_vitals'], null, 20);
|
||||
qh_page_start('nursing', 'Nursing vitals queue', 'Nursing queue for clinics that require vitals before the doctor visit.');
|
||||
|
||||
qh_page_start(
|
||||
'nursing',
|
||||
qh_t('Nursing vitals queue', 'طابور التمريض للعلامات الحيوية'),
|
||||
qh_t('Nursing workspace with separate English and Arabic views.', 'مساحة عمل التمريض مع فصل بين الواجهة العربية والإنجليزية.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<section class="page-header-panel mb-4">
|
||||
<div>
|
||||
<span class="section-kicker">Nursing / التمريض</span>
|
||||
<h1 class="section-title-xl mt-2">Capture vitals and release to doctor.</h1>
|
||||
<p class="section-copy mb-0">Only tickets from clinics marked “requires vitals” arrive here. Once notes are saved, the same ticket continues to the doctor queue.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Nursing', 'التمريض')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2"><?= qh_h(qh_t('Capture vitals and release patients to doctors.', 'سجّل العلامات الحيوية ثم حوّل المرضى إلى الأطباء.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Only clinics configured for vitals-first appear here.', 'تظهر هنا فقط العيادات التي تم إعدادها لتبدأ بالعلامات الحيوية.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="panel-card">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Waiting for vitals / بانتظار العلامات الحيوية</h2>
|
||||
<p class="section-copy mb-0">Add a short clinical note to transfer the patient to the assigned doctor.</p>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Waiting for vitals', 'بانتظار العلامات الحيوية')) ?></h2>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Add a short note, then send the patient to the doctor queue.', 'أضف ملاحظة قصيرة ثم أرسل المريض إلى طابور الطبيب.')) ?></p>
|
||||
</div>
|
||||
<span class="badge text-bg-warning px-3 py-2"><?= qh_h((string) count($waitingTickets)) ?> patients</span>
|
||||
<span class="badge rounded-pill text-bg-dark"><?= qh_h(count($waitingTickets) . ' ' . qh_t('patients', 'مرضى')) ?></span>
|
||||
</div>
|
||||
|
||||
<?php if ($waitingTickets): ?>
|
||||
<div class="vstack gap-3">
|
||||
<?php foreach ($waitingTickets as $ticket): ?>
|
||||
<div class="queue-row">
|
||||
<div class="queue-row-head">
|
||||
<article class="list-row-form">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
|
||||
<div>
|
||||
<div class="ticket-code"><?= qh_h($ticket['ticket_number']) ?></div>
|
||||
<div class="fw-semibold"><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="small text-secondary"><?= qh_h($ticket['clinic_name_en'] ?? '') ?> → <?= qh_h($ticket['doctor_name_en'] ?? '') ?> · Room <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
<div class="fw-semibold mt-1"><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="small text-secondary mt-1"><?= qh_h(qh_name($ticket, 'clinic_name')) ?> · <?= qh_h(qh_name($ticket, 'doctor_name')) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-end gap-2">
|
||||
<div class="d-flex gap-2 flex-wrap align-items-center">
|
||||
<?= qh_status_badge($ticket['status']) ?>
|
||||
<a class="btn btn-sm btn-outline-dark" href="ticket.php?id=<?= qh_h((string) $ticket['id']) ?>">Ticket detail</a>
|
||||
<a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('ticket.php', ['id' => (int) $ticket['id']])) ?>"><?= qh_h(qh_t('Ticket detail', 'تفاصيل التذكرة')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="row g-3 align-items-end mt-2">
|
||||
<form method="post" class="row g-3 align-items-end mt-1">
|
||||
<input type="hidden" name="ticket_id" value="<?= qh_h((string) $ticket['id']) ?>">
|
||||
<div class="col-lg-9">
|
||||
<label class="form-label">Vitals note / ملاحظة العلامات الحيوية</label>
|
||||
<input class="form-control" type="text" name="vitals_notes" placeholder="BP 120/80 · Temp 36.8 · Weight 68kg" required>
|
||||
<label class="form-label"><?= qh_h(qh_t('Vitals note', 'ملاحظة العلامات الحيوية')) ?></label>
|
||||
<textarea class="form-control" name="vitals_notes" rows="2" placeholder="<?= qh_h(qh_t('Blood pressure, pulse, temperature...', 'الضغط والنبض والحرارة...')) ?>" required></textarea>
|
||||
</div>
|
||||
<div class="col-lg-3 d-grid">
|
||||
<button class="btn btn-dark" type="submit">Send to doctor</button>
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h(qh_t('Send to doctor', 'إرسال إلى الطبيب')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<strong>No patients waiting for vitals.</strong>
|
||||
<span>Tickets from vitals-required clinics will appear here automatically after issue.</span>
|
||||
<strong><?= qh_h(qh_t('No patients are waiting for vitals.', 'لا يوجد مرضى بانتظار العلامات الحيوية.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('New vitals-first tickets from reception will appear here.', 'ستظهر هنا التذاكر الجديدة التي تتطلب العلامات الحيوية أولاً.')) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@ -16,6 +16,7 @@ function qh_boot(): void
|
||||
|
||||
qh_ensure_schema();
|
||||
qh_seed_demo_data();
|
||||
qh_seed_hospital_profile();
|
||||
$booted = true;
|
||||
}
|
||||
|
||||
@ -51,6 +52,31 @@ CREATE TABLE IF NOT EXISTS hospital_queue_records (
|
||||
SQL;
|
||||
|
||||
db()->exec($sql);
|
||||
|
||||
$profileSql = <<<SQL
|
||||
CREATE TABLE IF NOT EXISTS hospital_profile_settings (
|
||||
id TINYINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
name_en VARCHAR(160) DEFAULT NULL,
|
||||
name_ar VARCHAR(160) DEFAULT NULL,
|
||||
short_name VARCHAR(40) DEFAULT NULL,
|
||||
tagline_en VARCHAR(255) DEFAULT NULL,
|
||||
tagline_ar VARCHAR(255) DEFAULT NULL,
|
||||
phone VARCHAR(60) DEFAULT NULL,
|
||||
email VARCHAR(160) DEFAULT NULL,
|
||||
website VARCHAR(255) DEFAULT NULL,
|
||||
address_en VARCHAR(255) DEFAULT NULL,
|
||||
address_ar VARCHAR(255) DEFAULT NULL,
|
||||
working_hours_en VARCHAR(255) DEFAULT NULL,
|
||||
working_hours_ar VARCHAR(255) DEFAULT NULL,
|
||||
logo_url VARCHAR(255) DEFAULT NULL,
|
||||
favicon_url VARCHAR(255) DEFAULT NULL,
|
||||
primary_color VARCHAR(7) DEFAULT NULL,
|
||||
secondary_color VARCHAR(7) DEFAULT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
SQL;
|
||||
|
||||
db()->exec($profileSql);
|
||||
}
|
||||
|
||||
function qh_seed_demo_data(): void
|
||||
@ -128,6 +154,40 @@ function qh_seed_demo_data(): void
|
||||
}
|
||||
}
|
||||
|
||||
function qh_seed_hospital_profile(): void
|
||||
{
|
||||
$exists = (int) db()->query('SELECT COUNT(*) FROM hospital_profile_settings')->fetchColumn();
|
||||
if ($exists > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO hospital_profile_settings
|
||||
(id, name_en, name_ar, short_name, tagline_en, tagline_ar, phone, email, website, address_en, address_ar, working_hours_en, working_hours_ar, logo_url, favicon_url, primary_color, secondary_color)
|
||||
VALUES
|
||||
(1, :name_en, :name_ar, :short_name, :tagline_en, :tagline_ar, :phone, :email, :website, :address_en, :address_ar, :working_hours_en, :working_hours_ar, :logo_url, :favicon_url, :primary_color, :secondary_color)'
|
||||
);
|
||||
|
||||
$stmt->execute([
|
||||
'name_en' => qh_project_name('Hospital Queue Center'),
|
||||
'name_ar' => 'مركز إدارة الطوابير',
|
||||
'short_name' => 'HQC',
|
||||
'tagline_en' => 'Organized patient flow, queue control, and staff coordination.',
|
||||
'tagline_ar' => 'تنظيم تدفق المرضى وإدارة الطوابير وتنسيق عمل الطاقم.',
|
||||
'phone' => '',
|
||||
'email' => '',
|
||||
'website' => '',
|
||||
'address_en' => '',
|
||||
'address_ar' => '',
|
||||
'working_hours_en' => 'Sun–Thu · 8:00 AM – 8:00 PM',
|
||||
'working_hours_ar' => 'الأحد – الخميس · 8:00 ص – 8:00 م',
|
||||
'logo_url' => '',
|
||||
'favicon_url' => '',
|
||||
'primary_color' => '#0f8b8d',
|
||||
'secondary_color' => '#16697a',
|
||||
]);
|
||||
}
|
||||
|
||||
function qh_h(?string $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
@ -151,29 +211,313 @@ function qh_asset_version(string $relativePath): int
|
||||
return is_file($fullPath) ? (int) filemtime($fullPath) : time();
|
||||
}
|
||||
|
||||
|
||||
function qh_locale(): string
|
||||
{
|
||||
static $locale = null;
|
||||
if ($locale !== null) {
|
||||
return $locale;
|
||||
}
|
||||
|
||||
$requested = strtolower(trim((string) ($_GET['lang'] ?? '')));
|
||||
if (in_array($requested, ['en', 'ar'], true)) {
|
||||
$_SESSION['qh_lang'] = $requested;
|
||||
}
|
||||
|
||||
$sessionLocale = strtolower(trim((string) ($_SESSION['qh_lang'] ?? 'en')));
|
||||
$locale = in_array($sessionLocale, ['en', 'ar'], true) ? $sessionLocale : 'en';
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
function qh_is_ar(): bool
|
||||
{
|
||||
return qh_locale() === 'ar';
|
||||
}
|
||||
|
||||
function qh_t(string $en, string $ar): string
|
||||
{
|
||||
return qh_is_ar() ? $ar : $en;
|
||||
}
|
||||
|
||||
function qh_locale_label(?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?: qh_locale();
|
||||
return $locale === 'ar' ? 'العربية' : 'English';
|
||||
}
|
||||
|
||||
function qh_other_locale(): string
|
||||
{
|
||||
return qh_is_ar() ? 'en' : 'ar';
|
||||
}
|
||||
|
||||
function qh_url(string $path, array $params = []): string
|
||||
{
|
||||
$target = basename($path);
|
||||
$params['lang'] = $params['lang'] ?? qh_locale();
|
||||
$query = http_build_query($params);
|
||||
return $query !== '' ? $target . '?' . $query : $target;
|
||||
}
|
||||
|
||||
function qh_switch_lang_url(string $locale): string
|
||||
{
|
||||
$params = $_GET;
|
||||
$params['lang'] = in_array($locale, ['en', 'ar'], true) ? $locale : qh_other_locale();
|
||||
$target = basename((string) ($_SERVER['PHP_SELF'] ?? 'index.php'));
|
||||
$query = http_build_query($params);
|
||||
return $query !== '' ? $target . '?' . $query : $target;
|
||||
}
|
||||
|
||||
function qh_name(array $row, string $base = 'name', string $fallback = '—'): string
|
||||
{
|
||||
$primaryKey = $base . '_' . (qh_is_ar() ? 'ar' : 'en');
|
||||
$secondaryKey = $base . '_' . (qh_is_ar() ? 'en' : 'ar');
|
||||
|
||||
$primary = trim((string) ($row[$primaryKey] ?? ''));
|
||||
if ($primary !== '') {
|
||||
return $primary;
|
||||
}
|
||||
|
||||
$secondary = trim((string) ($row[$secondaryKey] ?? ''));
|
||||
return $secondary !== '' ? $secondary : $fallback;
|
||||
}
|
||||
|
||||
function qh_fetch_hospital_profile(): array
|
||||
{
|
||||
static $profile = null;
|
||||
if ($profile !== null) {
|
||||
return $profile;
|
||||
}
|
||||
|
||||
$row = db()->query('SELECT * FROM hospital_profile_settings WHERE id = 1 LIMIT 1')->fetch();
|
||||
$profile = is_array($row) ? $row : [];
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
function qh_hospital_name(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
$name = qh_name($profile, 'name', '');
|
||||
if ($name !== '') {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$shortName = trim((string) ($profile['short_name'] ?? ''));
|
||||
return $shortName !== '' ? $shortName : qh_project_name();
|
||||
}
|
||||
|
||||
function qh_hospital_tagline(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
return qh_name($profile, 'tagline', '');
|
||||
}
|
||||
|
||||
function qh_hospital_logo_url(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
return trim((string) ($profile['logo_url'] ?? ''));
|
||||
}
|
||||
|
||||
function qh_hospital_favicon_url(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
$favicon = trim((string) ($profile['favicon_url'] ?? ''));
|
||||
if ($favicon !== '') {
|
||||
return $favicon;
|
||||
}
|
||||
|
||||
return qh_hospital_logo_url();
|
||||
}
|
||||
|
||||
function qh_hospital_contact_value(string $field): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
return trim((string) ($profile[$field] ?? ''));
|
||||
}
|
||||
|
||||
function qh_hospital_brand_initials(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
$source = trim((string) ($profile['short_name'] ?? ''));
|
||||
if ($source === '') {
|
||||
$source = trim((string) ($profile['name_en'] ?? qh_project_name('Hospital Queue')));
|
||||
}
|
||||
|
||||
$parts = preg_split('/[^\p{L}\p{N}]+/u', $source, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$initials = [];
|
||||
foreach ($parts as $part) {
|
||||
if (preg_match('/^[\p{L}\p{N}]/u', $part, $matches)) {
|
||||
$initials[] = strtoupper($matches[0]);
|
||||
}
|
||||
if (count($initials) >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $initials !== [] ? implode('', array_slice($initials, 0, 2)) : 'HQ';
|
||||
}
|
||||
|
||||
function qh_sanitize_hex_color(?string $value, string $fallback): string
|
||||
{
|
||||
$candidate = strtoupper(trim((string) $value));
|
||||
return preg_match('/^#[0-9A-F]{6}$/', $candidate) ? $candidate : $fallback;
|
||||
}
|
||||
|
||||
function qh_hospital_primary_color(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
return qh_sanitize_hex_color($profile['primary_color'] ?? null, '#0F8B8D');
|
||||
}
|
||||
|
||||
function qh_hospital_secondary_color(): string
|
||||
{
|
||||
$profile = qh_fetch_hospital_profile();
|
||||
return qh_sanitize_hex_color($profile['secondary_color'] ?? null, '#16697A');
|
||||
}
|
||||
|
||||
function qh_current_language_badge(): string
|
||||
{
|
||||
return qh_t('English workspace', 'واجهة عربية');
|
||||
}
|
||||
|
||||
function qh_admin_sections(): array
|
||||
{
|
||||
return [
|
||||
'admin.php' => [
|
||||
'label' => qh_t('Overview', 'نظرة عامة'),
|
||||
'description' => qh_t('Admin home and setup summary.', 'الصفحة الرئيسية وملخص الإعدادات.'),
|
||||
'icon' => 'overview',
|
||||
],
|
||||
'admin_hospital.php' => [
|
||||
'label' => qh_t('Hospital profile', 'ملف المستشفى'),
|
||||
'description' => qh_t('Manage logo, favicon, contact details, and brand colors.', 'إدارة الشعار والأيقونة وبيانات التواصل وألوان الهوية.'),
|
||||
'icon' => 'hospital',
|
||||
],
|
||||
'admin_clinics.php' => [
|
||||
'label' => qh_t('Clinics', 'العيادات'),
|
||||
'description' => qh_t('Manage clinic codes, routing, and order.', 'إدارة رموز العيادات والمسار والترتيب.'),
|
||||
'icon' => 'clinic',
|
||||
],
|
||||
'admin_doctors.php' => [
|
||||
'label' => qh_t('Doctors', 'الأطباء'),
|
||||
'description' => qh_t('Manage doctors, rooms, and assignments.', 'إدارة الأطباء والغرف والتعيينات.'),
|
||||
'icon' => 'doctor',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function qh_admin_allowed_pages(): array
|
||||
{
|
||||
return array_keys(qh_admin_sections());
|
||||
}
|
||||
|
||||
function qh_admin_return_to(?string $candidate = null): string
|
||||
{
|
||||
$page = basename((string) ($candidate ?? 'admin.php'));
|
||||
return in_array($page, qh_admin_allowed_pages(), true) ? $page : 'admin.php';
|
||||
}
|
||||
|
||||
function qh_admin_sidebar_icon(string $icon): string
|
||||
{
|
||||
return match ($icon) {
|
||||
'hospital' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 20V7l7-3 7 3v13"/><path d="M9 11h1"/><path d="M14 11h1"/><path d="M11 8h2"/><path d="M11 20v-4h2v4"/><path d="M3 20h18"/></svg>',
|
||||
'clinic' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h16"/><path d="M6 20V8l6-4 6 4v12"/><path d="M9 12h1"/><path d="M14 12h1"/><path d="M11 20v-4h2v4"/></svg>',
|
||||
'doctor' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12h14"/><circle cx="12" cy="12" r="8.5"/></svg>',
|
||||
default => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="6.5" height="6.5" rx="1.5"/><rect x="13.5" y="4" width="6.5" height="6.5" rx="1.5"/><rect x="4" y="13.5" width="6.5" height="6.5" rx="1.5"/><rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1.5"/></svg>',
|
||||
};
|
||||
}
|
||||
|
||||
function qh_admin_stats(): array
|
||||
{
|
||||
$clinics = qh_fetch_clinics();
|
||||
$doctors = qh_fetch_doctors();
|
||||
$vitalsClinics = count(array_filter($clinics, static fn(array $clinic): bool => (int) ($clinic['requires_vitals'] ?? 0) === 1));
|
||||
|
||||
return [
|
||||
'clinics' => count($clinics),
|
||||
'doctors' => count($doctors),
|
||||
'vitals_clinics' => $vitalsClinics,
|
||||
'direct_clinics' => max(count($clinics) - $vitalsClinics, 0),
|
||||
];
|
||||
}
|
||||
|
||||
function qh_render_admin_sidebar(string $activePage, array $stats = []): void
|
||||
{
|
||||
$sections = qh_admin_sections();
|
||||
$activePage = qh_admin_return_to($activePage);
|
||||
if ($stats === []) {
|
||||
$stats = qh_admin_stats();
|
||||
}
|
||||
|
||||
echo '<div class="panel-card admin-sidebar">';
|
||||
echo ' <div class="admin-sidebar-top">';
|
||||
echo ' <span class="section-kicker">' . qh_h(qh_t('Admin panel', 'لوحة الإدارة')) . '</span>';
|
||||
echo ' <h1 class="section-title mt-3 mb-1">' . qh_h(qh_hospital_name()) . '</h1>';
|
||||
echo ' <p class="section-copy mb-0">' . qh_h(qh_hospital_tagline() !== '' ? qh_hospital_tagline() : qh_t('Move between dedicated pages instead of one long mixed admin screen.', 'تنقل بين صفحات مستقلة بدلاً من شاشة إدارة طويلة ومختلطة.')) . '</p>';
|
||||
echo ' </div>';
|
||||
echo ' <nav class="admin-sidebar-nav" aria-label="Admin section navigation">';
|
||||
foreach ($sections as $file => $section) {
|
||||
$activeClass = $file === $activePage ? ' active' : '';
|
||||
$icon = qh_admin_sidebar_icon((string) ($section['icon'] ?? 'overview'));
|
||||
echo ' <a class="admin-sidebar-link' . $activeClass . '" href="' . qh_h(qh_url($file)) . '" title="' . qh_h($section['description']) . '">';
|
||||
echo ' <span class="admin-sidebar-link-icon">' . $icon . '</span>';
|
||||
echo ' <span class="admin-sidebar-link-text">' . qh_h($section['label']) . '</span>';
|
||||
echo ' </a>';
|
||||
}
|
||||
echo ' </nav>';
|
||||
echo ' <div class="admin-sidebar-meta">';
|
||||
echo ' <div class="admin-mini-stat"><span class="admin-mini-stat-value">' . qh_h((string) ($stats['clinics'] ?? 0)) . '</span><span class="admin-mini-stat-label">' . qh_h(qh_t('Clinics', 'العيادات')) . '</span></div>';
|
||||
echo ' <div class="admin-mini-stat"><span class="admin-mini-stat-value">' . qh_h((string) ($stats['doctors'] ?? 0)) . '</span><span class="admin-mini-stat-label">' . qh_h(qh_t('Doctors', 'الأطباء')) . '</span></div>';
|
||||
echo ' <div class="admin-mini-stat"><span class="admin-mini-stat-value">' . qh_h((string) ($stats['vitals_clinics'] ?? 0)) . '</span><span class="admin-mini-stat-label">' . qh_h(qh_t('Vitals-first clinics', 'العيادات التي تبدأ بالعلامات')) . '</span></div>';
|
||||
echo ' </div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
function qh_ticket_next_stop(array $ticket): string
|
||||
{
|
||||
return (int) ($ticket['clinic_requires_vitals'] ?? 0) === 1
|
||||
? qh_t('Nursing vitals', 'العلامات الحيوية في التمريض')
|
||||
: qh_t('Doctor waiting area', 'منطقة انتظار الطبيب');
|
||||
}
|
||||
|
||||
function qh_ticket_last_note(array $ticket): string
|
||||
{
|
||||
return match ((string) ($ticket['status'] ?? '')) {
|
||||
'waiting_vitals' => qh_t('Proceed to nursing vitals first.', 'يرجى التوجه أولاً إلى العلامات الحيوية في التمريض.'),
|
||||
'ready_for_doctor' => qh_t('Wait for the doctor call on the public display.', 'انتظر نداء الطبيب على الشاشة العامة.'),
|
||||
'called' => qh_t('The patient has been called to the doctor room.', 'تم نداء المريض إلى غرفة الطبيب.'),
|
||||
'in_progress' => qh_t('The consultation is currently in progress.', 'الاستشارة جارية حالياً.'),
|
||||
'done' => qh_t('The visit has been completed.', 'تم إكمال الزيارة.'),
|
||||
'no_show' => qh_t('The patient was marked as no-show.', 'تم تسجيل المريض كحالة عدم حضور.'),
|
||||
default => trim((string) ($ticket['display_note'] ?? '')) !== '' ? (string) $ticket['display_note'] : '—',
|
||||
};
|
||||
}
|
||||
|
||||
function qh_label(string $en, string $ar, string $wrapper = 'span'): string
|
||||
{
|
||||
$tag = preg_replace('/[^a-z0-9]/i', '', $wrapper) ?: 'span';
|
||||
return sprintf(
|
||||
'<%1$s class="bi-label"><span class="label-en">%2$s</span><span class="label-sep"> / </span><span class="label-ar" lang="ar" dir="rtl">%3$s</span></%1$s>',
|
||||
$tag,
|
||||
qh_h($en),
|
||||
qh_h($ar)
|
||||
);
|
||||
return sprintf('<%1$s>%2$s</%1$s>', $tag, qh_h(qh_t($en, $ar)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_page_start(string $activePage, string $pageTitle, string $metaDescription = ''): void
|
||||
{
|
||||
$locale = qh_locale();
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? qh_project_description();
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
$fullTitle = $pageTitle . ' · ' . qh_project_name();
|
||||
$brandName = qh_hospital_name();
|
||||
$faviconUrl = qh_hospital_favicon_url();
|
||||
$fullTitle = $pageTitle . ' · ' . $brandName;
|
||||
$description = $metaDescription !== '' ? $metaDescription : qh_project_description();
|
||||
$bodyClass = 'page-' . preg_replace('/[^a-z0-9\-]/i', '-', $activePage);
|
||||
$bodyClass = 'page-' . preg_replace('/[^a-z0-9\-]/i', '-', $activePage) . ' lang-' . $locale;
|
||||
$assetVersionCss = qh_asset_version('assets/css/custom.css');
|
||||
$assetVersionJs = qh_asset_version('assets/js/main.js');
|
||||
$primaryColor = qh_hospital_primary_color();
|
||||
$secondaryColor = qh_hospital_secondary_color();
|
||||
|
||||
echo '<!doctype html>';
|
||||
echo '<html lang="en">';
|
||||
echo '<html lang="' . qh_h($locale) . '" dir="' . (qh_is_ar() ? 'rtl' : 'ltr') . '">';
|
||||
echo '<head>';
|
||||
echo ' <meta charset="utf-8">';
|
||||
echo ' <meta name="viewport" content="width=device-width, initial-scale=1">';
|
||||
@ -187,11 +531,16 @@ function qh_page_start(string $activePage, string $pageTitle, string $metaDescri
|
||||
echo ' <meta property="og:image" content="' . qh_h((string) $projectImageUrl) . '">';
|
||||
echo ' <meta property="twitter:image" content="' . qh_h((string) $projectImageUrl) . '">';
|
||||
}
|
||||
echo ' <meta name="theme-color" content="#f3f4f6">';
|
||||
if ($faviconUrl !== '') {
|
||||
echo ' <link rel="icon" href="' . qh_h($faviconUrl) . '">';
|
||||
echo ' <link rel="apple-touch-icon" href="' . qh_h($faviconUrl) . '">';
|
||||
}
|
||||
echo ' <meta name="theme-color" content="' . qh_h($primaryColor) . '">';
|
||||
echo ' <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">';
|
||||
echo ' <link rel="stylesheet" href="assets/css/custom.css?v=' . $assetVersionCss . '">';
|
||||
echo ' <style>:root{--accent:' . qh_h($primaryColor) . ';--accent-strong:' . qh_h($secondaryColor) . ';}</style>';
|
||||
echo '</head>';
|
||||
echo '<body class="' . qh_h($bodyClass) . '" data-page="' . qh_h($activePage) . '">';
|
||||
echo '<body class="' . qh_h($bodyClass) . '" data-page="' . qh_h($activePage) . '" data-locale="' . qh_h($locale) . '">';
|
||||
if ($activePage !== 'display') {
|
||||
qh_render_nav($activePage);
|
||||
}
|
||||
@ -199,6 +548,8 @@ function qh_page_start(string $activePage, string $pageTitle, string $metaDescri
|
||||
qh_render_flash();
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_page_end(): void
|
||||
{
|
||||
$assetVersionJs = qh_asset_version('assets/js/main.js');
|
||||
@ -211,37 +562,57 @@ function qh_page_end(): void
|
||||
function qh_render_nav(string $activePage): void
|
||||
{
|
||||
$links = [
|
||||
'home' => ['href' => 'index.php', 'label' => qh_label('Operations', 'العمليات')],
|
||||
'admin' => ['href' => 'admin.php', 'label' => qh_label('Admin', 'الإدارة')],
|
||||
'reception' => ['href' => 'reception.php', 'label' => qh_label('Reception', 'الاستقبال')],
|
||||
'nursing' => ['href' => 'nursing.php', 'label' => qh_label('Nursing', 'التمريض')],
|
||||
'doctor' => ['href' => 'doctor.php', 'label' => qh_label('Doctor', 'الطبيب')],
|
||||
'display' => ['href' => 'display.php', 'label' => qh_label('Display', 'الشاشة العامة')],
|
||||
'home' => ['href' => qh_url('index.php'), 'label' => qh_t('Operations', 'العمليات')],
|
||||
'admin' => ['href' => qh_url('admin.php'), 'label' => qh_t('Admin', 'الإدارة')],
|
||||
'reception' => ['href' => qh_url('reception.php'), 'label' => qh_t('Reception', 'الاستقبال')],
|
||||
'nursing' => ['href' => qh_url('nursing.php'), 'label' => qh_t('Nursing', 'التمريض')],
|
||||
'doctor' => ['href' => qh_url('doctor.php'), 'label' => qh_t('Doctor', 'الطبيب')],
|
||||
'display' => ['href' => qh_url('display.php'), 'label' => qh_t('Display', 'الشاشة')],
|
||||
];
|
||||
|
||||
echo '<header class="border-bottom bg-white sticky-top shadow-sm">';
|
||||
$logoUrl = qh_hospital_logo_url();
|
||||
$tagline = qh_hospital_tagline();
|
||||
|
||||
echo '<header class="app-header border-bottom sticky-top shadow-sm">';
|
||||
echo ' <nav class="navbar navbar-expand-lg">';
|
||||
echo ' <div class="container-fluid container-xxl px-3 px-lg-4">';
|
||||
echo ' <a class="navbar-brand d-flex flex-column gap-0" href="index.php">';
|
||||
echo ' <span class="brand-mark">HQ</span>';
|
||||
echo ' <span class="brand-text">' . qh_h(qh_project_name()) . '</span>';
|
||||
echo ' <a class="navbar-brand d-flex align-items-center" href="' . qh_h(qh_url('index.php')) . '">';
|
||||
if ($logoUrl !== '') {
|
||||
echo ' <span class="brand-mark brand-mark-image"><img src="' . qh_h($logoUrl) . '" alt="' . qh_h(qh_hospital_name()) . '"></span>';
|
||||
} else {
|
||||
echo ' <span class="brand-mark">' . qh_h(qh_hospital_brand_initials()) . '</span>';
|
||||
}
|
||||
echo ' <span class="brand-copy">';
|
||||
echo ' <span class="brand-text">' . qh_h(qh_hospital_name()) . '</span>';
|
||||
if ($tagline !== '') {
|
||||
echo ' <span class="brand-subtext">' . qh_h($tagline) . '</span>';
|
||||
}
|
||||
echo ' </span>';
|
||||
echo ' </a>';
|
||||
echo ' <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#appNav" aria-controls="appNav" aria-expanded="false" aria-label="Toggle navigation">';
|
||||
echo ' <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#appNav" aria-controls="appNav" aria-expanded="false" aria-label="' . qh_h(qh_t('Toggle navigation', 'تبديل التنقل')) . '">';
|
||||
echo ' <span class="navbar-toggler-icon"></span>';
|
||||
echo ' </button>';
|
||||
echo ' <div class="collapse navbar-collapse" id="appNav">';
|
||||
echo ' <ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2 mt-3 mt-lg-0">';
|
||||
foreach ($links as $key => $link) {
|
||||
$activeClass = $key === $activePage ? ' active' : '';
|
||||
echo ' <li class="nav-item"><a class="nav-link' . $activeClass . '" href="' . qh_h($link['href']) . '">' . $link['label'] . '</a></li>';
|
||||
echo ' <li class="nav-item"><a class="nav-link' . $activeClass . '" href="' . qh_h($link['href']) . '">' . qh_h($link['label']) . '</a></li>';
|
||||
}
|
||||
echo ' </ul>';
|
||||
echo ' <div class="lang-switcher ms-lg-3 mt-3 mt-lg-0">';
|
||||
foreach (['en', 'ar'] as $lang) {
|
||||
$activeClass = qh_locale() === $lang ? ' active' : '';
|
||||
echo ' <a class="lang-switch-link' . $activeClass . '" href="' . qh_h(qh_switch_lang_url($lang)) . '">' . qh_h(qh_locale_label($lang)) . '</a>';
|
||||
}
|
||||
echo ' </div>';
|
||||
echo ' </div>';
|
||||
echo ' </div>';
|
||||
echo ' </nav>';
|
||||
echo '</header>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_set_flash(string $type, string $message): void
|
||||
{
|
||||
$_SESSION['flash'] = ['type' => $type, 'message' => $message];
|
||||
@ -268,18 +639,27 @@ function qh_render_flash(): void
|
||||
echo ' <div class="toast app-toast js-app-toast text-bg-' . qh_h($toastType) . ' border-0" role="status" aria-live="polite" aria-atomic="true">';
|
||||
echo ' <div class="d-flex">';
|
||||
echo ' <div class="toast-body">' . qh_h((string) $flash['message']) . '</div>';
|
||||
echo ' <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>';
|
||||
echo ' <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="' . qh_h(qh_t('Close', 'إغلاق')) . '"></button>';
|
||||
echo ' </div>';
|
||||
echo ' </div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_redirect(string $location): void
|
||||
{
|
||||
if (strpos($location, 'lang=') === false) {
|
||||
$separator = str_contains($location, '?') ? '&' : '?';
|
||||
$location .= $separator . 'lang=' . qh_locale();
|
||||
}
|
||||
|
||||
header('Location: ' . $location);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_fetch_clinics(): array
|
||||
{
|
||||
$stmt = db()->query("SELECT * FROM hospital_queue_records WHERE item_type = 'clinic' ORDER BY sort_order ASC, name_en ASC");
|
||||
@ -398,12 +778,12 @@ function qh_create_ticket(string $patientName, int $clinicId, int $doctorId, str
|
||||
$pdo = db();
|
||||
$clinic = qh_fetch_clinic($clinicId);
|
||||
if (!$clinic) {
|
||||
throw new RuntimeException('Clinic not found.');
|
||||
throw new RuntimeException(qh_t('Clinic not found.', 'لم يتم العثور على العيادة.'));
|
||||
}
|
||||
|
||||
$doctor = qh_fetch_doctor($doctorId);
|
||||
if (!$doctor || (int) $doctor['clinic_id'] !== $clinicId) {
|
||||
throw new RuntimeException('Doctor does not belong to the selected clinic.');
|
||||
throw new RuntimeException(qh_t('The doctor does not belong to the selected clinic.', 'الطبيب لا يتبع العيادة المحددة.'));
|
||||
}
|
||||
|
||||
$ticketNumber = qh_generate_ticket_number($clinic['code']);
|
||||
@ -477,9 +857,11 @@ function qh_status_meta(string $status): array
|
||||
function qh_status_badge(string $status): string
|
||||
{
|
||||
$meta = qh_status_meta($status);
|
||||
return '<span class="badge text-bg-' . qh_h($meta['class']) . ' status-badge">' . qh_label($meta['en'], $meta['ar']) . '</span>';
|
||||
return '<span class="badge text-bg-' . qh_h($meta['class']) . ' status-badge">' . qh_h(qh_t($meta['en'], $meta['ar'])) . '</span>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_call_message(array $ticket): array
|
||||
{
|
||||
$ticketNumber = $ticket['ticket_number'] ?? '---';
|
||||
@ -505,18 +887,25 @@ function qh_format_datetime(?string $value): string
|
||||
function qh_require_post(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
|
||||
qh_set_flash('danger', 'Invalid request method.');
|
||||
qh_set_flash('danger', qh_t('Invalid request method.', 'طريقة الطلب غير صالحة.'));
|
||||
qh_redirect('index.php');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function qh_admin_handle_request(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$action = trim((string) ($_POST['action'] ?? ''));
|
||||
if ($action === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$returnTo = qh_admin_return_to($_POST['return_to'] ?? 'admin.php');
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
@ -526,7 +915,7 @@ function qh_admin_handle_request(): void
|
||||
$nameAr = trim((string) ($_POST['name_ar'] ?? ''));
|
||||
$requiresVitals = isset($_POST['requires_vitals']) ? 1 : 0;
|
||||
if ($code === '' || $nameEn === '' || $nameAr === '') {
|
||||
throw new InvalidArgumentException('Please complete the clinic code and bilingual names.');
|
||||
throw new InvalidArgumentException(qh_t('Please complete the clinic code and both clinic names.', 'يرجى إدخال رمز العيادة والاسمين الإنجليزي والعربي.'));
|
||||
}
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO hospital_queue_records (item_type, code, name_en, name_ar, requires_vitals, sort_order, status)
|
||||
@ -537,29 +926,59 @@ function qh_admin_handle_request(): void
|
||||
'name_en' => $nameEn,
|
||||
'name_ar' => $nameAr,
|
||||
'requires_vitals' => $requiresVitals,
|
||||
'sort_order' => (int) ($_POST['sort_order'] ?? 50),
|
||||
'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1),
|
||||
]);
|
||||
qh_set_flash('success', 'Clinic saved successfully.');
|
||||
qh_set_flash('success', qh_t('Clinic saved successfully.', 'تم حفظ العيادة بنجاح.'));
|
||||
} elseif ($action === 'update_clinic') {
|
||||
$clinicId = (int) ($_POST['clinic_id'] ?? 0);
|
||||
$code = strtoupper(trim((string) ($_POST['code'] ?? '')));
|
||||
$nameEn = trim((string) ($_POST['name_en'] ?? ''));
|
||||
$nameAr = trim((string) ($_POST['name_ar'] ?? ''));
|
||||
if ($clinicId <= 0 || $code === '' || $nameEn === '' || $nameAr === '') {
|
||||
throw new InvalidArgumentException(qh_t('Please complete the clinic details before updating.', 'يرجى إكمال بيانات العيادة قبل التحديث.'));
|
||||
}
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE hospital_queue_records
|
||||
SET requires_vitals = :requires_vitals, sort_order = :sort_order
|
||||
SET code = :code,
|
||||
name_en = :name_en,
|
||||
name_ar = :name_ar,
|
||||
requires_vitals = :requires_vitals,
|
||||
sort_order = :sort_order
|
||||
WHERE item_type = 'clinic' AND id = :clinic_id"
|
||||
);
|
||||
$stmt->execute([
|
||||
'code' => substr($code, 0, 10),
|
||||
'name_en' => $nameEn,
|
||||
'name_ar' => $nameAr,
|
||||
'requires_vitals' => isset($_POST['requires_vitals']) ? 1 : 0,
|
||||
'sort_order' => (int) ($_POST['sort_order'] ?? 50),
|
||||
'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1),
|
||||
'clinic_id' => $clinicId,
|
||||
]);
|
||||
qh_set_flash('success', 'Clinic settings updated.');
|
||||
qh_set_flash('success', qh_t('Clinic updated successfully.', 'تم تحديث العيادة بنجاح.'));
|
||||
} elseif ($action === 'delete_clinic') {
|
||||
$clinicId = (int) ($_POST['clinic_id'] ?? 0);
|
||||
if ($clinicId <= 0) {
|
||||
throw new InvalidArgumentException(qh_t('Invalid clinic selected.', 'تم اختيار عيادة غير صالحة.'));
|
||||
}
|
||||
$doctorCountStmt = $pdo->prepare("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'doctor' AND clinic_id = :clinic_id");
|
||||
$doctorCountStmt->execute(['clinic_id' => $clinicId]);
|
||||
$doctorCount = (int) $doctorCountStmt->fetchColumn();
|
||||
$ticketCountStmt = $pdo->prepare("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND clinic_id = :clinic_id");
|
||||
$ticketCountStmt->execute(['clinic_id' => $clinicId]);
|
||||
$ticketCount = (int) $ticketCountStmt->fetchColumn();
|
||||
if ($doctorCount > 0 || $ticketCount > 0) {
|
||||
throw new InvalidArgumentException(qh_t('This clinic cannot be deleted because it is linked to doctors or patient tickets.', 'لا يمكن حذف هذه العيادة لأنها مرتبطة بأطباء أو تذاكر مرضى.'));
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM hospital_queue_records WHERE item_type = 'clinic' AND id = :clinic_id");
|
||||
$stmt->execute(['clinic_id' => $clinicId]);
|
||||
qh_set_flash('success', qh_t('Clinic deleted successfully.', 'تم حذف العيادة بنجاح.'));
|
||||
} elseif ($action === 'add_doctor') {
|
||||
$nameEn = trim((string) ($_POST['name_en'] ?? ''));
|
||||
$nameAr = trim((string) ($_POST['name_ar'] ?? ''));
|
||||
$clinicId = (int) ($_POST['clinic_id'] ?? 0);
|
||||
$roomNumber = trim((string) ($_POST['room_number'] ?? ''));
|
||||
if ($nameEn === '' || $nameAr === '' || $clinicId <= 0 || $roomNumber === '') {
|
||||
throw new InvalidArgumentException('Please complete the doctor form before saving.');
|
||||
throw new InvalidArgumentException(qh_t('Please complete the doctor form before saving.', 'يرجى إكمال بيانات الطبيب قبل الحفظ.'));
|
||||
}
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO hospital_queue_records (item_type, name_en, name_ar, clinic_id, room_number, sort_order, status)
|
||||
@ -570,31 +989,129 @@ function qh_admin_handle_request(): void
|
||||
'name_ar' => $nameAr,
|
||||
'clinic_id' => $clinicId,
|
||||
'room_number' => $roomNumber,
|
||||
'sort_order' => (int) ($_POST['sort_order'] ?? 50),
|
||||
'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1),
|
||||
]);
|
||||
qh_set_flash('success', 'Doctor profile saved.');
|
||||
qh_set_flash('success', qh_t('Doctor profile saved.', 'تم حفظ ملف الطبيب.'));
|
||||
} elseif ($action === 'update_doctor') {
|
||||
$doctorId = (int) ($_POST['doctor_id'] ?? 0);
|
||||
$nameEn = trim((string) ($_POST['name_en'] ?? ''));
|
||||
$nameAr = trim((string) ($_POST['name_ar'] ?? ''));
|
||||
$clinicId = (int) ($_POST['clinic_id'] ?? 0);
|
||||
$roomNumber = trim((string) ($_POST['room_number'] ?? ''));
|
||||
if ($doctorId <= 0 || $nameEn === '' || $nameAr === '' || $clinicId <= 0 || $roomNumber === '') {
|
||||
throw new InvalidArgumentException(qh_t('Please complete the doctor details before updating.', 'يرجى إكمال بيانات الطبيب قبل التحديث.'));
|
||||
}
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE hospital_queue_records
|
||||
SET clinic_id = :clinic_id, room_number = :room_number, sort_order = :sort_order
|
||||
SET name_en = :name_en,
|
||||
name_ar = :name_ar,
|
||||
clinic_id = :clinic_id,
|
||||
room_number = :room_number,
|
||||
sort_order = :sort_order
|
||||
WHERE item_type = 'doctor' AND id = :doctor_id"
|
||||
);
|
||||
$stmt->execute([
|
||||
'name_en' => $nameEn,
|
||||
'name_ar' => $nameAr,
|
||||
'clinic_id' => $clinicId,
|
||||
'room_number' => $roomNumber,
|
||||
'sort_order' => (int) ($_POST['sort_order'] ?? 50),
|
||||
'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1),
|
||||
'doctor_id' => $doctorId,
|
||||
]);
|
||||
qh_set_flash('success', 'Doctor assignment updated.');
|
||||
qh_set_flash('success', qh_t('Doctor assignment updated.', 'تم تحديث تعيين الطبيب.'));
|
||||
} elseif ($action === 'delete_doctor') {
|
||||
$doctorId = (int) ($_POST['doctor_id'] ?? 0);
|
||||
if ($doctorId <= 0) {
|
||||
throw new InvalidArgumentException(qh_t('Invalid doctor selected.', 'تم اختيار طبيب غير صالح.'));
|
||||
}
|
||||
$ticketCountStmt = $pdo->prepare("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND doctor_id = :doctor_id");
|
||||
$ticketCountStmt->execute(['doctor_id' => $doctorId]);
|
||||
$ticketCount = (int) $ticketCountStmt->fetchColumn();
|
||||
if ($ticketCount > 0) {
|
||||
throw new InvalidArgumentException(qh_t('This doctor cannot be deleted because patient tickets are linked to the profile.', 'لا يمكن حذف هذا الطبيب لأن هناك تذاكر مرضى مرتبطة بالملف.'));
|
||||
}
|
||||
$stmt = $pdo->prepare("DELETE FROM hospital_queue_records WHERE item_type = 'doctor' AND id = :doctor_id");
|
||||
$stmt->execute(['doctor_id' => $doctorId]);
|
||||
qh_set_flash('success', qh_t('Doctor deleted successfully.', 'تم حذف الطبيب بنجاح.'));
|
||||
} elseif ($action === 'save_hospital_profile') {
|
||||
$nameEn = trim((string) ($_POST['name_en'] ?? ''));
|
||||
$nameAr = trim((string) ($_POST['name_ar'] ?? ''));
|
||||
$shortName = trim((string) ($_POST['short_name'] ?? ''));
|
||||
$taglineEn = trim((string) ($_POST['tagline_en'] ?? ''));
|
||||
$taglineAr = trim((string) ($_POST['tagline_ar'] ?? ''));
|
||||
$phone = trim((string) ($_POST['phone'] ?? ''));
|
||||
$email = trim((string) ($_POST['email'] ?? ''));
|
||||
$website = trim((string) ($_POST['website'] ?? ''));
|
||||
$addressEn = trim((string) ($_POST['address_en'] ?? ''));
|
||||
$addressAr = trim((string) ($_POST['address_ar'] ?? ''));
|
||||
$workingHoursEn = trim((string) ($_POST['working_hours_en'] ?? ''));
|
||||
$workingHoursAr = trim((string) ($_POST['working_hours_ar'] ?? ''));
|
||||
$logoUrl = trim((string) ($_POST['logo_url'] ?? ''));
|
||||
$faviconUrl = trim((string) ($_POST['favicon_url'] ?? ''));
|
||||
$primaryColor = qh_sanitize_hex_color($_POST['primary_color'] ?? '', '#0F8B8D');
|
||||
$secondaryColor = qh_sanitize_hex_color($_POST['secondary_color'] ?? '', '#16697A');
|
||||
|
||||
if ($nameEn === '' || $nameAr === '') {
|
||||
throw new InvalidArgumentException(qh_t('Please enter both the English and Arabic hospital names.', 'يرجى إدخال اسم المستشفى بالإنجليزية والعربية.'));
|
||||
}
|
||||
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
throw new InvalidArgumentException(qh_t('Please enter a valid hospital email address.', 'يرجى إدخال بريد إلكتروني صالح للمستشفى.'));
|
||||
}
|
||||
if ($website !== '' && filter_var($website, FILTER_VALIDATE_URL) === false) {
|
||||
throw new InvalidArgumentException(qh_t('Please enter a valid website URL.', 'يرجى إدخال رابط موقع صالح.'));
|
||||
}
|
||||
if ($logoUrl !== '' && filter_var($logoUrl, FILTER_VALIDATE_URL) === false) {
|
||||
throw new InvalidArgumentException(qh_t('Please enter a valid logo URL.', 'يرجى إدخال رابط صالح للشعار.'));
|
||||
}
|
||||
if ($faviconUrl !== '' && filter_var($faviconUrl, FILTER_VALIDATE_URL) === false) {
|
||||
throw new InvalidArgumentException(qh_t('Please enter a valid favicon URL.', 'يرجى إدخال رابط صالح للأيقونة.'));
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE hospital_profile_settings
|
||||
SET name_en = :name_en,
|
||||
name_ar = :name_ar,
|
||||
short_name = :short_name,
|
||||
tagline_en = :tagline_en,
|
||||
tagline_ar = :tagline_ar,
|
||||
phone = :phone,
|
||||
email = :email,
|
||||
website = :website,
|
||||
address_en = :address_en,
|
||||
address_ar = :address_ar,
|
||||
working_hours_en = :working_hours_en,
|
||||
working_hours_ar = :working_hours_ar,
|
||||
logo_url = :logo_url,
|
||||
favicon_url = :favicon_url,
|
||||
primary_color = :primary_color,
|
||||
secondary_color = :secondary_color
|
||||
WHERE id = 1"
|
||||
);
|
||||
$stmt->execute([
|
||||
'name_en' => $nameEn,
|
||||
'name_ar' => $nameAr,
|
||||
'short_name' => substr($shortName, 0, 40),
|
||||
'tagline_en' => $taglineEn,
|
||||
'tagline_ar' => $taglineAr,
|
||||
'phone' => $phone,
|
||||
'email' => $email,
|
||||
'website' => $website,
|
||||
'address_en' => $addressEn,
|
||||
'address_ar' => $addressAr,
|
||||
'working_hours_en' => $workingHoursEn,
|
||||
'working_hours_ar' => $workingHoursAr,
|
||||
'logo_url' => $logoUrl,
|
||||
'favicon_url' => $faviconUrl,
|
||||
'primary_color' => $primaryColor,
|
||||
'secondary_color' => $secondaryColor,
|
||||
]);
|
||||
qh_set_flash('success', qh_t('Hospital profile updated successfully.', 'تم تحديث ملف المستشفى بنجاح.'));
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
qh_set_flash('danger', $exception->getMessage());
|
||||
}
|
||||
|
||||
qh_redirect('admin.php');
|
||||
qh_redirect($returnTo);
|
||||
}
|
||||
|
||||
function qh_reception_handle_request(): void
|
||||
@ -610,11 +1127,11 @@ function qh_reception_handle_request(): void
|
||||
$languagePref = trim((string) ($_POST['language_pref'] ?? 'en'));
|
||||
|
||||
if ($patientName === '' || $clinicId <= 0 || $doctorId <= 0) {
|
||||
throw new InvalidArgumentException('Please complete patient name, clinic, and doctor.');
|
||||
throw new InvalidArgumentException(qh_t('Please complete the patient name, clinic, and doctor.', 'يرجى إدخال اسم المريض والعيادة والطبيب.'));
|
||||
}
|
||||
|
||||
$ticketId = qh_create_ticket($patientName, $clinicId, $doctorId, $languagePref);
|
||||
qh_set_flash('success', 'Ticket issued successfully.');
|
||||
qh_set_flash('success', qh_t('Ticket issued successfully.', 'تم إصدار التذكرة بنجاح.'));
|
||||
qh_redirect('reception.php?ticket_id=' . $ticketId);
|
||||
} catch (Throwable $exception) {
|
||||
qh_set_flash('danger', $exception->getMessage());
|
||||
@ -632,7 +1149,7 @@ function qh_nursing_handle_request(): void
|
||||
$ticketId = (int) ($_POST['ticket_id'] ?? 0);
|
||||
$vitalsNotes = trim((string) ($_POST['vitals_notes'] ?? ''));
|
||||
if ($ticketId <= 0 || $vitalsNotes === '') {
|
||||
throw new InvalidArgumentException('Please add a short vitals note before sending the patient forward.');
|
||||
throw new InvalidArgumentException(qh_t('Please add a short vitals note before sending the patient forward.', 'يرجى إضافة ملاحظة قصيرة للعلامات الحيوية قبل إرسال المريض.'));
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
@ -646,7 +1163,7 @@ function qh_nursing_handle_request(): void
|
||||
'vitals_notes' => $vitalsNotes,
|
||||
'ticket_id' => $ticketId,
|
||||
]);
|
||||
qh_set_flash('success', 'Vitals captured and patient moved to doctor queue.');
|
||||
qh_set_flash('success', qh_t('Vitals captured and patient moved to the doctor queue.', 'تم حفظ العلامات الحيوية ونقل المريض إلى طابور الطبيب.'));
|
||||
} catch (Throwable $exception) {
|
||||
qh_set_flash('danger', $exception->getMessage());
|
||||
}
|
||||
@ -666,7 +1183,7 @@ function qh_doctor_handle_request(): void
|
||||
$action = trim((string) ($_POST['action'] ?? ''));
|
||||
$ticket = qh_fetch_ticket($ticketId);
|
||||
if (!$ticket || $doctorId <= 0 || (int) $ticket['doctor_id'] !== $doctorId) {
|
||||
throw new InvalidArgumentException('That ticket is not available for the selected doctor.');
|
||||
throw new InvalidArgumentException(qh_t('That ticket is not available for the selected doctor.', 'هذه التذكرة غير متاحة للطبيب المحدد.'));
|
||||
}
|
||||
|
||||
if ($action === 'call_ticket') {
|
||||
@ -680,7 +1197,7 @@ function qh_doctor_handle_request(): void
|
||||
'display_note' => $message['en'],
|
||||
'ticket_id' => $ticketId,
|
||||
]);
|
||||
qh_set_flash('success', 'Patient call pushed to the public display.');
|
||||
qh_set_flash('success', qh_t('Patient call was sent to the public display.', 'تم إرسال نداء المريض إلى الشاشة العامة.'));
|
||||
} elseif ($action === 'start_visit') {
|
||||
$stmt = db()->prepare(
|
||||
"UPDATE hospital_queue_records
|
||||
@ -688,7 +1205,7 @@ function qh_doctor_handle_request(): void
|
||||
WHERE item_type = 'ticket' AND id = :ticket_id"
|
||||
);
|
||||
$stmt->execute(['ticket_id' => $ticketId]);
|
||||
qh_set_flash('success', 'Consultation started.');
|
||||
qh_set_flash('success', qh_t('Consultation started.', 'بدأت الاستشارة.'));
|
||||
} elseif ($action === 'complete_ticket') {
|
||||
$stmt = db()->prepare(
|
||||
"UPDATE hospital_queue_records
|
||||
@ -696,7 +1213,7 @@ function qh_doctor_handle_request(): void
|
||||
WHERE item_type = 'ticket' AND id = :ticket_id"
|
||||
);
|
||||
$stmt->execute(['ticket_id' => $ticketId]);
|
||||
qh_set_flash('success', 'Visit marked as completed.');
|
||||
qh_set_flash('success', qh_t('Visit marked as completed.', 'تم إنهاء الزيارة.'));
|
||||
} elseif ($action === 'mark_no_show') {
|
||||
$stmt = db()->prepare(
|
||||
"UPDATE hospital_queue_records
|
||||
@ -704,7 +1221,7 @@ function qh_doctor_handle_request(): void
|
||||
WHERE item_type = 'ticket' AND id = :ticket_id"
|
||||
);
|
||||
$stmt->execute(['ticket_id' => $ticketId]);
|
||||
qh_set_flash('warning', 'Patient marked as no-show.');
|
||||
qh_set_flash('warning', qh_t('Patient marked as no-show.', 'تم تسجيل المريض كحالة عدم حضور.'));
|
||||
}
|
||||
|
||||
qh_redirect('doctor.php?doctor_id=' . $doctorId);
|
||||
|
||||
@ -9,54 +9,57 @@ $doctors = qh_fetch_doctors();
|
||||
$currentTicket = isset($_GET['ticket_id']) ? qh_fetch_ticket((int) $_GET['ticket_id']) : null;
|
||||
$todayTickets = qh_fetch_tickets(['waiting_vitals', 'ready_for_doctor', 'called', 'in_progress', 'done', 'no_show'], null, 12);
|
||||
|
||||
qh_page_start('reception', 'Reception ticket issuance', 'Reception desk ticket issue flow with one ticket that follows the patient through vitals and doctor visit.');
|
||||
qh_page_start(
|
||||
'reception',
|
||||
qh_t('Reception ticket issuance', 'إصدار التذاكر في الاستقبال'),
|
||||
qh_t('Reception page with separated language views.', 'صفحة الاستقبال مع فصل واضح بين اللغتين.')
|
||||
);
|
||||
?>
|
||||
<div class="container-xxl px-3 px-lg-4">
|
||||
<section class="page-header-panel mb-4">
|
||||
<div>
|
||||
<span class="section-kicker">Reception / الاستقبال</span>
|
||||
<h1 class="section-title-xl mt-2">Issue one ticket for the full visit.</h1>
|
||||
<p class="section-copy mb-0">Reception decides the clinic and doctor once. The system automatically routes the patient to vitals first only when the selected clinic requires it.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Reception', 'الاستقبال')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2"><?= qh_h(qh_t('Issue one ticket for the full visit.', 'أصدر تذكرة واحدة لكامل الزيارة.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Reception selects the clinic and doctor once. The system routes to nursing only when the clinic requires vitals.', 'يقوم الاستقبال باختيار العيادة والطبيب مرة واحدة. ويوجه النظام إلى التمريض فقط عندما تتطلب العيادة العلامات الحيوية.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-xl-5">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">New patient ticket / تذكرة مريض جديدة</h2>
|
||||
<h2 class="section-title mb-3"><?= qh_h(qh_t('New patient ticket', 'تذكرة مريض جديدة')) ?></h2>
|
||||
<form method="post" class="vstack gap-3" novalidate>
|
||||
<div>
|
||||
<label class="form-label">Patient name / اسم المريض</label>
|
||||
<label class="form-label"><?= qh_h(qh_t('Patient name', 'اسم المريض')) ?></label>
|
||||
<input class="form-control" type="text" name="patient_name" placeholder="Maha Ali" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Clinic / العيادة</label>
|
||||
<label class="form-label"><?= qh_h(qh_t('Clinic', 'العيادة')) ?></label>
|
||||
<select class="form-select js-clinic-select" name="clinic_id" required>
|
||||
<option value="">Choose clinic</option>
|
||||
<option value=""><?= qh_h(qh_t('Choose clinic', 'اختر العيادة')) ?></option>
|
||||
<?php foreach ($clinics as $clinic): ?>
|
||||
<option value="<?= qh_h((string) $clinic['id']) ?>"><?= qh_h($clinic['name_en']) ?> / <?= qh_h($clinic['name_ar']) ?><?= (int) $clinic['requires_vitals'] === 1 ? ' · Vitals first' : '' ?></option>
|
||||
<option value="<?= qh_h((string) $clinic['id']) ?>"><?= qh_h(qh_name($clinic)) ?><?php if ((int) $clinic['requires_vitals'] === 1): ?> · <?= qh_h(qh_t('Vitals first', 'العلامات أولاً')) ?><?php endif; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Doctor / الطبيب</label>
|
||||
<label class="form-label"><?= qh_h(qh_t('Doctor', 'الطبيب')) ?></label>
|
||||
<select class="form-select js-doctor-select" name="doctor_id" required>
|
||||
<option value="">Choose doctor</option>
|
||||
<option value=""><?= qh_h(qh_t('Choose doctor', 'اختر الطبيب')) ?></option>
|
||||
<?php foreach ($doctors as $doctor): ?>
|
||||
<option value="<?= qh_h((string) $doctor['id']) ?>" data-clinic-id="<?= qh_h((string) $doctor['clinic_id']) ?>">
|
||||
<?= qh_h($doctor['name_en']) ?> / <?= qh_h($doctor['name_ar']) ?> · Room <?= qh_h($doctor['room_number']) ?>
|
||||
</option>
|
||||
<option value="<?= qh_h((string) $doctor['id']) ?>" data-clinic-id="<?= qh_h((string) $doctor['clinic_id']) ?>"><?= qh_h(qh_name($doctor)) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($doctor['room_number']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Preferred language / اللغة المفضلة</label>
|
||||
<label class="form-label"><?= qh_h(qh_t('Preferred language', 'اللغة المفضلة')) ?></label>
|
||||
<select class="form-select" name="language_pref">
|
||||
<option value="en">English</option>
|
||||
<option value="ar">العربية</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-dark" type="submit">Issue ticket / إصدار التذكرة</button>
|
||||
<button class="btn btn-dark" type="submit"><?= qh_h(qh_t('Issue ticket', 'إصدار التذكرة')) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,40 +69,36 @@ qh_page_start('reception', 'Reception ticket issuance', 'Reception desk ticket i
|
||||
<div class="panel-card ticket-card mb-4">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="section-kicker">Issued ticket / التذكرة الصادرة</div>
|
||||
<div class="section-kicker"><?= qh_h(qh_t('Issued ticket', 'التذكرة الصادرة')) ?></div>
|
||||
<div class="ticket-number mt-2"><?= qh_h($currentTicket['ticket_number']) ?></div>
|
||||
<div class="mt-2 fw-semibold"><?= qh_h($currentTicket['patient_name']) ?></div>
|
||||
<div class="text-secondary"><?= qh_h($currentTicket['clinic_name_en'] ?? '') ?> · <?= qh_h($currentTicket['doctor_name_en'] ?? '') ?> · Room <?= qh_h($currentTicket['doctor_room'] ?? '--') ?></div>
|
||||
<div class="text-secondary"><?= qh_h(qh_name($currentTicket, 'clinic_name')) ?> · <?= qh_h(qh_name($currentTicket, 'doctor_name')) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($currentTicket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-lg-end gap-2">
|
||||
<?= qh_status_badge($currentTicket['status']) ?>
|
||||
<button class="btn btn-outline-dark btn-sm js-print-ticket" type="button">Print ticket</button>
|
||||
<button class="btn btn-outline-dark btn-sm js-print-ticket" type="button"><?= qh_h(qh_t('Print ticket', 'طباعة التذكرة')) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row g-3 small">
|
||||
<div class="col-md-4"><strong>Issued:</strong><br><?= qh_format_datetime($currentTicket['created_at']) ?></div>
|
||||
<div class="col-md-4"><strong>Language:</strong><br><?= strtoupper(qh_h($currentTicket['language_pref'])) ?></div>
|
||||
<div class="col-md-4"><strong>Next stop:</strong><br><?= (int) $currentTicket['clinic_requires_vitals'] === 1 ? 'Nursing vitals / التمريض' : 'Doctor waiting / انتظار الطبيب' ?></div>
|
||||
<div class="col-md-4"><strong><?= qh_h(qh_t('Issued', 'وقت الإصدار')) ?>:</strong><br><?= qh_format_datetime($currentTicket['created_at']) ?></div>
|
||||
<div class="col-md-4"><strong><?= qh_h(qh_t('Language', 'اللغة')) ?>:</strong><br><?= qh_h(qh_locale_label($currentTicket['language_pref'] ?? 'en')) ?></div>
|
||||
<div class="col-md-4"><strong><?= qh_h(qh_t('Next stop', 'المحطة التالية')) ?>:</strong><br><?= qh_h(qh_ticket_next_stop($currentTicket)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="section-title mb-1">Today’s tickets / تذاكر اليوم</h2>
|
||||
<p class="section-copy mb-0">The latest issued tickets and where they currently are in the visit flow.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="section-title mb-1"><?= qh_h(qh_t('Today’s tickets', 'تذاكر اليوم')) ?></h2>
|
||||
<p class="section-copy mb-3"><?= qh_h(qh_t('Latest tickets and where they currently sit in the visit flow.', 'أحدث التذاكر وموقعها الحالي في مسار الزيارة.')) ?></p>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Patient</th>
|
||||
<th>Clinic</th>
|
||||
<th>Status</th>
|
||||
<th><?= qh_h(qh_t('Ticket', 'التذكرة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Patient', 'المريض')) ?></th>
|
||||
<th><?= qh_h(qh_t('Clinic', 'العيادة')) ?></th>
|
||||
<th><?= qh_h(qh_t('Status', 'الحالة')) ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -108,9 +107,9 @@ qh_page_start('reception', 'Reception ticket issuance', 'Reception desk ticket i
|
||||
<tr>
|
||||
<td class="fw-semibold"><?= qh_h($ticket['ticket_number']) ?></td>
|
||||
<td><?= qh_h($ticket['patient_name']) ?></td>
|
||||
<td><?= qh_h($ticket['clinic_name_en'] ?? '—') ?></td>
|
||||
<td><?= qh_h(qh_name($ticket, 'clinic_name')) ?></td>
|
||||
<td><?= qh_status_badge($ticket['status']) ?></td>
|
||||
<td class="text-end"><a class="btn btn-sm btn-outline-dark" href="ticket.php?id=<?= qh_h((string) $ticket['id']) ?>">View</a></td>
|
||||
<td class="text-end"><a class="btn btn-sm btn-outline-dark" href="<?= qh_h(qh_url('ticket.php', ['id' => (int) $ticket['id']])) ?>"><?= qh_h(qh_t('View', 'عرض')) ?></a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
||||
81
ticket.php
81
ticket.php
@ -6,22 +6,27 @@ qh_boot();
|
||||
$ticketId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$ticket = $ticketId > 0 ? qh_fetch_ticket($ticketId) : null;
|
||||
|
||||
qh_page_start('home', 'Ticket detail', 'Detailed patient ticket timeline for the hospital queue workflow.');
|
||||
qh_page_start(
|
||||
'home',
|
||||
qh_t('Ticket detail', 'تفاصيل التذكرة'),
|
||||
qh_t('Detailed patient ticket timeline for the hospital queue workflow.', 'عرض تفصيلي لخط سير التذكرة في مسار طابور المستشفى.')
|
||||
);
|
||||
?>
|
||||
<div class="container-lg px-3 px-lg-4">
|
||||
<section class="page-header-panel mb-4">
|
||||
<div>
|
||||
<span class="section-kicker">Ticket detail / تفاصيل التذكرة</span>
|
||||
<h1 class="section-title-xl mt-2">Track one patient through the visit.</h1>
|
||||
<p class="section-copy mb-0">This view confirms the assigned clinic, doctor, room, vitals notes, and current status.</p>
|
||||
<span class="section-kicker"><?= qh_h(qh_t('Ticket detail', 'تفاصيل التذكرة')) ?></span>
|
||||
<div class="locale-chip mt-3"><?= qh_h(qh_current_language_badge()) ?></div>
|
||||
<h1 class="section-title-xl mt-2"><?= qh_h(qh_t('Track one patient through the visit.', 'تابع مريضاً واحداً خلال الزيارة.')) ?></h1>
|
||||
<p class="section-copy mb-0"><?= qh_h(qh_t('Review the assigned clinic, doctor, room, vitals notes, and current status.', 'راجع العيادة والطبيب والغرفة وملاحظات العلامات الحيوية والحالة الحالية.')) ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!$ticket): ?>
|
||||
<div class="panel-card">
|
||||
<div class="empty-state">
|
||||
<strong>Ticket not found.</strong>
|
||||
<span>Return to reception and choose a valid ticket.</span>
|
||||
<strong><?= qh_h(qh_t('Ticket not found.', 'لم يتم العثور على التذكرة.')) ?></strong>
|
||||
<span><?= qh_h(qh_t('Return to reception and choose a valid ticket.', 'عد إلى الاستقبال واختر تذكرة صالحة.')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
@ -30,11 +35,11 @@ qh_page_start('home', 'Ticket detail', 'Detailed patient ticket timeline for the
|
||||
<div>
|
||||
<div class="ticket-number"><?= qh_h($ticket['ticket_number']) ?></div>
|
||||
<div class="mt-2 fw-semibold"><?= qh_h($ticket['patient_name']) ?></div>
|
||||
<div class="text-secondary"><?= qh_h($ticket['clinic_name_en'] ?? '') ?> · <?= qh_h($ticket['doctor_name_en'] ?? '') ?> · Room <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
<div class="text-secondary"><?= qh_h(qh_name($ticket, 'clinic_name')) ?> · <?= qh_h(qh_name($ticket, 'doctor_name')) ?> · <?= qh_h(qh_t('Room', 'غرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-2 align-items-lg-end">
|
||||
<?= qh_status_badge($ticket['status']) ?>
|
||||
<span class="small text-secondary">Preferred language: <?= strtoupper(qh_h($ticket['language_pref'])) ?></span>
|
||||
<span class="small text-secondary"><?= qh_h(qh_t('Preferred language', 'اللغة المفضلة')) ?>: <?= qh_h(qh_locale_label($ticket['language_pref'] ?? 'en')) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -42,62 +47,32 @@ qh_page_start('home', 'Ticket detail', 'Detailed patient ticket timeline for the
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Visit timeline / خط سير الزيارة</h2>
|
||||
<h2 class="section-title mb-3"><?= qh_h(qh_t('Visit timeline', 'خط سير الزيارة')) ?></h2>
|
||||
<div class="timeline-list">
|
||||
<div class="timeline-item done">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Ticket issued / تم إصدار التذكرة</div>
|
||||
<div class="small text-secondary"><?= qh_format_datetime($ticket['created_at']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item done"><div class="timeline-dot"></div><div><div class="fw-semibold"><?= qh_h(qh_t('Ticket issued', 'تم إصدار التذكرة')) ?></div><div class="small text-secondary"><?= qh_format_datetime($ticket['created_at']) ?></div></div></div>
|
||||
<?php if ((int) $ticket['clinic_requires_vitals'] === 1): ?>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['ready_for_doctor', 'called', 'in_progress', 'done', 'no_show'], true) ? 'done' : 'current' ?>">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Nursing vitals / العلامات الحيوية</div>
|
||||
<div class="small text-secondary"><?= qh_h($ticket['vitals_notes'] ?: 'Waiting for nursing input.') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['ready_for_doctor', 'called', 'in_progress', 'done', 'no_show'], true) ? 'done' : 'current' ?>"><div class="timeline-dot"></div><div><div class="fw-semibold"><?= qh_h(qh_t('Nursing vitals', 'العلامات الحيوية')) ?></div><div class="small text-secondary"><?= qh_h($ticket['vitals_notes'] ?: qh_t('Waiting for nursing input.', 'بانتظار إدخال التمريض.')) ?></div></div></div>
|
||||
<?php endif; ?>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['called', 'in_progress', 'done', 'no_show'], true) ? 'done' : 'current' ?>">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Ready for doctor / جاهز للطبيب</div>
|
||||
<div class="small text-secondary">Assigned to <?= qh_h($ticket['doctor_name_en'] ?? 'Doctor') ?>, room <?= qh_h($ticket['doctor_room'] ?? '--') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['in_progress', 'done', 'no_show'], true) ? 'done' : (($ticket['status'] === 'called') ? 'current' : '') ?>">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Called to room / تم النداء للغرفة</div>
|
||||
<div class="small text-secondary"><?= qh_format_datetime($ticket['called_at']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['done', 'no_show'], true) ? 'done' : '' ?>">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Visit closed / إغلاق الزيارة</div>
|
||||
<div class="small text-secondary"><?= $ticket['status'] === 'done' ? 'Completed successfully.' : ($ticket['status'] === 'no_show' ? 'Marked as no-show.' : 'Still active.') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['called', 'in_progress', 'done', 'no_show'], true) ? 'done' : 'current' ?>"><div class="timeline-dot"></div><div><div class="fw-semibold"><?= qh_h(qh_t('Ready for doctor', 'جاهز للطبيب')) ?></div><div class="small text-secondary"><?= qh_h(qh_t('Assigned to', 'مخصص إلى')) ?> <?= qh_h(qh_name($ticket, 'doctor_name', qh_t('Doctor', 'الطبيب'))) ?>، <?= qh_h(qh_t('room', 'الغرفة')) ?> <?= qh_h($ticket['doctor_room'] ?? '--') ?></div></div></div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['in_progress', 'done', 'no_show'], true) ? 'done' : (($ticket['status'] === 'called') ? 'current' : '') ?>"><div class="timeline-dot"></div><div><div class="fw-semibold"><?= qh_h(qh_t('Called to room', 'تم النداء للغرفة')) ?></div><div class="small text-secondary"><?= qh_format_datetime($ticket['called_at']) ?></div></div></div>
|
||||
<div class="timeline-item <?= in_array($ticket['status'], ['done', 'no_show'], true) ? 'done' : '' ?>"><div class="timeline-dot"></div><div><div class="fw-semibold"><?= qh_h(qh_t('Visit closed', 'إغلاق الزيارة')) ?></div><div class="small text-secondary"><?= qh_h($ticket['status'] === 'done' ? qh_t('Completed successfully.', 'تمت الزيارة بنجاح.') : ($ticket['status'] === 'no_show' ? qh_t('Marked as no-show.', 'تم تسجيل عدم الحضور.') : qh_t('Still active.', 'لا تزال نشطة.'))) ?></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="panel-card h-100">
|
||||
<h2 class="section-title mb-3">Details / التفاصيل</h2>
|
||||
<h2 class="section-title mb-3"><?= qh_h(qh_t('Details', 'التفاصيل')) ?></h2>
|
||||
<dl class="detail-list mb-0">
|
||||
<div><dt>Clinic</dt><dd><?= qh_h($ticket['clinic_name_en'] ?? '—') ?></dd></div>
|
||||
<div><dt>Doctor</dt><dd><?= qh_h($ticket['doctor_name_en'] ?? '—') ?></dd></div>
|
||||
<div><dt>Room</dt><dd><?= qh_h($ticket['doctor_room'] ?? '--') ?></dd></div>
|
||||
<div><dt>Vitals note</dt><dd><?= qh_h($ticket['vitals_notes'] ?: 'Not captured yet.') ?></dd></div>
|
||||
<div><dt>Last note</dt><dd><?= qh_h($ticket['display_note'] ?: '—') ?></dd></div>
|
||||
<div><dt><?= qh_h(qh_t('Clinic', 'العيادة')) ?></dt><dd><?= qh_h(qh_name($ticket, 'clinic_name')) ?></dd></div>
|
||||
<div><dt><?= qh_h(qh_t('Doctor', 'الطبيب')) ?></dt><dd><?= qh_h(qh_name($ticket, 'doctor_name')) ?></dd></div>
|
||||
<div><dt><?= qh_h(qh_t('Room', 'الغرفة')) ?></dt><dd><?= qh_h($ticket['doctor_room'] ?? '--') ?></dd></div>
|
||||
<div><dt><?= qh_h(qh_t('Vitals note', 'ملاحظة العلامات')) ?></dt><dd><?= qh_h($ticket['vitals_notes'] ?: qh_t('Not captured yet.', 'لم تُسجل بعد.')) ?></dd></div>
|
||||
<div><dt><?= qh_h(qh_t('Last note', 'آخر ملاحظة')) ?></dt><dd><?= qh_h(qh_ticket_last_note($ticket)) ?></dd></div>
|
||||
</dl>
|
||||
<div class="d-flex flex-wrap gap-2 mt-4">
|
||||
<a class="btn btn-outline-dark btn-sm" href="reception.php?ticket_id=<?= qh_h((string) $ticket['id']) ?>">Back to reception</a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="doctor.php?doctor_id=<?= qh_h((string) ($ticket['doctor_id'] ?? 0)) ?>">Open doctor queue</a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="display.php">Open display</a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="<?= qh_h(qh_url('reception.php', ['ticket_id' => (int) $ticket['id']])) ?>"><?= qh_h(qh_t('Back to reception', 'العودة إلى الاستقبال')) ?></a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="<?= qh_h(qh_url('doctor.php', ['doctor_id' => (int) ($ticket['doctor_id'] ?? 0)])) ?>"><?= qh_h(qh_t('Open doctor queue', 'فتح طابور الطبيب')) ?></a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="<?= qh_h(qh_url('display.php')) ?>"><?= qh_h(qh_t('Open display', 'فتح الشاشة')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user