357 lines
16 KiB
PHP
357 lines
16 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Appointments</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css">
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap5.min.css">
|
|
<link rel="stylesheet" href="<?= base_url('css/app.css') ?>">
|
|
<link rel="stylesheet" href="<?= base_url('css/dashboard.css') ?>">
|
|
</head>
|
|
<body class="app-body overview-layout">
|
|
<aside class="ov-sidebar" id="sidebar">
|
|
<div class="ov-brand"><h1><i class="bi bi-hospital me-1"></i> DoctGuide</h1><span>Control Panel</span></div>
|
|
<nav class="ov-nav">
|
|
<div class="ov-nav__section">Main</div>
|
|
<a href="<?= base_url('admin/dashboard') ?>" class="ov-nav__link"><i class="bi bi-speedometer2"></i> Dashboard</a>
|
|
<div class="ov-nav__section">Manage</div>
|
|
<div class="ov-nav__dropdown">
|
|
<a href="#" class="ov-nav__link d-flex justify-content-between align-items-center" onclick="toggleNavDropdown(event, this)">
|
|
<span><i class="bi bi-person-badge"></i> Doctors</span>
|
|
<i class="bi bi-chevron-down dropdown-icon"></i>
|
|
</a>
|
|
<div class="ov-dropdown-menu">
|
|
<a href="<?= base_url('admin/doctors') ?>" class="ov-nav__sublink">Doctor List</a>
|
|
<a href="<?= base_url('admin/doctors/add') ?>" class="ov-nav__sublink">Add Doctor</a>
|
|
</div>
|
|
</div>
|
|
<div class="ov-nav__dropdown">
|
|
<a href="#" class="ov-nav__link d-flex justify-content-between align-items-center" onclick="toggleNavDropdown(event, this)">
|
|
<span><i class="bi bi-people"></i> Patients</span>
|
|
<i class="bi bi-chevron-down dropdown-icon"></i>
|
|
</a>
|
|
<div class="ov-dropdown-menu">
|
|
<a href="<?= base_url('admin/patients') ?>" class="ov-nav__sublink">Patient List</a>
|
|
<a href="<?= base_url('admin/patients/add') ?>" class="ov-nav__sublink">Add Patient</a>
|
|
</div>
|
|
</div>
|
|
<a href="<?= base_url('admin/appointments') ?>" class="ov-nav__link active"><i class="bi bi-calendar2-check"></i> Appointments</a>
|
|
<a href="<?= base_url('admin/activity-log') ?>" class="ov-nav__link"><i class="bi bi-clipboard-data"></i> Activity Log</a>
|
|
</nav>
|
|
<div class="ov-sidebar__footer"><a href="<?= base_url('logout') ?>"><i class="bi bi-box-arrow-left"></i> Logout</a></div>
|
|
</aside>
|
|
|
|
<div class="ov-main" id="mainContent">
|
|
<header class="ov-topbar">
|
|
<div class="d-flex align-items-center">
|
|
<button class="ov-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar"><i class="bi bi-list" id="toggleIcon"></i></button>
|
|
<p class="ov-topbar__title mb-0">Appointments</p>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="ov-content">
|
|
<div class="ov-panel">
|
|
<div class="ov-panel__header">
|
|
<h2 class="ov-panel__title">Appointments</h2>
|
|
<!-- <a href="<?= base_url('admin/dashboard') ?>" class="btn btn-sm btn-outline-secondary px-3">Back to dashboard</a> -->
|
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
|
<!-- DataTable export buttons will appear here -->
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="bi bi-three-dots-vertical"></i>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item" href="#" onclick="exportTable('csv'); return false;"><i class="bi bi-file-earmark-text me-2"></i> CSV</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportTable('excel'); return false;"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportTable('pdf'); return false;"><i class="bi bi-file-earmark-pdf me-2"></i> PDF</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-3">
|
|
<table id="appointmentsTable" class="table table-hover" style="width:100%">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>#ID</th>
|
|
<th>Patient</th>
|
|
<th>Service Request</th>
|
|
<th>Pref Date & Time</th>
|
|
<th>Pref Physician</th>
|
|
<th>Status</th>
|
|
<th width="130">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
|
|
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
|
|
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap5.min.js"></script>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/pdfmake.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/vfs_fonts.js"></script>
|
|
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
let appointmentsTable = null;
|
|
|
|
$(document).ready(function() {
|
|
|
|
appointmentsTable = $('#appointmentsTable').DataTable({
|
|
processing: true,
|
|
ajax: {
|
|
url: "<?= base_url('admin/appointmentsData') ?>",
|
|
type: 'GET',
|
|
dataSrc: 'data',
|
|
error: function(xhr, status, error) {
|
|
let msg = 'Failed to load appointments. Please try again later.';
|
|
$('#appointmentsTable tbody').html('<tr><td colspan="6" class="text-danger text-center">' + msg + '</td></tr>');
|
|
}
|
|
},
|
|
columns: [
|
|
{ data: 'sl_no' },
|
|
{ data: 'patient_name' },
|
|
{ data: 'service_request' },
|
|
{
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
|
|
let date = row.appointment_date;
|
|
let time = row.appointment_time;
|
|
|
|
if (!date) return '';
|
|
|
|
// Format Date
|
|
let formattedDate = new Date(date).toLocaleDateString('en-IN', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric'
|
|
});
|
|
|
|
// Format Time
|
|
let formattedTime = '';
|
|
if (time) {
|
|
let t = new Date('1970-01-01T' + time);
|
|
formattedTime = t.toLocaleTimeString('en-IN', {
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
}
|
|
|
|
return `
|
|
<div class="d-flex flex-column">
|
|
<span class="fw-semibold">${formattedDate}</span>
|
|
<small class="text-muted">${formattedTime}</small>
|
|
</div>
|
|
`;
|
|
}
|
|
},
|
|
{
|
|
data: 'doctor_name',
|
|
render: function(data){
|
|
const name = String(data || '').trim();
|
|
return name !== '' ? name : 'Not Assigned';
|
|
}
|
|
},
|
|
{ data: 'status',
|
|
render: function(data){
|
|
let status = (data || 'pending').toLowerCase();
|
|
let cls = 'secondary';
|
|
if (status === 'confirmed' || status === 'approved' || status === 'assigned') cls = 'primary';
|
|
if (status === 'completed') cls = 'success';
|
|
if (status === 'cancelled' || status === 'rejected') cls = 'danger';
|
|
|
|
return `<span class="badge bg-${cls}">${status.charAt(0).toUpperCase() + status.slice(1)}</span>`;
|
|
}
|
|
},
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function (data, type, row) {
|
|
const params = new URLSearchParams();
|
|
params.append('patient_id', row.patient_id || '');
|
|
const deleteUrl = "<?= base_url('admin/appointments/delete') ?>/" + encodeURIComponent(row.id);
|
|
|
|
if (Array.isArray(row.services)) {
|
|
row.services.forEach(function(service) {
|
|
params.append('services[]', service);
|
|
});
|
|
} else if (row.services) {
|
|
params.append('services[]', row.services);
|
|
}
|
|
|
|
params.append('date', row.appointment_date || '');
|
|
params.append('time', row.appointment_time || '');
|
|
params.append('service_text', row.service_request || '');
|
|
params.append('doctor_id', row.doctor_id || '');
|
|
params.append('doctor_name', row.doctor_name || '');
|
|
|
|
const previewUrl = "<?= base_url('admin/appointment-preview') ?>?" + params.toString();
|
|
const currentStatus = (row.status || 'pending').toLowerCase();
|
|
|
|
let statusButtons = '';
|
|
// if (currentStatus === 'assigned' || currentStatus === 'approved' || currentStatus === 'pending') {
|
|
// statusButtons = `
|
|
// <button class="btn btn-outline-success btn-sm update-status-btn" data-id="${row.id}" data-status="completed" title="Mark Completed">
|
|
// <i class="bi bi-check-circle"></i>
|
|
// </button>
|
|
// <button class="btn btn-outline-warning btn-sm update-status-btn" data-id="${row.id}" data-status="cancelled" title="Cancel Appointment">
|
|
// <i class="bi bi-x-circle"></i>
|
|
// </button>
|
|
// `;
|
|
// }
|
|
|
|
return `
|
|
<div class="btn-group" role="group">
|
|
<a href="${previewUrl}" class="btn btn-outline-secondary btn-sm" title="Preview">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
<a href="#" class="btn btn-outline-primary btn-sm" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
|
|
<a href="#"class="btn btn-outline-success btn-sm"title="Book Appointment">
|
|
<i class="bi bi-plus-lg"></i>
|
|
</a>
|
|
${statusButtons}
|
|
<a href="${deleteUrl}" class="btn btn-outline-danger btn-sm" onclick="return confirm('Clear this appointment? This will free the doctor time slot.');" title="Delete Permanent">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
],
|
|
pageLength: 10,
|
|
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
|
|
order: [[0, 'asc']],
|
|
dom: '<"row mb-3"<"col-md-6 d-flex align-items-center"l><"col-md-6 d-flex justify-content-end"f>>rtip',
|
|
buttons: [
|
|
{
|
|
extend: 'csvHtml5',
|
|
className: 'buttons-csv',
|
|
title: 'Appointments',
|
|
filename: 'appointments_list_' + new Date().toISOString().split('T')[0],
|
|
exportOptions: {
|
|
columns: [0, 1, 2, 3, 4, 5]
|
|
}
|
|
},
|
|
{
|
|
extend: 'excelHtml5',
|
|
className: 'buttons-excel',
|
|
title: 'Appointments',
|
|
filename: 'appointments_list_' + new Date().toISOString().split('T')[0],
|
|
exportOptions: {
|
|
columns: [0, 1, 2, 3, 4, 5]
|
|
}
|
|
},
|
|
{
|
|
extend: 'pdfHtml5',
|
|
className: 'buttons-pdf',
|
|
title: 'Appointments List',
|
|
filename: 'appointments_list_' + new Date().toISOString().split('T')[0],
|
|
orientation: 'landscape',
|
|
pageSize: 'A4',
|
|
exportOptions: {
|
|
columns: [0, 1, 2, 3, 4, 5]
|
|
}
|
|
}
|
|
],
|
|
language: {
|
|
search: 'Search appointments:',
|
|
lengthMenu: 'Show _MENU_ appointments',
|
|
info: 'Showing _START_ to _END_ of _TOTAL_ appointments',
|
|
paginate: {
|
|
first: 'First',
|
|
last: 'Last',
|
|
next: 'Next',
|
|
previous: 'Previous'
|
|
},
|
|
emptyTable: 'No appointments found',
|
|
zeroRecords: 'No matching appointments found'
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '.update-status-btn', function() {
|
|
const btn = $(this);
|
|
const id = btn.data('id');
|
|
const status = btn.data('status');
|
|
|
|
if (!confirm(`Are you sure you want to mark this appointment as ${status}?`)) {
|
|
return;
|
|
}
|
|
|
|
btn.prop('disabled', true);
|
|
|
|
$.ajax({
|
|
url: "<?= base_url('admin/appointments/update-status') ?>",
|
|
method: 'POST',
|
|
data: {
|
|
appointment_id: id,
|
|
status: status,
|
|
"<?= csrf_token() ?>": "<?= csrf_hash() ?>"
|
|
},
|
|
success: function(res) {
|
|
if (res && res.success) {
|
|
appointmentsTable.ajax.reload(null, false);
|
|
} else {
|
|
alert(res.message || 'Failed to update status');
|
|
btn.prop('disabled', false);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('An error occurred. Please try again.');
|
|
btn.prop('disabled', false);
|
|
}
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
function toggleSidebar() {
|
|
const sidebar = document.getElementById('sidebar');
|
|
const main = document.getElementById('mainContent');
|
|
const icon = document.getElementById('toggleIcon');
|
|
sidebar.classList.toggle('collapsed');
|
|
main.classList.toggle('expanded');
|
|
icon.className = sidebar.classList.contains('collapsed') ? 'bi bi-layout-sidebar' : 'bi bi-list';
|
|
}
|
|
|
|
function toggleNavDropdown(event, element) {
|
|
event.preventDefault();
|
|
element.parentElement.classList.toggle('active');
|
|
}
|
|
function exportTable(type) {
|
|
if (!appointmentsTable) return;
|
|
|
|
if (type === 'csv') {
|
|
appointmentsTable.button('.buttons-csv').trigger();
|
|
}
|
|
else if (type === 'excel') {
|
|
appointmentsTable.button('.buttons-excel').trigger();
|
|
}
|
|
else if (type === 'pdf') {
|
|
appointmentsTable.button('.buttons-pdf').trigger();
|
|
}
|
|
|
|
// close dropdown
|
|
$('.dropdown-toggle').dropdown('hide');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|