prepare("SELECT * FROM appointments WHERE id = ?"); $stmt->execute([$id]); $appointment = $stmt->fetch(PDO::FETCH_ASSOC); echo json_encode($appointment); exit; } $startStr = $_GET['start'] ?? null; $endStr = $_GET['end'] ?? null; $doctor_id = $_GET['doctor_id'] ?? null; $events = []; $businessHours = []; // Fetch Appointments $query = " SELECT a.id, a.start_time as start, a.end_time as end, a.reason, a.status, a.patient_id, a.doctor_id, a.nurse_id, a.visit_type, a.address, p.name as patient_name, d.name_$lang as doctor_name, n.name_$lang as nurse_name FROM appointments a JOIN patients p ON a.patient_id = p.id LEFT JOIN doctors d ON a.doctor_id = d.id LEFT JOIN nurses n ON a.nurse_id = n.id WHERE 1=1"; $params = []; if ($startStr) { $query .= " AND a.start_time >= ?"; $params[] = $startStr; } if ($endStr) { $query .= " AND a.start_time <= ?"; $params[] = $endStr; } if ($doctor_id) { $query .= " AND a.doctor_id = ?"; $params[] = $doctor_id; } try { $stmt = $db->prepare($query); $stmt->execute($params); $appointments = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { error_log("Appointments Fetch Error: " . $e->getMessage()); $appointments = []; } foreach ($appointments as $a) { $color = '#0d6efd'; // blue if ($a['status'] === 'Completed') $color = '#198754'; // green if ($a['status'] === 'Cancelled') $color = '#dc3545'; // red if ($a['visit_type'] === 'Home') $color = '#fd7e14'; // orange for home visits $providerName = $a['doctor_name'] ? $a['doctor_name'] : ($a['nurse_name'] . ' (' . __('nurse') . ')'); $title = $a['patient_name'] . ' - ' . $providerName; if ($a['visit_type'] === 'Home') { $title = '[🏠] ' . $title; } $events[] = [ 'id' => $a['id'], 'title' => $title, 'start' => $a['start'], 'end' => $a['end'], 'color' => $color, 'extendedProps' => [ 'type' => 'appointment', 'patient_id' => $a['patient_id'], 'doctor_id' => $a['doctor_id'], 'nurse_id' => $a['nurse_id'], 'visit_type' => $a['visit_type'], 'address' => $a['address'], 'patient_name' => $a['patient_name'], 'doctor_name' => $a['doctor_name'], 'nurse_name' => $a['nurse_name'], 'status' => $a['status'], 'reason' => $a['reason'] ] ]; } // 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)); } if ($endStr) { $holidayQuery .= " AND holiday_date <= ?"; $holidayParams[] = date('Y-m-d', strtotime($endStr)); } try { $stmt = $db->prepare($holidayQuery); $stmt->execute($holidayParams); $holidays = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { $holidays = []; } $s = get_system_settings(); $global_start = $s['working_hours_start'] ?? '08:00'; $global_end = $s['working_hours_end'] ?? '17:00'; foreach ($holidays as $h) { // Render a visible block event in the time grid $events[] = [ 'id' => 'hol_block_' . $h['start'], 'title' => __('holiday') . ': ' . $h['title'], 'start' => $h['start'] . 'T' . $global_start . ':00', 'end' => $h['start'] . 'T' . $global_end . ':00', 'allDay' => false, 'color' => '#dc3545', 'textColor' => '#fff', 'className' => 'public-holiday-event', 'extendedProps' => ['type' => 'public_holiday_block', 'blocks_selection' => true] ]; // Render daily blocks for time grid $events[] = [ 'id' => 'hol_bg_' . $h['start'], 'start' => $h['start'] . 'T00:00:00', 'end' => $h['start'] . 'T23:59:59', 'allDay' => false, 'display' => 'background', 'backgroundColor' => 'rgba(255, 193, 7, 0.5)', 'overlap' => false, 'extendedProps' => ['type' => 'public_holiday', 'blocks_selection' => true] ]; // Visible event strip across the top $events[] = [ 'id' => 'hol_title_' . $h['start'], 'title' => __('holiday') . ': ' . $h['title'], 'start' => $h['start'], 'end' => $h['start'], 'allDay' => true, 'color' => '#ffc107', 'textColor' => '#000', '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; } try { $stmt = $db->prepare($docHolidayQuery); $stmt->execute($docHolidayParams); $docHolidays = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { $docHolidays = []; } foreach ($docHolidays as $dh) { $title = $dh['doctor_name'] . ' - ' . ($dh['note'] ?: 'Holiday'); $endDayStr = date('Y-m-d', strtotime($dh['end_date'] . ' +1 day')); $currentDate = strtotime($dh['start_date']); $endDate = strtotime($dh['end_date']); $isFilteredDoctor = ($doctor_id && $doctor_id == $dh['doctor_id']); // Output background events for each day individually while ($currentDate <= $endDate) { $dateStr = date('Y-m-d', $currentDate); $events[] = [ 'id' => 'doc_hol_block_' . $dh['id'] . '_' . $dateStr, 'title' => 'Holiday: ' . $dh['doctor_name'], 'start' => $dateStr . 'T' . $global_start . ':00', 'end' => $dateStr . 'T' . $global_end . ':00', 'allDay' => false, 'color' => '#ffc107', 'textColor' => '#000', 'className' => 'doctor-holiday-event', 'extendedProps' => ['type' => 'doctor_holiday_block', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor] ]; $events[] = [ 'id' => 'doc_hol_bg_' . $dh['id'] . '_' . $dateStr, 'start' => $dateStr . 'T00:00:00', 'end' => $dateStr . 'T23:59:59', 'display' => 'background', 'allDay' => false, 'backgroundColor' => $isFilteredDoctor ? 'rgba(255, 193, 7, 0.5)' : 'rgba(255, 193, 7, 0.15)', 'overlap' => !$isFilteredDoctor, 'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor] ]; $currentDate = strtotime('+1 day', $currentDate); } $events[] = [ 'id' => 'doc_hol_' . $dh['id'], 'title' => $title, 'start' => $dh['start_date'], 'end' => $endDayStr, 'allDay' => true, 'color' => '#ffc107', 'textColor' => '#000', 'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor] ]; } // 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 = ?"); $scheduleStmt->execute([$doctor_id]); $schedules = $scheduleStmt->fetchAll(PDO::FETCH_ASSOC); $bhMap = []; foreach ($schedules as $s) { $key = $s['start'] . '-' . $s['end']; if (!isset($bhMap[$key])) { $bhMap[$key] = [ 'daysOfWeek' => [], 'startTime' => $s['start'], 'endTime' => $s['end'] ]; } $bhMap[$key]['daysOfWeek'][] = (int)$s['day']; } $businessHours = array_values($bhMap); } else { $st = $s['working_hours_start'] ?? '08:00'; $et = $s['working_hours_end'] ?? '17:00'; $businessHours = [ [ 'daysOfWeek' => [0, 1, 2, 3, 4, 5, 6], 'startTime' => $st, 'endTime' => $et ] ]; } echo json_encode([ 'events' => $events, 'businessHours' => $businessHours, 'settings' => [ 'working_hours_start' => $global_start, 'working_hours_end' => $global_end ] ]); exit; } function checkDoctorHoliday($db, $doctor_id, $start_time) { if (!$doctor_id || !$start_time) return false; $date = date('Y-m-d', strtotime($start_time)); try { $stmt = $db->prepare("SELECT COUNT(*) FROM doctor_holidays WHERE doctor_id = ? AND ? BETWEEN start_date AND end_date"); $stmt->execute([$doctor_id, $date]); return $stmt->fetchColumn() > 0; } catch (PDOException $e) { error_log("Check Holiday Error: " . $e->getMessage()); return false; } } if ($method === 'POST') { $input = json_decode(file_get_contents('php://input'), true); if (!$input) { $input = $_POST; } if (empty($input)) { echo json_encode(['success' => false, 'error' => 'No input data received']); exit; } $action = $input['action'] ?? ''; if (empty($action)) { echo json_encode(['success' => false, 'error' => 'No action specified']); exit; } if ($action === 'create') { $patient_id = $input['patient_id'] ?? ''; $doctor_id = $input['doctor_id'] ?: null; // Nullable $nurse_id = $input['nurse_id'] ?: null; // Nullable $visit_type = $input['visit_type'] ?? 'Clinic'; $address = $input['address'] ?? ''; $start_time = $input['start_time'] ?? ''; $reason = $input['reason'] ?? ''; if ($patient_id && ($doctor_id || $nurse_id) && $start_time) { // Check for holiday conflict if doctor assigned if ($doctor_id && checkDoctorHoliday($db, $doctor_id, $start_time)) { echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']); exit; } try { $stmt = $db->prepare("INSERT INTO appointments (patient_id, doctor_id, nurse_id, visit_type, address, start_time, end_time, reason) VALUES (?, ?, ?, ?, ?, ?, DATE_ADD(?, INTERVAL 30 MINUTE), ?)"); $stmt->execute([$patient_id, $doctor_id, $nurse_id, $visit_type, $address, $start_time, $start_time, $reason]); echo json_encode(['success' => true, 'id' => $db->lastInsertId()]); } catch (PDOException $e) { echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]); } } else { echo json_encode(['success' => false, 'error' => 'Missing fields']); } } elseif ($action === 'update') { $id = $input['id'] ?? ''; $patient_id = $input['patient_id'] ?? ''; $doctor_id = $input['doctor_id'] ?: null; $nurse_id = $input['nurse_id'] ?: null; $visit_type = $input['visit_type'] ?? 'Clinic'; $address = $input['address'] ?? ''; $start_time = $input['start_time'] ?? ''; $status = $input['status'] ?? 'Scheduled'; $reason = $input['reason'] ?? ''; if ($id && $patient_id && ($doctor_id || $nurse_id) && $start_time) { // Check for holiday conflict if ($doctor_id && checkDoctorHoliday($db, $doctor_id, $start_time)) { echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']); exit; } try { $stmt = $db->prepare("UPDATE appointments SET patient_id = ?, doctor_id = ?, nurse_id = ?, visit_type = ?, address = ?, start_time = ?, end_time = DATE_ADD(?, INTERVAL 30 MINUTE), status = ?, reason = ? WHERE id = ?"); $stmt->execute([$patient_id, $doctor_id, $nurse_id, $visit_type, $address, $start_time, $start_time, $status, $reason, $id]); echo json_encode(['success' => true]); } catch (PDOException $e) { echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]); } } else { echo json_encode(['success' => false, 'error' => 'Missing fields']); } } elseif ($action === 'delete') { $id = $input['id'] ?? ''; if ($id) { try { $stmt = $db->prepare("DELETE FROM appointments WHERE id = ?"); $stmt->execute([$id]); echo json_encode(['success' => true]); } catch (PDOException $e) { echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]); } } else { echo json_encode(['success' => false, 'error' => 'Missing ID']); } } else { echo json_encode(['success' => false, 'error' => 'Invalid action']); } exit; }