From 2575e8e91eb5112d7f6b51cb6f5b6e69c91e15c0 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 17 Mar 2026 02:41:09 +0000 Subject: [PATCH] Autosave: 20260317-024109 --- .gemini/GEMINI_LAST_MESSAGE.txt | 21 +- api/queue.php | 179 ++++++++++++ check_patients_table.php | 9 + .../20260316_create_patient_queue.sql | 19 ++ debug_queue.php | 37 +++ includes/layout/header.php | 3 +- includes/pages/laboratory_inquiries.php | 252 ++++++++++++++++- includes/pages/queue_management.php | 256 +++++++++++++++++ lang.php | 38 ++- queue.php | 14 + queue_display.php | 265 ++++++++++++++++++ 11 files changed, 1081 insertions(+), 12 deletions(-) create mode 100644 api/queue.php create mode 100644 check_patients_table.php create mode 100644 db/migrations/20260316_create_patient_queue.sql create mode 100644 debug_queue.php create mode 100644 includes/pages/queue_management.php create mode 100644 queue.php create mode 100644 queue_display.php diff --git a/.gemini/GEMINI_LAST_MESSAGE.txt b/.gemini/GEMINI_LAST_MESSAGE.txt index b08b94e..4f250e6 100644 --- a/.gemini/GEMINI_LAST_MESSAGE.txt +++ b/.gemini/GEMINI_LAST_MESSAGE.txt @@ -1,14 +1,19 @@ Plan: -1. **Fix Syntax Error:** I identified that the JavaScript code for age calculation in `includes/layout/footer.php` contained Python-style comments (`#`), causing a syntax error. -2. **Correct Comments:** I created and executed a Python script (`fix_footer_js.py` and `fix_footer_js_v2.py`) to replace these with valid JavaScript comments (`//`). -3. **Verify:** I verified the file content to ensure the syntax is now correct. +1. **Fix 500 Error:** I identified and fixed a critical syntax error in `includes/actions.php` (a missing quote in the SQL query) that was crashing the application. +2. **Restore Token Logic:** I carefully re-implemented the token generation code in `includes/actions.php` to ensure it works correctly without breaking the visit recording process. +3. **Add Missing UI:** I added the **"Issue Token"** checkbox to the "Details" tab of the "Add Visit" modal in `includes/layout/footer.php`, which was missing from the previous attempt. +4. **Verify Integrity:** I confirmed that the `patient_queue` database table exists and that all new files (`queue.php`, `api/queue.php`, `queue_display.php`) have valid syntax. Changed: -* `includes/layout/footer.php`: Replaced invalid `#` comments with `//` in the JavaScript section at the end of the file. +* `includes/actions.php`: Fixed a syntax error and correctly implemented the token generation logic. +* `includes/layout/footer.php`: Added the "Issue Token" checkbox to the "Details" tab of the `recordVisitModal`. Notes: -* The automatic age-to-DOB calculation (and vice-versa) should now work correctly when you type in the "Age" field. -* The script listens for input on the Age field and sets the DOB to `YYYY-01-01`. +* The application should now load correctly without the 500 error. +* To use the token system: + 1. **Receptionist:** When adding a new visit, check the **"Issue Token"** box in the "Details" tab. + 2. **Queue Management:** Go to **"Queue Management"** in the sidebar to manage the queue (Call, Finish). + 3. **Display:** Click **"Open TV Display"** to open the public waiting list on a separate screen. +* Reminder: click Save in the editor to sync changes. -Next: Please try entering the age again in the "Add Patient" or "Edit Patient" modal. It should now automatically populate the Date of Birth field. -Reminder: Click Save in the editor to sync changes. \ No newline at end of file +Next: Please refresh your browser and try adding a visit with the "Issue Token" checkbox checked. Let me know if everything works as expected! \ No newline at end of file diff --git a/api/queue.php b/api/queue.php new file mode 100644 index 0000000..2054796 --- /dev/null +++ b/api/queue.php @@ -0,0 +1,179 @@ +prepare(" + SELECT MAX(token_number) + FROM patient_queue + WHERE department_id = ? + AND DATE(created_at) = ? + "); + $stmt->execute([$department_id, $today]); + $max_token = $stmt->fetchColumn(); + $next_token = ($max_token) ? $max_token + 1 : 1; + + // Insert + $stmt = $db->prepare(" + INSERT INTO patient_queue (patient_id, department_id, doctor_id, token_number, status, created_at) + VALUES (?, ?, ?, ?, 'waiting', NOW()) + "); + $stmt->execute([$patient_id, $department_id, $doctor_id ?: null, $next_token]); + $queue_id = $db->lastInsertId(); + + echo json_encode(['success' => true, 'message' => 'Token generated', 'token_number' => $next_token, 'queue_id' => $queue_id]); + exit; + } + + // --- LIST QUEUE --- + if ($action === 'list') { + $dept_id = $_GET['department_id'] ?? null; + $doc_id = $_GET['doctor_id'] ?? null; + $status = $_GET['status'] ?? null; // Can be comma separated 'waiting,serving' + $today = date('Y-m-d'); + + $where = "WHERE DATE(q.created_at) = ?"; + $params = [$today]; + + if ($dept_id) { + $where .= " AND q.department_id = ?"; + $params[] = $dept_id; + } + if ($doc_id) { + $where .= " AND (q.doctor_id = ? OR q.doctor_id IS NULL)"; + $params[] = $doc_id; + } + if ($status) { + $statuses = explode(',', $status); + $placeholders = implode(',', array_fill(0, count($statuses), '?')); + $where .= " AND q.status IN ($placeholders)"; + $params = array_merge($params, $statuses); + } + + $sql = " + SELECT q.*, + p.name as patient_name, + d.name_$lang as doctor_name, + d.name_en as doctor_name_en, + d.name_ar as doctor_name_ar, + dept.name_$lang as department_name, + dept.name_en as department_name_en, + dept.name_ar as department_name_ar + FROM patient_queue q + JOIN patients p ON q.patient_id = p.id + JOIN departments dept ON q.department_id = dept.id + LEFT JOIN doctors d ON q.doctor_id = d.id + $where + ORDER BY + CASE WHEN q.status = 'serving' THEN 1 WHEN q.status = 'waiting' THEN 2 ELSE 3 END, + q.token_number ASC + "; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $queue = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode(['success' => true, 'data' => $queue]); + exit; + } + + // --- UPDATE STATUS --- + if ($action === 'update_status') { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + throw new Exception('Invalid request method'); + } + + $queue_id = $_POST['queue_id'] ?? null; + $new_status = $_POST['status'] ?? null; + $doctor_id = $_POST['doctor_id'] ?? null; // If a doctor picks up a general department token + + if (!$queue_id || !$new_status) { + throw new Exception('Queue ID and Status are required'); + } + + if (!in_array($new_status, ['waiting', 'serving', 'completed', 'cancelled'])) { + throw new Exception('Invalid status'); + } + + // Logic: If setting to 'serving', update doctor_id if provided + $sql = "UPDATE patient_queue SET status = ?, updated_at = NOW()"; + $params = [$new_status]; + + if ($new_status === 'serving' && $doctor_id) { + $sql .= ", doctor_id = ?"; + $params[] = $doctor_id; + } + + $sql .= " WHERE id = ?"; + $params[] = $queue_id; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + + echo json_encode(['success' => true, 'message' => 'Status updated']); + exit; + } + + // --- SUMMARY --- + if ($action === 'summary') { + $today = date('Y-m-d'); + $dept_id = $_GET['department_id'] ?? null; + + $where = "WHERE DATE(q.created_at) = ?"; + $params = [$today]; + + if ($dept_id) { + $where .= " AND q.department_id = ?"; + $params[] = $dept_id; + } + + $sql = " + SELECT + dept.name_$lang as department_name, + dept.id as department_id, + SUM(CASE WHEN q.status = 'waiting' THEN 1 ELSE 0 END) as waiting, + SUM(CASE WHEN q.status = 'serving' THEN 1 ELSE 0 END) as serving, + SUM(CASE WHEN q.status = 'completed' THEN 1 ELSE 0 END) as completed + FROM patient_queue q + JOIN departments dept ON q.department_id = dept.id + $where + GROUP BY dept.id + "; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $summary = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode(['success' => true, 'data' => $summary]); + exit; + } + + throw new Exception('Invalid action'); + +} catch (Exception $e) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/check_patients_table.php b/check_patients_table.php new file mode 100644 index 0000000..469ea02 --- /dev/null +++ b/check_patients_table.php @@ -0,0 +1,9 @@ +query("DESCRIBE patients"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +foreach ($rows as $row) { + echo $row['Field'] . "\n"; +} + diff --git a/db/migrations/20260316_create_patient_queue.sql b/db/migrations/20260316_create_patient_queue.sql new file mode 100644 index 0000000..6ac5844 --- /dev/null +++ b/db/migrations/20260316_create_patient_queue.sql @@ -0,0 +1,19 @@ +-- Create patient_queue table +CREATE TABLE IF NOT EXISTS patient_queue ( + id INT AUTO_INCREMENT PRIMARY KEY, + patient_id INT NOT NULL, + department_id INT NOT NULL, + doctor_id INT NULL, + visit_id INT NULL, + token_number INT NOT NULL, + status ENUM('waiting', 'serving', 'completed', 'cancelled') DEFAULT 'waiting', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE, + FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE CASCADE, + FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE SET NULL, + FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE SET NULL +); + +-- Index for faster searching of today's queue +CREATE INDEX idx_queue_date_dept ON patient_queue(created_at, department_id); diff --git a/debug_queue.php b/debug_queue.php new file mode 100644 index 0000000..ebebf53 --- /dev/null +++ b/debug_queue.php @@ -0,0 +1,37 @@ +Patient Queue (Latest 5)"; +$stmt = $pdo->query("SELECT * FROM patient_queue ORDER BY id DESC LIMIT 5"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +if ($rows) { + echo ""; + foreach (array_keys($rows[0]) as $k) echo ""; + echo ""; + foreach ($rows as $row) { + echo ""; + foreach ($row as $v) echo ""; + echo ""; + } + echo "
$k
$v
"; +} else { + echo "No records in patient_queue.
"; +} + +echo "

Visits (Latest 5)

"; +$stmt = $pdo->query("SELECT id, patient_id, doctor_id, created_at FROM visits ORDER BY id DESC LIMIT 5"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +if ($rows) { + echo ""; + foreach (array_keys($rows[0]) as $k) echo ""; + echo ""; + foreach ($rows as $row) { + echo ""; + foreach ($row as $v) echo ""; + echo ""; + } + echo "
$k
$v
"; +} else { + echo "No records in visits.
"; +} diff --git a/includes/layout/header.php b/includes/layout/header.php index 681b887..9c7674d 100644 --- a/includes/layout/header.php +++ b/includes/layout/header.php @@ -102,6 +102,7 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp "> + @@ -197,4 +198,4 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp - + \ No newline at end of file diff --git a/includes/pages/laboratory_inquiries.php b/includes/pages/laboratory_inquiries.php index 3a62f8a..a60bfb3 100644 --- a/includes/pages/laboratory_inquiries.php +++ b/includes/pages/laboratory_inquiries.php @@ -89,7 +89,7 @@ unset($inquiry); - No inquiries found. + @@ -101,7 +101,7 @@ unset($inquiry);
- Linked to Visit # () + # ()
@@ -154,3 +154,251 @@ unset($inquiry); + + + + + + + + \ No newline at end of file diff --git a/includes/pages/queue_management.php b/includes/pages/queue_management.php new file mode 100644 index 0000000..d0b49a3 --- /dev/null +++ b/includes/pages/queue_management.php @@ -0,0 +1,256 @@ +query("SELECT * FROM departments")->fetchAll(PDO::FETCH_ASSOC); +// Fetch Doctors +$doctors = $db->query("SELECT * FROM doctors")->fetchAll(PDO::FETCH_ASSOC); +// Fetch Patients (Limit 50 for initial load, preferably use AJAX for real search) +$patients = $db->query("SELECT * FROM patients ORDER BY id DESC LIMIT 50")->fetchAll(PDO::FETCH_ASSOC); + +?> + +
+

+
+ + + + +
+
+ + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+ +
+ + + + + diff --git a/lang.php b/lang.php index 43cde8e..1ef4431 100644 --- a/lang.php +++ b/lang.php @@ -278,6 +278,24 @@ $translations = [ 'printed_on' => 'Printed on', 'no_appointments' => 'No appointments found for this date', 'back' => 'Back', + 'queue_management' => 'Queue Management', + 'issue_token' => 'Issue Token', + 'issue_new_token' => 'Issue New Token', + 'token' => 'Token', + 'open_tv_display' => 'Open TV Display', + 'all_departments' => 'All Departments', + 'all_statuses' => 'All Statuses', + 'waiting' => 'Waiting', + 'serving' => 'Serving', + 'wait_time' => 'Wait Time', + 'call' => 'Call', + 'finish' => 'Finish', + 'no_tokens_found' => 'No tokens found', + 'select_patient' => 'Select Patient', + 'doctor_optional' => 'Doctor (Optional)', + 'any_doctor' => 'Any Doctor', + 'showing_last_50_patients' => 'Showing last 50 patients', + 'queue_display' => 'Queue Display', ], 'ar' => [ 'attachment' => 'المرفق', @@ -559,5 +577,23 @@ $translations = [ 'printed_on' => 'طبع في', 'no_appointments' => 'لا توجد مواعيد لهذا التاريخ', 'back' => 'رجوع', + 'queue_management' => 'إدارة الطوابير', + 'issue_token' => 'إصدار تذكرة', + 'issue_new_token' => 'إصدار تذكرة جديدة', + 'token' => 'رقم التذكرة', + 'open_tv_display' => 'عرض شاشة التلفاز', + 'all_departments' => 'كل الأقسام', + 'all_statuses' => 'كل الحالات', + 'waiting' => 'في الانتظار', + 'serving' => 'جاري الخدمة', + 'wait_time' => 'وقت الانتظار', + 'call' => 'نداء', + 'finish' => 'إنهاء', + 'no_tokens_found' => 'لا توجد تذاكر', + 'select_patient' => 'اختر المريض', + 'doctor_optional' => 'الطبيب (اختياري)', + 'any_doctor' => 'أي طبيب', + 'showing_last_50_patients' => 'عرض آخر 50 مريض', + 'queue_display' => 'شاشة الانتظار', ] -]; \ No newline at end of file +]; diff --git a/queue.php b/queue.php new file mode 100644 index 0000000..2560831 --- /dev/null +++ b/queue.php @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/queue_display.php b/queue_display.php new file mode 100644 index 0000000..431a5a4 --- /dev/null +++ b/queue_display.php @@ -0,0 +1,265 @@ +query("SELECT * FROM departments")->fetchAll(PDO::FETCH_ASSOC); +} catch (Exception $e) { + die("Database error"); +} +?> + + + + + + Queue Display / شاشة الانتظار + + + + + + + +
+

Hospital Queue Status / حالة انتظار المستشفى

+
00:00:00
+
+ +
+
+ +
+
+ + + + + \ No newline at end of file