query("SELECT COUNT(*) FROM users"); if ($stmt !== false && ((int)$stmt->fetchColumn()) > 0) { $isInstalled = true; @file_put_contents(__DIR__ . '/install.lock', date('Y-m-d H:i:s')); } } } } catch (\Throwable $e) {} } if (!$isInstalled && $currentPage !== "install.php") { header("Location: install.php"); exit; } if (!in_array($currentPage, $publicPages, true)) { if (empty($_SESSION["user_id"])) { header("Location: login.php"); exit; } $role = $_SESSION["role"] ?? "admin"; $allowed = false; if ($role === "admin") { $allowed = true; } elseif ($currentPage === "index.php") { $allowed = true; } elseif ($role === "reception" && $currentPage === "reception.php") { $allowed = true; } elseif ($role === "nursing" && $currentPage === "nursing.php") { $allowed = true; } elseif ($role === "doctor" && $currentPage === "doctor.php") { $allowed = true; } if (!$allowed) { header("Location: index.php"); exit; } } require_once __DIR__ . '/db/config.php'; function qh_boot(): void { static $booted = false; if ($booted) { return; } qh_ensure_schema(); qh_seed_demo_data(); qh_seed_hospital_profile(); $profile = qh_fetch_hospital_profile(); $timezone = $profile['timezone'] ?? 'UTC'; if ($timezone) { date_default_timezone_set($timezone); try { $offset = (new DateTime('now', new DateTimeZone($timezone)))->format('P'); db()->exec("SET time_zone = " . db()->quote($offset)); } catch (\Throwable $e) {} } $booted = true; } function qh_ensure_schema(): void { $sql = <<exec($sql); $profileSql = <<exec($profileSql); try { db()->exec("ALTER TABLE hospital_profile_settings ADD COLUMN news_ticker_en VARCHAR(1000) DEFAULT NULL"); } catch (\Throwable $e) {} try { db()->exec("ALTER TABLE hospital_profile_settings ADD COLUMN news_ticker_ar VARCHAR(1000) DEFAULT NULL"); } catch (\Throwable $e) {} try { db()->exec("ALTER TABLE hospital_profile_settings ADD COLUMN timezone VARCHAR(100) DEFAULT 'UTC'"); } catch (\Throwable $e) {} try { db()->exec("ALTER TABLE hospital_profile_settings ADD COLUMN default_language VARCHAR(10) DEFAULT 'en'"); } catch (\Throwable $e) {} } try { db()->exec("CREATE TABLE IF NOT EXISTS users (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, role VARCHAR(20) DEFAULT 'admin', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); } catch (\Throwable $e) {} try { db()->exec("ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'admin'"); } catch (\Throwable $e) {} function qh_seed_demo_data(): void { $pdo = db(); $clinicCount = (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'clinic'")->fetchColumn(); if ($clinicCount === 0) { $insertClinic = $pdo->prepare( 'INSERT INTO hospital_queue_records (item_type, code, name_en, name_ar, requires_vitals, sort_order, status) VALUES (\'clinic\', :code, :name_en, :name_ar, :requires_vitals, :sort_order, \'active\')' ); $clinics = [ ['code' => 'GEN', 'name_en' => 'General Medicine', 'name_ar' => 'الطب العام', 'requires_vitals' => 1, 'sort_order' => 10], ['code' => 'PED', 'name_en' => 'Pediatrics', 'name_ar' => 'طب الأطفال', 'requires_vitals' => 0, 'sort_order' => 20], ['code' => 'CAR', 'name_en' => 'Cardiology', 'name_ar' => 'أمراض القلب', 'requires_vitals' => 1, 'sort_order' => 30], ]; foreach ($clinics as $clinic) { $insertClinic->execute($clinic); } } $clinicMap = []; foreach (qh_fetch_clinics() as $clinic) { $clinicMap[$clinic['code']] = $clinic['id']; } $doctorCount = (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'doctor'")->fetchColumn(); if ($doctorCount === 0 && $clinicMap !== []) { $insertDoctor = $pdo->prepare( 'INSERT INTO hospital_queue_records (item_type, name_en, name_ar, clinic_id, room_number, sort_order, status) VALUES (\'doctor\', :name_en, :name_ar, :clinic_id, :room_number, :sort_order, \'active\')' ); $doctors = [ ['name_en' => 'Dr. Sarah Malik', 'name_ar' => 'د. سارة مالك', 'clinic_id' => $clinicMap['GEN'] ?? null, 'room_number' => '201', 'sort_order' => 10], ['name_en' => 'Dr. Omar Nasser', 'name_ar' => 'د. عمر ناصر', 'clinic_id' => $clinicMap['GEN'] ?? null, 'room_number' => '202', 'sort_order' => 20], ['name_en' => 'Dr. Lina Haddad', 'name_ar' => 'د. لينا حداد', 'clinic_id' => $clinicMap['PED'] ?? null, 'room_number' => '103', 'sort_order' => 30], ['name_en' => 'Dr. Ahmad Kareem', 'name_ar' => 'د. أحمد كريم', 'clinic_id' => $clinicMap['CAR'] ?? null, 'room_number' => '305', 'sort_order' => 40], ]; foreach ($doctors as $doctor) { if ($doctor['clinic_id']) { $insertDoctor->execute($doctor); } } } $ticketCount = (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket'")->fetchColumn(); if ($ticketCount === 0) { $doctors = qh_fetch_doctors(); $doctorByClinic = []; foreach ($doctors as $doctor) { $doctorByClinic[$doctor['clinic_id']][] = $doctor; } foreach (qh_fetch_clinics() as $clinic) { if (empty($doctorByClinic[$clinic['id']])) { continue; } $assignedDoctor = $doctorByClinic[$clinic['id']][0]; qh_create_ticket( $clinic['name_en'] === 'General Medicine' ? 'Maha Ali' : 'Yousef Karim', (int) $clinic['id'], (int) $assignedDoctor['id'], $clinic['name_en'] === 'General Medicine' ? 'ar' : 'en', $clinic['requires_vitals'] ? 'waiting_vitals' : 'ready_for_doctor' ); if ($clinic['name_en'] !== 'General Medicine') { break; } } } } 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, news_ticker_en, news_ticker_ar) 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, :news_ticker_en, :news_ticker_ar)' ); $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', 'news_ticker_en' => 'Welcome to our hospital.', 'news_ticker_ar' => 'مرحباً بكم في مستشفانا.', ]); } function qh_h(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function qh_project_name(string $fallback = 'Hospital Queue'): string { $candidate = trim((string) ($_SERVER['PROJECT_NAME'] ?? $_SERVER['PROJECT_TITLE'] ?? '')); return $candidate !== '' ? $candidate : $fallback; } function qh_project_description(string $fallback = 'Bilingual hospital queue workflow for reception, nursing, doctors, and the public display.'): string { $candidate = trim((string) ($_SERVER['PROJECT_DESCRIPTION'] ?? '')); return $candidate !== '' ? $candidate : $fallback; } function qh_asset_version(string $relativePath): int { $fullPath = __DIR__ . '/' . ltrim($relativePath, '/'); 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'] ?? ''))); if (in_array($sessionLocale, ['en', 'ar'], true)) { $locale = $sessionLocale; } else { $profile = qh_fetch_hospital_profile(); $defaultLang = strtolower(trim((string) ($profile['default_language'] ?? 'en'))); $locale = in_array($defaultLang, ['en', 'ar'], true) ? $defaultLang : '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_ads.php' => [ 'label' => qh_t('Advertisements', 'الإعلانات'), 'description' => qh_t('Manage videos shown on the queue display.', 'إدارة الفيديوهات المعروضة على شاشة الطابور.'), 'icon' => 'display', ], '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_users.php' => [ 'label' => qh_t('Users', 'المستخدمون'), 'description' => qh_t('Manage system users and access.', 'إدارة مستخدمي النظام وصلاحيات الوصول.'), 'icon' => 'users', ], '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' => '', 'clinic' => '', 'doctor' => '', 'users' => '', default => '', }; } 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 '
'; echo '
'; echo ' ' . qh_h(qh_t('Admin panel', 'لوحة الإدارة')) . ''; echo '

' . qh_h(qh_hospital_name()) . '

'; echo '

' . qh_h(qh_hospital_tagline() !== '' ? qh_hospital_tagline() : qh_t('Move between dedicated pages instead of one long mixed admin screen.', 'تنقل بين صفحات مستقلة بدلاً من شاشة إدارة طويلة ومختلطة.')) . '

'; echo '
'; echo ' '; echo '
'; echo '
' . qh_h((string) ($stats['clinics'] ?? 0)) . '' . qh_h(qh_t('Clinics', 'العيادات')) . '
'; echo '
' . qh_h((string) ($stats['doctors'] ?? 0)) . '' . qh_h(qh_t('Doctors', 'الأطباء')) . '
'; echo '
' . qh_h((string) ($stats['vitals_clinics'] ?? 0)) . '' . qh_h(qh_t('Vitals-first clinics', 'العيادات التي تبدأ بالعلامات')) . '
'; echo '
'; echo '
'; } 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>%2$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'] ?? ''; $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) . ' lang-' . $locale; $assetVersionCss = qh_asset_version('assets/css/custom.css'); $primaryColor = qh_hospital_primary_color(); $secondaryColor = qh_hospital_secondary_color(); echo ''; echo ''; echo ''; echo ' '; echo ' '; echo ' ' . qh_h($fullTitle) . ''; echo ' '; if ($projectDescription) { echo ' '; echo ' '; } if ($projectImageUrl) { echo ' '; echo ' '; } if ($faviconUrl !== '') { echo ' '; echo ' '; } echo ' '; echo ' '; echo ' '; echo ' '; echo ' '; echo ''; $profile = qh_fetch_hospital_profile(); $timezone = qh_h($profile['timezone'] ?? 'UTC'); echo ''; if ($activePage !== 'display') { qh_render_nav($activePage); } echo '
'; qh_render_flash(); } function qh_page_end(): void { $profile = qh_fetch_hospital_profile(); $newsTicker = trim((string)($profile['news_ticker'] ?? '')); if ($newsTicker !== '') { echo '
'; echo '' . qh_h($newsTicker) . ''; echo '
'; } $assetVersionJs = qh_asset_version('assets/js/main.js'); echo '
'; echo ''; echo ''; echo ''; } function qh_render_nav(string $activePage): void { $links = [ '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', 'الطبيب')], 'users' => ['href' => qh_url('admin_users.php'), 'label' => qh_t('Users', 'المستخدمون')], 'display' => ['href' => qh_url('display.php'), 'label' => qh_t('Display', 'الشاشة')], ]; $logoUrl = qh_hospital_logo_url(); $tagline = qh_hospital_tagline(); echo '
'; echo ' '; echo '
'; } function qh_set_flash(string $type, string $message): void { $_SESSION['flash'] = ['type' => $type, 'message' => $message]; } function qh_render_flash(): void { if (empty($_SESSION['flash']) || !is_array($_SESSION['flash'])) { return; } $flash = $_SESSION['flash']; unset($_SESSION['flash']); $typeMap = [ 'success' => 'success', 'danger' => 'danger', 'warning' => 'warning', 'info' => 'primary', ]; $toastType = $typeMap[$flash['type']] ?? 'primary'; echo '
'; echo '
'; echo '
'; echo '
' . qh_h((string) $flash['message']) . '
'; echo ' '; echo '
'; echo '
'; echo '
'; } 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"); return $stmt->fetchAll(); } function qh_fetch_doctors(?int $clinicId = null): array { if ($clinicId) { $stmt = db()->prepare( "SELECT d.*, c.name_en AS clinic_name_en, c.name_ar AS clinic_name_ar, c.code AS clinic_code FROM hospital_queue_records d LEFT JOIN hospital_queue_records c ON c.id = d.clinic_id AND c.item_type = 'clinic' WHERE d.item_type = 'doctor' AND d.clinic_id = :clinic_id ORDER BY d.sort_order ASC, d.name_en ASC" ); $stmt->execute(['clinic_id' => $clinicId]); return $stmt->fetchAll(); } $stmt = db()->query( "SELECT d.*, c.name_en AS clinic_name_en, c.name_ar AS clinic_name_ar, c.code AS clinic_code FROM hospital_queue_records d LEFT JOIN hospital_queue_records c ON c.id = d.clinic_id AND c.item_type = 'clinic' WHERE d.item_type = 'doctor' ORDER BY c.sort_order ASC, d.sort_order ASC, d.name_en ASC" ); return $stmt->fetchAll(); } function qh_fetch_ticket(int $ticketId): ?array { $stmt = db()->prepare( "SELECT t.*, c.name_en AS clinic_name_en, c.name_ar AS clinic_name_ar, c.code AS clinic_code, c.requires_vitals AS clinic_requires_vitals, d.name_en AS doctor_name_en, d.name_ar AS doctor_name_ar, d.room_number AS doctor_room FROM hospital_queue_records t LEFT JOIN hospital_queue_records c ON c.id = t.clinic_id AND c.item_type = 'clinic' LEFT JOIN hospital_queue_records d ON d.id = t.doctor_id AND d.item_type = 'doctor' WHERE t.item_type = 'ticket' AND t.id = :ticket_id LIMIT 1" ); $stmt->execute(['ticket_id' => $ticketId]); $ticket = $stmt->fetch(); return $ticket ?: null; } function qh_fetch_tickets(array $statuses = [], ?int $doctorId = null, ?int $limit = null): array { $sql = "SELECT t.*, c.name_en AS clinic_name_en, c.name_ar AS clinic_name_ar, c.code AS clinic_code, c.requires_vitals AS clinic_requires_vitals, d.name_en AS doctor_name_en, d.name_ar AS doctor_name_ar, d.room_number AS doctor_room FROM hospital_queue_records t LEFT JOIN hospital_queue_records c ON c.id = t.clinic_id AND c.item_type = 'clinic' LEFT JOIN hospital_queue_records d ON d.id = t.doctor_id AND d.item_type = 'doctor' WHERE t.item_type = 'ticket' AND DATE(t.created_at) = CURDATE()"; $params = []; if ($statuses !== []) { $statusPlaceholders = []; foreach ($statuses as $index => $status) { $key = 'status_' . $index; $statusPlaceholders[] = ':' . $key; $params[$key] = $status; } $sql .= ' AND t.status IN (' . implode(', ', $statusPlaceholders) . ')'; } if ($doctorId) { $sql .= ' AND t.doctor_id = :doctor_id'; $params['doctor_id'] = $doctorId; } $sql .= ' ORDER BY COALESCE(t.called_at, t.created_at) DESC, t.id DESC'; if ($limit) { $sql .= ' LIMIT ' . (int) $limit; } $stmt = db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } function qh_queue_overview(): array { $sql = "SELECT c.id, c.name_en, c.name_ar, c.code, SUM(CASE WHEN t.status = 'waiting_vitals' THEN 1 ELSE 0 END) AS vitals_waiting, SUM(CASE WHEN t.status = 'ready_for_doctor' THEN 1 ELSE 0 END) AS doctor_waiting, SUM(CASE WHEN t.status IN ('called', 'in_progress') THEN 1 ELSE 0 END) AS active_calls, COUNT(t.id) AS total_today FROM hospital_queue_records c LEFT JOIN hospital_queue_records t ON t.clinic_id = c.id AND t.item_type = 'ticket' AND DATE(t.created_at) = CURDATE() WHERE c.item_type = 'clinic' GROUP BY c.id, c.name_en, c.name_ar, c.code ORDER BY c.sort_order ASC, c.name_en ASC"; return db()->query($sql)->fetchAll(); } function qh_dashboard_stats(): array { $pdo = db(); return [ 'issued_today' => (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE()")->fetchColumn(), 'waiting_vitals' => (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND status = 'waiting_vitals'")->fetchColumn(), 'ready_for_doctor' => (int) $pdo->query("SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND status = 'ready_for_doctor'")->fetchColumn(), 'active_rooms' => (int) $pdo->query("SELECT COUNT(DISTINCT doctor_id) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND status IN ('called', 'in_progress') AND doctor_id IS NOT NULL")->fetchColumn(), ]; } function qh_create_ticket(string $patientName, int $clinicId, int $doctorId, string $languagePref = 'en', ?string $forcedStatus = null): int { $pdo = db(); $clinic = qh_fetch_clinic($clinicId); if (!$clinic) { throw new RuntimeException(qh_t('Clinic not found.', 'لم يتم العثور على العيادة.')); } $doctor = qh_fetch_doctor($doctorId); if (!$doctor || (int) $doctor['clinic_id'] !== $clinicId) { throw new RuntimeException(qh_t('The doctor does not belong to the selected clinic.', 'الطبيب لا يتبع العيادة المحددة.')); } $ticketNumber = qh_generate_ticket_number($clinic['code']); $status = $forcedStatus ?: ((int) $clinic['requires_vitals'] === 1 ? 'waiting_vitals' : 'ready_for_doctor'); $stmt = $pdo->prepare( "INSERT INTO hospital_queue_records (item_type, clinic_id, doctor_id, patient_name, language_pref, ticket_number, status, display_note) VALUES ('ticket', :clinic_id, :doctor_id, :patient_name, :language_pref, :ticket_number, :status, :display_note)" ); $stmt->execute([ 'clinic_id' => $clinicId, 'doctor_id' => $doctorId, 'patient_name' => $patientName, 'language_pref' => in_array($languagePref, ['en', 'ar'], true) ? $languagePref : 'en', 'ticket_number' => $ticketNumber, 'status' => $status, 'display_note' => $status === 'waiting_vitals' ? 'Proceed to nursing vitals first.' : 'Wait for your doctor call on the public screen.', ]); return (int) $pdo->lastInsertId(); } function qh_generate_ticket_number(string $clinicCode): string { $prefix = strtoupper(substr(preg_replace('/[^A-Z0-9]/i', '', $clinicCode), 0, 3)); $stmt = db()->prepare( "SELECT COUNT(*) FROM hospital_queue_records WHERE item_type = 'ticket' AND DATE(created_at) = CURDATE() AND ticket_number LIKE :prefix" ); $stmt->execute(['prefix' => $prefix . '-%']); $next = ((int) $stmt->fetchColumn()) + 1; return $prefix . '-' . str_pad((string) $next, 3, '0', STR_PAD_LEFT); } function qh_fetch_clinic(int $clinicId): ?array { $stmt = db()->prepare("SELECT * FROM hospital_queue_records WHERE item_type = 'clinic' AND id = :id LIMIT 1"); $stmt->execute(['id' => $clinicId]); $row = $stmt->fetch(); return $row ?: null; } function qh_fetch_doctor(int $doctorId): ?array { $stmt = db()->prepare("SELECT * FROM hospital_queue_records WHERE item_type = 'doctor' AND id = :id LIMIT 1"); $stmt->execute(['id' => $doctorId]); $row = $stmt->fetch(); return $row ?: null; } function qh_status_meta(string $status): array { $map = [ 'waiting_vitals' => ['class' => 'warning', 'en' => 'Waiting for vitals', 'ar' => 'بانتظار العلامات الحيوية'], 'ready_for_doctor' => ['class' => 'info', 'en' => 'Ready for doctor', 'ar' => 'جاهز للطبيب'], 'called' => ['class' => 'primary', 'en' => 'Called', 'ar' => 'تم النداء'], 'in_progress' => ['class' => 'secondary', 'en' => 'In consultation', 'ar' => 'داخل العيادة'], 'done' => ['class' => 'success', 'en' => 'Completed', 'ar' => 'مكتمل'], 'no_show' => ['class' => 'danger', 'en' => 'No-show', 'ar' => 'لم يحضر'], ]; return $map[$status] ?? ['class' => 'light', 'en' => ucfirst(str_replace('_', ' ', $status)), 'ar' => $status]; } function qh_status_badge(string $status): string { $meta = qh_status_meta($status); return '' . qh_h(qh_t($meta['en'], $meta['ar'])) . ''; } function qh_call_message(array $ticket): array { $ticketNumber = $ticket['ticket_number'] ?? '---'; $doctorNameEn = $ticket['doctor_name_en'] ?? 'Doctor'; $doctorNameAr = $ticket['doctor_name_ar'] ?? 'الطبيب'; $room = $ticket['doctor_room'] ?? '--'; return [ 'en' => sprintf('Ticket %s, please proceed to room %s for %s.', $ticketNumber, $room, $doctorNameEn), 'ar' => sprintf('رقم التذكرة %s، يرجى التوجه إلى الغرفة %s إلى %s.', $ticketNumber, $room, $doctorNameAr), ]; } function qh_format_datetime(?string $value): string { if (!$value) { return '—'; } $timestamp = strtotime($value); return $timestamp ? date('M d, Y H:i', $timestamp) : '—'; } function qh_require_post(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { qh_set_flash('danger', qh_t('Invalid request method.', 'طريقة الطلب غير صالحة.')); qh_redirect('index.php'); } } function qh_handle_image_upload(string $inputName): string { if (!isset($_FILES[$inputName]) || $_FILES[$inputName]['error'] !== UPLOAD_ERR_OK) { return ''; } $tmpName = $_FILES[$inputName]['tmp_name']; $finfo = finfo_open(FILEINFO_MIME_TYPE); if ($finfo === false) return ''; $mime = finfo_file($finfo, $tmpName); finfo_close($finfo); if (!is_string($mime) || !str_starts_with($mime, 'image/')) { throw new InvalidArgumentException(qh_t('Invalid image format.', 'تنسيق صورة غير صالح.')); } $ext = match ($mime) { 'image/jpeg' => '.jpg', 'image/png' => '.png', 'image/gif' => '.gif', 'image/webp' => '.webp', 'image/svg+xml' => '.svg', 'image/x-icon' => '.ico', 'image/vnd.microsoft.icon' => '.ico', default => '.bin' }; if ($ext === '.bin') { throw new InvalidArgumentException(qh_t('Unsupported image type.', 'نوع الصورة غير مدعوم.')); } $uploadDir = __DIR__ . '/assets/images/uploads'; if (!is_dir($uploadDir)) { if (!mkdir($uploadDir, 0775, true)) { throw new RuntimeException(qh_t('Failed to create upload directory.', 'فشل إنشاء مجلد الرفع.')); } } $filename = uniqid('img_', true) . $ext; $dest = $uploadDir . '/' . $filename; if (!move_uploaded_file($tmpName, $dest)) { throw new RuntimeException(qh_t('Failed to save uploaded file.', 'فشل حفظ الملف المرفوع.')); } return 'assets/images/uploads/' . $filename; } function qh_admin_handle_request(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } $action = trim((string) ($_POST['action'] ?? '')); if ($action === '' || in_array($action, ['add_video', 'delete_video', 'toggle_status', 'add_news', 'delete_news', 'toggle_news_status', 'create_user', 'update_user', 'delete_user'])) { return; } $returnTo = qh_admin_return_to($_POST['return_to'] ?? 'admin.php'); $pdo = db(); try { if ($action === 'add_clinic') { $code = strtoupper(trim((string) ($_POST['code'] ?? ''))); $nameEn = trim((string) ($_POST['name_en'] ?? '')); $nameAr = trim((string) ($_POST['name_ar'] ?? '')); $requiresVitals = isset($_POST['requires_vitals']) ? 1 : 0; if ($code === '' || $nameEn === '' || $nameAr === '') { 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) VALUES ('clinic', :code, :name_en, :name_ar, :requires_vitals, :sort_order, 'active')" ); $stmt->execute([ 'code' => substr($code, 0, 10), 'name_en' => $nameEn, 'name_ar' => $nameAr, 'requires_vitals' => $requiresVitals, 'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1), ]); 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 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' => max((int) ($_POST['sort_order'] ?? 50), 1), 'clinic_id' => $clinicId, ]); 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(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) VALUES ('doctor', :name_en, :name_ar, :clinic_id, :room_number, :sort_order, 'active')" ); $stmt->execute([ 'name_en' => $nameEn, 'name_ar' => $nameAr, 'clinic_id' => $clinicId, 'room_number' => $roomNumber, 'sort_order' => max((int) ($_POST['sort_order'] ?? 50), 1), ]); 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 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' => max((int) ($_POST['sort_order'] ?? 50), 1), 'doctor_id' => $doctorId, ]); 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'] ?? '')); $newsTickerEn = trim((string) ($_POST['news_ticker_en'] ?? '')); $newsTickerAr = trim((string) ($_POST['news_ticker_ar'] ?? '')); $timezone = trim((string) ($_POST['timezone'] ?? '')); if ($timezone === '' || !in_array($timezone, timezone_identifiers_list(), true)) $timezone = 'UTC'; $defaultLanguage = trim((string) ($_POST['default_language'] ?? 'en')); if (!in_array($defaultLanguage, ['en', 'ar'], true)) $defaultLanguage = 'en'; $profile = qh_fetch_hospital_profile(); $logoUrl = $profile['logo_url'] ?? ''; $faviconUrl = $profile['favicon_url'] ?? ''; if (!empty($_POST['remove_logo'])) { $logoUrl = ''; } else { $newLogo = qh_handle_image_upload('logo_upload'); if ($newLogo !== '') $logoUrl = $newLogo; } if (!empty($_POST['remove_favicon'])) { $faviconUrl = ''; } else { $newFavicon = qh_handle_image_upload('favicon_upload'); if ($newFavicon !== '') $faviconUrl = $newFavicon; } $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.', 'يرجى إدخال رابط موقع صالح.')); } $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, news_ticker_en = :news_ticker_en, news_ticker_ar = :news_ticker_ar, timezone = :timezone, default_language = :default_language 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, 'news_ticker_en' => $newsTickerEn, 'news_ticker_ar' => $newsTickerAr, 'timezone' => $timezone, 'default_language' => $defaultLanguage, ]); qh_set_flash('success', qh_t('Hospital profile updated successfully.', 'تم تحديث ملف المستشفى بنجاح.')); } } catch (Throwable $exception) { qh_set_flash('danger', $exception->getMessage()); } qh_redirect($returnTo); } function qh_reception_handle_request(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } try { $patientName = trim((string) ($_POST['patient_name'] ?? '')); $clinicId = (int) ($_POST['clinic_id'] ?? 0); $doctorId = (int) ($_POST['doctor_id'] ?? 0); $languagePref = trim((string) ($_POST['language_pref'] ?? 'en')); if ($patientName === '' || $clinicId <= 0 || $doctorId <= 0) { 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', qh_t('Ticket issued successfully.', 'تم إصدار التذكرة بنجاح.')); qh_redirect('reception.php?ticket_id=' . $ticketId); } catch (Throwable $exception) { qh_set_flash('danger', $exception->getMessage()); qh_redirect('reception.php'); } } function qh_nursing_handle_request(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } try { $ticketId = (int) ($_POST['ticket_id'] ?? 0); $vitalsNotes = trim((string) ($_POST['vitals_notes'] ?? '')); if ($ticketId <= 0 || $vitalsNotes === '') { throw new InvalidArgumentException(qh_t('Please add a short vitals note before sending the patient forward.', 'يرجى إضافة ملاحظة قصيرة للعلامات الحيوية قبل إرسال المريض.')); } $stmt = db()->prepare( "UPDATE hospital_queue_records SET vitals_notes = :vitals_notes, status = 'ready_for_doctor', display_note = 'Vitals completed. Wait for doctor call.' WHERE item_type = 'ticket' AND id = :ticket_id AND status = 'waiting_vitals'" ); $stmt->execute([ 'vitals_notes' => $vitalsNotes, 'ticket_id' => $ticketId, ]); qh_set_flash('success', qh_t('Vitals captured and patient moved to the doctor queue.', 'تم حفظ العلامات الحيوية ونقل المريض إلى طابور الطبيب.')); } catch (Throwable $exception) { qh_set_flash('danger', $exception->getMessage()); } qh_redirect('nursing.php'); } function qh_doctor_handle_request(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } try { $ticketId = (int) ($_POST['ticket_id'] ?? 0); $doctorId = (int) ($_POST['doctor_id'] ?? 0); $action = trim((string) ($_POST['action'] ?? '')); $ticket = qh_fetch_ticket($ticketId); if (!$ticket || $doctorId <= 0 || (int) $ticket['doctor_id'] !== $doctorId) { throw new InvalidArgumentException(qh_t('That ticket is not available for the selected doctor.', 'هذه التذكرة غير متاحة للطبيب المحدد.')); } if ($action === 'call_ticket') { $message = qh_call_message($ticket); $stmt = db()->prepare( "UPDATE hospital_queue_records SET status = 'called', called_at = NOW(), display_note = :display_note WHERE item_type = 'ticket' AND id = :ticket_id" ); $stmt->execute([ 'display_note' => $message['en'], 'ticket_id' => $ticketId, ]); 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 SET status = 'in_progress', display_note = 'Patient is now in consultation.' WHERE item_type = 'ticket' AND id = :ticket_id" ); $stmt->execute(['ticket_id' => $ticketId]); qh_set_flash('success', qh_t('Consultation started.', 'بدأت الاستشارة.')); } elseif ($action === 'complete_ticket') { $stmt = db()->prepare( "UPDATE hospital_queue_records SET status = 'done', served_at = NOW(), display_note = 'Visit completed.' WHERE item_type = 'ticket' AND id = :ticket_id" ); $stmt->execute(['ticket_id' => $ticketId]); qh_set_flash('success', qh_t('Visit marked as completed.', 'تم إنهاء الزيارة.')); } elseif ($action === 'refer_ticket') { $referToDoctorId = (int) ($_POST["refer_to_doctor_id"] ?? 0); if ($referToDoctorId <= 0 || $referToDoctorId === $doctorId) { throw new InvalidArgumentException(qh_t('Please select a valid doctor to refer the patient to.', 'يرجى اختيار طبيب صالح لتحويل المريض إليه.')); } $stmt = db()->prepare( "UPDATE hospital_queue_records SET status = 'ready_for_doctor', doctor_id = :refer_to_doctor_id, display_note = 'Referred' WHERE item_type = 'ticket' AND id = :ticket_id" ); $stmt->execute([ 'refer_to_doctor_id' => $referToDoctorId, 'ticket_id' => $ticketId, ]); qh_set_flash('success', qh_t('Patient referred successfully.', 'تم تحويل المريض بنجاح.')); } elseif ($action === 'mark_no_show') { $stmt = db()->prepare( "UPDATE hospital_queue_records SET status = 'no_show', served_at = NOW(), display_note = 'Marked as no-show.' WHERE item_type = 'ticket' AND id = :ticket_id" ); $stmt->execute(['ticket_id' => $ticketId]); qh_set_flash('warning', qh_t('Patient marked as no-show.', 'تم تسجيل المريض كحالة عدم حضور.')); } qh_redirect('doctor.php?doctor_id=' . $doctorId); } catch (Throwable $exception) { qh_set_flash('danger', $exception->getMessage()); qh_redirect('doctor.php'); } }