From 4ea57c75244b535ee4c8a198dcda7d2edd0789d8 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 15 Mar 2026 17:58:07 +0000 Subject: [PATCH] Autosave: 20260315-175807 --- api/appointments.php | 77 ++++++-- api/doctor_holidays.php | 53 ++++++ .../20260315_create_doctor_holidays.sql | 9 + includes/pages/appointments.php | 37 +++- includes/pages/doctors.php | 164 +++++++++++++++- lang.php | 12 +- print_appointments.php | 180 ++++++++++++++++++ 7 files changed, 507 insertions(+), 25 deletions(-) create mode 100644 api/doctor_holidays.php create mode 100644 db/migrations/20260315_create_doctor_holidays.sql create mode 100644 print_appointments.php diff --git a/api/appointments.php b/api/appointments.php index 671f763..874b712 100644 --- a/api/appointments.php +++ b/api/appointments.php @@ -29,7 +29,7 @@ if ($method === 'GET') { // Fetch Appointments $query = " SELECT - a.id, a.start_time as start, a.end_time as end, a.reason as title, a.status, + a.id, a.start_time as start, a.end_time as end, a.reason, a.status, a.patient_id, a.doctor_id, p.name as patient_name, d.name_$lang as doctor_name @@ -70,7 +70,7 @@ if ($method === 'GET') { ]; } - // Fetch Holidays + // Fetch Public Holidays $holidayQuery = "SELECT holiday_date as start, name_$lang as title FROM holidays WHERE 1=1"; $holidayParams = []; if ($startStr) { $holidayQuery .= " AND holiday_date >= ?"; $holidayParams[] = date('Y-m-d', strtotime($startStr)); } @@ -83,25 +83,70 @@ if ($method === 'GET') { foreach ($holidays as $h) { $events[] = [ 'id' => 'hol_' . $h['start'], - 'title' => 'Holiday: ' . $h['title'], - 'start' => $h['start'], - 'allDay' => true, - 'color' => '#ffc107', // yellow - 'textColor' => '#000', + 'title' => __('holiday') . ': ' . $h['title'], + 'start' => $h['start'] . 'T00:00:00', + 'end' => $h['start'] . 'T23:59:59', + 'allDay' => false, // Set to false to cover vertical time slots 'display' => 'background', - 'extendedProps' => ['type' => 'holiday'] - ]; - - $events[] = [ - 'title' => $h['title'], - 'start' => $h['start'], - 'allDay' => true, + 'backgroundColor' => '#ffc107', // Explicitly set background color 'color' => '#ffc107', - 'textColor' => '#000', - 'extendedProps' => ['type' => 'holiday'] + 'overlap' => false, + 'extendedProps' => ['type' => 'public_holiday'] ]; } + // Fetch Doctor Holidays + $docHolidayQuery = "SELECT dh.*, d.name_$lang as doctor_name FROM doctor_holidays dh JOIN doctors d ON dh.doctor_id = d.id WHERE 1=1"; + $docHolidayParams = []; + + // Date filtering for doctor holidays (ranges) + if ($startStr && $endStr) { + $docHolidayQuery .= " AND dh.start_date <= ? AND dh.end_date >= ?"; + $docHolidayParams[] = date('Y-m-d', strtotime($endStr)); + $docHolidayParams[] = date('Y-m-d', strtotime($startStr)); + } + + if ($doctor_id) { + $docHolidayQuery .= " AND dh.doctor_id = ?"; + $docHolidayParams[] = $doctor_id; + } + + $stmt = $db->prepare($docHolidayQuery); + $stmt->execute($docHolidayParams); + $docHolidays = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($docHolidays as $dh) { + $title = $dh['doctor_name'] . ' - ' . ($dh['note'] ?: 'Holiday'); + + // If filtering by specific doctor, show as background to block the day clearly + // If showing all doctors, show as a block event so we know WHO is on holiday + if ($doctor_id) { + $events[] = [ + 'id' => 'doc_hol_' . $dh['id'], + 'title' => 'Holiday', + 'start' => $dh['start_date'] . 'T00:00:00', + 'end' => date('Y-m-d', strtotime($dh['end_date'] . ' +1 day')) . 'T00:00:00', + 'allDay' => false, // Set to false to cover vertical time slots + 'display' => 'background', + 'backgroundColor' => '#ffc107', // Explicitly set background color + 'color' => '#ffc107', + 'overlap' => false, // Prevent booking on holiday + 'extendedProps' => ['type' => 'doctor_holiday'] + ]; + } else { + $events[] = [ + 'id' => 'doc_hol_' . $dh['id'], + 'title' => $title, + 'start' => $dh['start_date'], + 'end' => date('Y-m-d', strtotime($dh['end_date'] . ' +1 day')), + 'allDay' => true, + 'color' => '#fd7e14', // Orange + 'textColor' => '#fff', + 'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id']] + ]; + } + } + // Fetch Doctor Business Hours if ($doctor_id) { $scheduleStmt = $db->prepare("SELECT day_of_week as day, start_time as start, end_time as end FROM doctor_schedules WHERE doctor_id = ?"); diff --git a/api/doctor_holidays.php b/api/doctor_holidays.php new file mode 100644 index 0000000..6bf29b8 --- /dev/null +++ b/api/doctor_holidays.php @@ -0,0 +1,53 @@ + false, 'error' => 'Missing doctor_id']); + exit; + } + + $stmt = $db->prepare("SELECT * FROM doctor_holidays WHERE doctor_id = ? ORDER BY start_date DESC"); + $stmt->execute([$doctor_id]); + $holidays = $stmt->fetchAll(); + + echo json_encode(['success' => true, 'holidays' => $holidays]); + exit; +} + +if ($method === 'POST') { + $action = $input['action'] ?? ''; + + if ($action === 'create') { + $doctor_id = $input['doctor_id'] ?? null; + $start_date = $input['start_date'] ?? null; + $end_date = $input['end_date'] ?? null; + $note = $input['note'] ?? ''; + + if ($doctor_id && $start_date && $end_date) { + $stmt = $db->prepare("INSERT INTO doctor_holidays (doctor_id, start_date, end_date, note) VALUES (?, ?, ?, ?)"); + $stmt->execute([$doctor_id, $start_date, $end_date, $note]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Missing fields']); + } + } elseif ($action === 'delete') { + $id = $input['id'] ?? null; + if ($id) { + $stmt = $db->prepare("DELETE FROM doctor_holidays WHERE id = ?"); + $stmt->execute([$id]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Missing ID']); + } + } + exit; +} diff --git a/db/migrations/20260315_create_doctor_holidays.sql b/db/migrations/20260315_create_doctor_holidays.sql new file mode 100644 index 0000000..29ba88b --- /dev/null +++ b/db/migrations/20260315_create_doctor_holidays.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS doctor_holidays ( + id INT AUTO_INCREMENT PRIMARY KEY, + doctor_id INT NOT NULL, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + note VARCHAR(255) DEFAULT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE CASCADE +); diff --git a/includes/pages/appointments.php b/includes/pages/appointments.php index e89b944..c244a49 100644 --- a/includes/pages/appointments.php +++ b/includes/pages/appointments.php @@ -7,9 +7,14 @@

- +
+ + +
@@ -151,7 +156,9 @@ function showCreateModal(startTime = null) { $('#apt_doctor_id').val(document.getElementById('doctorFilter').value).trigger('change'); } - var modal = new bootstrap.Modal(document.getElementById('appointmentDetailsModal')); + // Initialize/reset modal + var modalEl = document.getElementById('appointmentDetailsModal'); + var modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } @@ -175,7 +182,9 @@ function saveAppointment() { .then(response => response.json()) .then(result => { if (result.success) { - bootstrap.Modal.getInstance(document.getElementById('appointmentDetailsModal')).hide(); + var modalEl = document.getElementById('appointmentDetailsModal'); + var modal = bootstrap.Modal.getInstance(modalEl); + modal.hide(); calendar.refetchEvents(); } else { alert('Error: ' + (result.error || 'Unknown error')); @@ -195,7 +204,9 @@ function deleteAppointment() { .then(response => response.json()) .then(result => { if (result.success) { - bootstrap.Modal.getInstance(document.getElementById('appointmentDetailsModal')).hide(); + var modalEl = document.getElementById('appointmentDetailsModal'); + var modal = bootstrap.Modal.getInstance(modalEl); + modal.hide(); calendar.refetchEvents(); } else { alert('Error: ' + (result.error || 'Unknown error')); @@ -203,6 +214,17 @@ function deleteAppointment() { }); } +function printAppointments() { + var doctorId = document.getElementById('doctorFilter').value; + // Get the date that is currently focused in the calendar + var date = calendar.getDate(); + // Format as YYYY-MM-DD using local time to avoid timezone shifts + var offset = date.getTimezoneOffset() * 60000; + var dateStr = (new Date(date.getTime() - offset)).toISOString().slice(0, 10); + + window.open('print_appointments.php?date=' + dateStr + '&doctor_id=' + doctorId, '_blank'); +} + document.addEventListener('DOMContentLoaded', function() { var calendarEl = document.getElementById('calendar'); var doctorFilter = document.getElementById('doctorFilter'); @@ -254,7 +276,8 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('apt_reason').value = props.reason || ''; document.getElementById('btnDeleteApt').style.display = 'block'; - var modal = new bootstrap.Modal(document.getElementById('appointmentDetailsModal')); + var modalEl = document.getElementById('appointmentDetailsModal'); + var modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } } diff --git a/includes/pages/doctors.php b/includes/pages/doctors.php index 73cb578..7aeeb75 100644 --- a/includes/pages/doctors.php +++ b/includes/pages/doctors.php @@ -73,6 +73,11 @@ if (isset($_GET['ajax_search'])) { data-bs-toggle="tooltip" title=""> + + + + + + + + + +function showDoctorHolidays(doctorId, doctorName) { + document.getElementById('holidayDoctorId').value = doctorId; + document.getElementById('doctorHolidaysTitle').innerText = 'Holidays - ' + doctorName; + + // Clear form + document.getElementById('holidayStartDate').value = ''; + document.getElementById('holidayEndDate').value = ''; + document.getElementById('holidayNote').value = ''; + + // Fetch holidays + fetchHolidays(doctorId); + + var modal = new bootstrap.Modal(document.getElementById('doctorHolidaysModal')); + modal.show(); +} + +function fetchHolidays(doctorId) { + fetch('api/doctor_holidays.php?doctor_id=' + doctorId) + .then(response => response.json()) + .then(data => { + const tbody = document.getElementById('holidaysListBody'); + tbody.innerHTML = ''; + + if (data.success && data.holidays.length > 0) { + data.holidays.forEach(h => { + tbody.innerHTML += ` + + ${h.start_date} + ${h.end_date} + ${h.note || '-'} + + + + + `; + }); + } else { + tbody.innerHTML = 'No holidays found.'; + } + }) + .catch(err => console.error(err)); +} + +function addDoctorHoliday() { + const doctorId = document.getElementById('holidayDoctorId').value; + const start = document.getElementById('holidayStartDate').value; + const end = document.getElementById('holidayEndDate').value; + const note = document.getElementById('holidayNote').value; + + if (!start || !end) return; + + fetch('api/doctor_holidays.php', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + action: 'create', + doctor_id: doctorId, + start_date: start, + end_date: end, + note: note + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + fetchHolidays(doctorId); // Refresh list + document.getElementById('holidayStartDate').value = ''; + document.getElementById('holidayEndDate').value = ''; + document.getElementById('holidayNote').value = ''; + } else { + alert('Error adding holiday: ' + (data.error || 'Unknown error')); + } + }) + .catch(err => console.error(err)); +} + +function deleteDoctorHoliday(id) { + if (!confirm('Delete this holiday?')) return; + + const doctorId = document.getElementById('holidayDoctorId').value; + + fetch('api/doctor_holidays.php', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + action: 'delete', + id: id + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + fetchHolidays(doctorId); // Refresh list + } else { + alert('Error deleting holiday: ' + (data.error || 'Unknown error')); + } + }) + .catch(err => console.error(err)); +} + \ No newline at end of file diff --git a/lang.php b/lang.php index aceab68..ad3bcd5 100644 --- a/lang.php +++ b/lang.php @@ -270,6 +270,11 @@ $translations = [ 'active' => 'Active', 'inactive' => 'Inactive', 'are_you_sure_delete_service' => 'Are you sure you want to delete this service?', + 'appointments_list' => 'Appointments List', + 'doctor_holidays' => 'Doctor Holidays', + 'printed_on' => 'Printed on', + 'no_appointments' => 'No appointments found for this date', + 'back' => 'Back', ], 'ar' => [ 'attachment' => 'المرفق', @@ -543,5 +548,10 @@ $translations = [ 'active' => 'نشط', 'inactive' => 'غير نشط', 'are_you_sure_delete_service' => 'هل أنت متأكد أنك تريد حذف هذه الخدمة؟', + 'appointments_list' => 'قائمة المواعيد', + 'doctor_holidays' => 'إجازات الأطباء', + 'printed_on' => 'طبع في', + 'no_appointments' => 'لا توجد مواعيد لهذا التاريخ', + 'back' => 'رجوع', ] -]; +]; \ No newline at end of file diff --git a/print_appointments.php b/print_appointments.php new file mode 100644 index 0000000..942ece8 --- /dev/null +++ b/print_appointments.php @@ -0,0 +1,180 @@ +prepare("SELECT name_$lang FROM doctors WHERE id = ?"); + $stmt->execute([$doctor_id]); + $doctor_name = $stmt->fetchColumn(); +} + +// Fetch Holidays for the date +$holidays_on_date = []; +if ($doctor_id) { + // Specific doctor holiday check + $stmt = $db->prepare("SELECT note FROM doctor_holidays WHERE doctor_id = ? AND ? BETWEEN start_date AND end_date"); + $stmt->execute([$doctor_id, $date]); + $holiday_note = $stmt->fetchColumn(); + if ($holiday_note !== false) { + $holidays_on_date[] = [ + 'doctor_name' => $doctor_name, + 'note' => $holiday_note + ]; + } +} else { + // All doctors holiday check + $stmt = $db->prepare(" + SELECT d.name_$lang as doctor_name, h.note + FROM doctor_holidays h + JOIN doctors d ON h.doctor_id = d.id + WHERE ? BETWEEN h.start_date AND h.end_date + "); + $stmt->execute([$date]); + $holidays_on_date = $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +// Fetch Appointments +$query = " + SELECT + a.start_time, a.end_time, a.status, a.reason, + p.name as patient_name, p.tel as patient_tel, + d.name_$lang as doctor_name + FROM appointments a + JOIN patients p ON a.patient_id = p.id + JOIN doctors d ON a.doctor_id = d.id + WHERE DATE(a.start_time) = ? +"; +$params = [$date]; + +if ($doctor_id) { + $query .= " AND a.doctor_id = ?"; + $params[] = $doctor_id; +} + +$query .= " ORDER BY a.start_time ASC"; + +$stmt = $db->prepare($query); +$stmt->execute($params); +$appointments = $stmt->fetchAll(); + +?> + + + + + + <?php echo __('appointments'); ?> - <?php echo $date; ?> + + + + + +
+
+ + +
+ +
+

+

:

+ +

:

+ +
+ + +
+
⚠️
+
    + +
  • + : + +
  • + +
+
+ + + +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/
+ + + +
+ + +
+

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