Autosave: 20260315-175807
This commit is contained in:
parent
5420bde76a
commit
4ea57c7524
@ -29,7 +29,7 @@ if ($method === 'GET') {
|
|||||||
// Fetch Appointments
|
// Fetch Appointments
|
||||||
$query = "
|
$query = "
|
||||||
SELECT
|
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,
|
a.patient_id, a.doctor_id,
|
||||||
p.name as patient_name,
|
p.name as patient_name,
|
||||||
d.name_$lang as doctor_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";
|
$holidayQuery = "SELECT holiday_date as start, name_$lang as title FROM holidays WHERE 1=1";
|
||||||
$holidayParams = [];
|
$holidayParams = [];
|
||||||
if ($startStr) { $holidayQuery .= " AND holiday_date >= ?"; $holidayParams[] = date('Y-m-d', strtotime($startStr)); }
|
if ($startStr) { $holidayQuery .= " AND holiday_date >= ?"; $holidayParams[] = date('Y-m-d', strtotime($startStr)); }
|
||||||
@ -83,25 +83,70 @@ if ($method === 'GET') {
|
|||||||
foreach ($holidays as $h) {
|
foreach ($holidays as $h) {
|
||||||
$events[] = [
|
$events[] = [
|
||||||
'id' => 'hol_' . $h['start'],
|
'id' => 'hol_' . $h['start'],
|
||||||
'title' => 'Holiday: ' . $h['title'],
|
'title' => __('holiday') . ': ' . $h['title'],
|
||||||
'start' => $h['start'],
|
'start' => $h['start'] . 'T00:00:00',
|
||||||
'allDay' => true,
|
'end' => $h['start'] . 'T23:59:59',
|
||||||
'color' => '#ffc107', // yellow
|
'allDay' => false, // Set to false to cover vertical time slots
|
||||||
'textColor' => '#000',
|
|
||||||
'display' => 'background',
|
'display' => 'background',
|
||||||
'extendedProps' => ['type' => 'holiday']
|
'backgroundColor' => '#ffc107', // Explicitly set background color
|
||||||
];
|
|
||||||
|
|
||||||
$events[] = [
|
|
||||||
'title' => $h['title'],
|
|
||||||
'start' => $h['start'],
|
|
||||||
'allDay' => true,
|
|
||||||
'color' => '#ffc107',
|
'color' => '#ffc107',
|
||||||
'textColor' => '#000',
|
'overlap' => false,
|
||||||
'extendedProps' => ['type' => 'holiday']
|
'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
|
// Fetch Doctor Business Hours
|
||||||
if ($doctor_id) {
|
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 = $db->prepare("SELECT day_of_week as day, start_time as start, end_time as end FROM doctor_schedules WHERE doctor_id = ?");
|
||||||
|
|||||||
53
api/doctor_holidays.php
Normal file
53
api/doctor_holidays.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
require_once __DIR__ . '/../helpers.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||||
|
|
||||||
|
if ($method === 'GET') {
|
||||||
|
$doctor_id = $_GET['doctor_id'] ?? null;
|
||||||
|
if (!$doctor_id) {
|
||||||
|
echo json_encode(['success' => 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;
|
||||||
|
}
|
||||||
9
db/migrations/20260315_create_doctor_holidays.sql
Normal file
9
db/migrations/20260315_create_doctor_holidays.sql
Normal file
@ -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
|
||||||
|
);
|
||||||
@ -7,10 +7,15 @@
|
|||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h3 class="fw-bold text-secondary"><?php echo __('appointments'); ?></h3>
|
<h3 class="fw-bold text-secondary"><?php echo __('appointments'); ?></h3>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-outline-secondary shadow-sm me-2" onclick="printAppointments()">
|
||||||
|
<i class="bi bi-printer me-1"></i> Print Daily List
|
||||||
|
</button>
|
||||||
<button class="btn btn-primary shadow-sm" onclick="showCreateModal()">
|
<button class="btn btn-primary shadow-sm" onclick="showCreateModal()">
|
||||||
<i class="bi bi-calendar-plus me-1"></i> <?php echo __('book_appointment'); ?>
|
<i class="bi bi-calendar-plus me-1"></i> <?php echo __('book_appointment'); ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="card shadow-sm border-0 mb-4">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
@ -151,7 +156,9 @@ function showCreateModal(startTime = null) {
|
|||||||
$('#apt_doctor_id').val(document.getElementById('doctorFilter').value).trigger('change');
|
$('#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();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +182,9 @@ function saveAppointment() {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result.success) {
|
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();
|
calendar.refetchEvents();
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (result.error || 'Unknown error'));
|
alert('Error: ' + (result.error || 'Unknown error'));
|
||||||
@ -195,7 +204,9 @@ function deleteAppointment() {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result.success) {
|
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();
|
calendar.refetchEvents();
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (result.error || 'Unknown error'));
|
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() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
var doctorFilter = document.getElementById('doctorFilter');
|
var doctorFilter = document.getElementById('doctorFilter');
|
||||||
@ -254,7 +276,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.getElementById('apt_reason').value = props.reason || '';
|
document.getElementById('apt_reason').value = props.reason || '';
|
||||||
document.getElementById('btnDeleteApt').style.display = 'block';
|
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();
|
modal.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,11 @@ if (isset($_GET['ajax_search'])) {
|
|||||||
data-bs-toggle="tooltip" title="<?php echo __('edit'); ?>">
|
data-bs-toggle="tooltip" title="<?php echo __('edit'); ?>">
|
||||||
<i class="bi bi-pencil-square"></i>
|
<i class="bi bi-pencil-square"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-link text-warning py-1 px-2 border-end"
|
||||||
|
onclick="showDoctorHolidays(<?php echo $d['id']; ?>, '<?php echo htmlspecialchars($d['name_'.$lang]); ?>')"
|
||||||
|
data-bs-toggle="tooltip" title="Holidays">
|
||||||
|
<i class="bi bi-calendar-x"></i>
|
||||||
|
</button>
|
||||||
<button class="btn btn-link text-danger py-1 px-2"
|
<button class="btn btn-link text-danger py-1 px-2"
|
||||||
onclick="showDeleteDoctorModal(<?php echo $d['id']; ?>)"
|
onclick="showDeleteDoctorModal(<?php echo $d['id']; ?>)"
|
||||||
data-bs-toggle="tooltip" title="<?php echo __('delete'); ?>">
|
data-bs-toggle="tooltip" title="<?php echo __('delete'); ?>">
|
||||||
@ -219,6 +224,11 @@ if (isset($_GET['ajax_search'])) {
|
|||||||
data-bs-toggle="tooltip" title="<?php echo __('edit'); ?>">
|
data-bs-toggle="tooltip" title="<?php echo __('edit'); ?>">
|
||||||
<i class="bi bi-pencil-square"></i>
|
<i class="bi bi-pencil-square"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-link text-warning py-1 px-2 border-end"
|
||||||
|
onclick="showDoctorHolidays(<?php echo $d['id']; ?>, '<?php echo htmlspecialchars($d['name_'.$lang]); ?>')"
|
||||||
|
data-bs-toggle="tooltip" title="Holidays">
|
||||||
|
<i class="bi bi-calendar-x"></i>
|
||||||
|
</button>
|
||||||
<button class="btn btn-link text-danger py-1 px-2"
|
<button class="btn btn-link text-danger py-1 px-2"
|
||||||
onclick="showDeleteDoctorModal(<?php echo $d['id']; ?>)"
|
onclick="showDeleteDoctorModal(<?php echo $d['id']; ?>)"
|
||||||
data-bs-toggle="tooltip" title="<?php echo __('delete'); ?>">
|
data-bs-toggle="tooltip" title="<?php echo __('delete'); ?>">
|
||||||
@ -285,6 +295,56 @@ if (isset($_GET['ajax_search'])) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Doctor Holidays Modal -->
|
||||||
|
<div class="modal fade" id="doctorHolidaysModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header border-0 pb-0">
|
||||||
|
<h5 class="modal-title fw-bold text-secondary" id="doctorHolidaysTitle">Doctor Holidays</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body pt-3">
|
||||||
|
<input type="hidden" id="holidayDoctorId">
|
||||||
|
<form id="addHolidayForm" onsubmit="addDoctorHoliday(); return false;">
|
||||||
|
<div class="row g-2 mb-3">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="small text-muted">Start Date</label>
|
||||||
|
<input type="date" id="holidayStartDate" class="form-control form-control-sm" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="small text-muted">End Date</label>
|
||||||
|
<input type="date" id="holidayEndDate" class="form-control form-control-sm" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm w-100">Add</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mt-1">
|
||||||
|
<input type="text" id="holidayNote" class="form-control form-control-sm" placeholder="Note (optional)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h6 class="small text-muted text-uppercase fw-bold mb-2">Current Holidays</h6>
|
||||||
|
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
|
||||||
|
<table class="table table-sm table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Start</th>
|
||||||
|
<th>End</th>
|
||||||
|
<th>Note</th>
|
||||||
|
<th style="width: 50px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="holidaysListBody">
|
||||||
|
<tr><td colspan="4" class="text-center text-muted">Loading...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const searchName = document.getElementById('doctorSearchName');
|
const searchName = document.getElementById('doctorSearchName');
|
||||||
@ -356,4 +416,106 @@ function fetchDoctors(page) {
|
|||||||
if (tableBody) tableBody.style.opacity = '1';
|
if (tableBody) tableBody.style.opacity = '1';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 += `
|
||||||
|
<tr>
|
||||||
|
<td>${h.start_date}</td>
|
||||||
|
<td>${h.end_date}</td>
|
||||||
|
<td>${h.note || '-'}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-link text-danger p-0" onclick="deleteDoctorHoliday(${h.id})">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No holidays found.</td></tr>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
10
lang.php
10
lang.php
@ -270,6 +270,11 @@ $translations = [
|
|||||||
'active' => 'Active',
|
'active' => 'Active',
|
||||||
'inactive' => 'Inactive',
|
'inactive' => 'Inactive',
|
||||||
'are_you_sure_delete_service' => 'Are you sure you want to delete this service?',
|
'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' => [
|
'ar' => [
|
||||||
'attachment' => 'المرفق',
|
'attachment' => 'المرفق',
|
||||||
@ -543,5 +548,10 @@ $translations = [
|
|||||||
'active' => 'نشط',
|
'active' => 'نشط',
|
||||||
'inactive' => 'غير نشط',
|
'inactive' => 'غير نشط',
|
||||||
'are_you_sure_delete_service' => 'هل أنت متأكد أنك تريد حذف هذه الخدمة؟',
|
'are_you_sure_delete_service' => 'هل أنت متأكد أنك تريد حذف هذه الخدمة؟',
|
||||||
|
'appointments_list' => 'قائمة المواعيد',
|
||||||
|
'doctor_holidays' => 'إجازات الأطباء',
|
||||||
|
'printed_on' => 'طبع في',
|
||||||
|
'no_appointments' => 'لا توجد مواعيد لهذا التاريخ',
|
||||||
|
'back' => 'رجوع',
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
180
print_appointments.php
Normal file
180
print_appointments.php
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/helpers.php';
|
||||||
|
|
||||||
|
$lang = $_SESSION['lang'] ?? 'en';
|
||||||
|
$date = $_GET['date'] ?? date('Y-m-d');
|
||||||
|
$doctor_id = $_GET['doctor_id'] ?? '';
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
// Fetch Doctor Name if filtered
|
||||||
|
$doctor_name = '';
|
||||||
|
if ($doctor_id) {
|
||||||
|
$stmt = $db->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();
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?php echo $lang; ?>" dir="<?php echo $lang == 'ar' ? 'rtl' : 'ltr'; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo __('appointments'); ?> - <?php echo $date; ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #fff; color: #000; }
|
||||||
|
@media print {
|
||||||
|
.no-print { display: none !important; }
|
||||||
|
body { padding: 0; margin: 0; }
|
||||||
|
.container { max-width: 100% !important; }
|
||||||
|
a[href]:after { content: none !important; }
|
||||||
|
}
|
||||||
|
.header-box { border-bottom: 2px solid #000; padding-bottom: 15px; margin-bottom: 20px; }
|
||||||
|
.table thead th { background-color: #f0f0f0 !important; border-bottom: 2px solid #000; }
|
||||||
|
.table td, .table th { border: 1px solid #ddd; padding: 8px 12px; vertical-align: middle; }
|
||||||
|
.holiday-banner { background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; padding: 10px; margin-bottom: 15px; border-radius: 4px; }
|
||||||
|
@media print {
|
||||||
|
.holiday-banner { border: 1px solid #000; background-color: #eee; color: #000; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="p-4">
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4 no-print">
|
||||||
|
<a href="javascript:window.close()" class="btn btn-outline-secondary">← <?php echo __('back'); ?></a>
|
||||||
|
<button onclick="window.print()" class="btn btn-primary"><?php echo __('print'); ?></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-box text-center">
|
||||||
|
<h3><?php echo __('appointments_list'); ?></h3>
|
||||||
|
<p class="mb-1"><strong><?php echo __('date'); ?>:</strong> <?php echo date('l, d F Y', strtotime($date)); ?></p>
|
||||||
|
<?php if ($doctor_name): ?>
|
||||||
|
<p class="mb-0"><strong><?php echo __('doctor'); ?>:</strong> <?php echo htmlspecialchars($doctor_name); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($holidays_on_date)): ?>
|
||||||
|
<div class="holiday-banner">
|
||||||
|
<h5 class="mb-2 fw-bold">⚠️ <?php echo __('doctor_holidays'); ?></h5>
|
||||||
|
<ul class="mb-0 ps-3">
|
||||||
|
<?php foreach ($holidays_on_date as $h): ?>
|
||||||
|
<li>
|
||||||
|
<strong><?php echo htmlspecialchars($h['doctor_name']); ?>:</strong>
|
||||||
|
<?php echo htmlspecialchars($h['note'] ?: 'On Holiday'); ?>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($appointments)): ?>
|
||||||
|
<div class="alert alert-light text-center border p-5">
|
||||||
|
<h4><?php echo __('no_appointments'); ?></h4>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-striped w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="15%"><?php echo __('time'); ?></th>
|
||||||
|
<th width="25%"><?php echo __('patient'); ?></th>
|
||||||
|
<th width="15%"><?php echo __('phone'); ?></th>
|
||||||
|
<?php if (!$doctor_id): ?>
|
||||||
|
<th width="20%"><?php echo __('doctor'); ?></th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<th><?php echo __('reason'); ?> / <?php echo __('notes'); ?></th>
|
||||||
|
<th width="10%"><?php echo __('status'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($appointments as $app): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="fw-bold"><?php echo date('h:i A', strtotime($app['start_time'])); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($app['patient_name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($app['patient_tel']); ?></td>
|
||||||
|
<?php if (!$doctor_id): ?>
|
||||||
|
<td><?php echo htmlspecialchars($app['doctor_name']); ?></td>
|
||||||
|
<?php endif; ?>
|
||||||
|
<td><?php echo htmlspecialchars($app['reason'] ?: '-'); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?php
|
||||||
|
echo match($app['status']) {
|
||||||
|
'Completed' => 'success',
|
||||||
|
'Cancelled' => 'danger',
|
||||||
|
default => 'primary'
|
||||||
|
};
|
||||||
|
?> text-dark bg-opacity-10 border border-<?php
|
||||||
|
echo match($app['status']) {
|
||||||
|
'Completed' => 'success',
|
||||||
|
'Cancelled' => 'danger',
|
||||||
|
default => 'primary'
|
||||||
|
};
|
||||||
|
?>">
|
||||||
|
<?php echo $app['status']; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="mt-5 pt-3 border-top text-center text-muted small">
|
||||||
|
<p><?php echo __('printed_on'); ?> <?php echo date('d/m/Y H:i'); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user