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="">
+
+