doctor-appointment-system/app/Views/admin/appointment_preview.php
2026-05-08 12:22:08 +05:30

577 lines
27 KiB
PHP

<?php
$patient = $patient ?? null;
$service_names = $service_names ?? [];
$services = $services ?? [];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Appointment Preview</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 href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<link rel="stylesheet" href="<?= base_url('css/app.css') ?>">
<link rel="stylesheet" href="<?= base_url('css/dashboard.css') ?>">
<style>
.toggle-expand-btn {
padding: 0;
font-size: 0.75rem;
text-decoration: none;
color: #0d6efd;
cursor: pointer;
display: inline-block;
margin-top: 4px;
}
.toggle-expand-btn:hover {
text-decoration: underline;
}
.expandable-container {
transition: all 0.3s ease;
}
</style>
</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 w-100 gap-3">
<!-- Left Section -->
<div class="d-flex align-items-center gap-2">
<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">Appointment Preview</p>
</div>
<!-- Right Button -->
<a href="<?= base_url('admin/appointments') ?>"
class="btn btn-sm btn-outline-secondary px-3 ms-auto">
Back to Appointments
</a>
</div>
</header>
<main class="ov-content">
<div class="p-3">
<div class="card mb-3 shadow-sm">
<div class="card-header bg-light fw-semibold">
<i class="bi bi-person-vcard me-2"></i>Appointment Details
</div>
<div class="card-body">
<?php
$patientRecord = is_array($patient ?? null) ? $patient : [];
$fullName = trim(((string) ($patientRecord['first_name'] ?? '')) . ' ' . ((string) ($patientRecord['last_name'] ?? '')));
if ($fullName === '') {
$fullName = 'Not Available';
}
$gender = $patientRecord['patient_gender'] ?? $patientRecord['user_gender'] ?? null;
$serviceNameList = is_array($service_names ?? null) ? $service_names : [];
$serviceSummary = $serviceNameList !== [] ? implode(', ', $serviceNameList) : 'Not Specified';
?>
<div class="row">
<!-- LEFT SIDE -->
<div class="col-md-6 border-end pe-4">
<h6 class="section-title">Patient Info</h6>
<div class="row g-2">
<div class="col-md-8">
<strong>Patient ID:</strong><br>
<?= esc($patientRecord['formatted_user_id'] ?? 'N/A') ?>
</div>
<div class="col-md-4">
<strong>Full Name:</strong><br>
<span class="fw-semibold"><?= esc($fullName) ?></span>
</div>
<div class="col-md-8">
<strong>Contact:</strong><br>
<?php
$phone = $patientRecord['phone'] ?? '';
$email = $patientRecord['email'] ?? '';
?>
<div class="d-flex align-items-center flex-wrap gap-3 mt-1">
<!-- PHONE -->
<a
href="<?= $phone ? 'tel:' . esc($phone) : '#' ?>"
class="text-decoration-none text-primary d-flex align-items-center gap-1"
>
<i class="bi bi-telephone-fill"></i>
<?= $phone ? esc($phone) : 'Not Available' ?>
</a>
<!-- EMAIL -->
<a
href="<?= $email ? 'mailto:' . esc($email) : '#' ?>"
class="text-decoration-none text-primary d-flex align-items-center gap-1"
>
<i class="bi bi-envelope-fill"></i>
<?= $email ? esc($email) : 'Not Available' ?>
</a>
</div>
</div>
<!-- <div class="col-md-6">
<strong>Email:</strong><br>
<?= esc($patientRecord['email'] ?? 'Not Available') ?>
</div>
<div class="col-md-6">
<strong>Phone:</strong><br>
<?= esc($patientRecord['phone'] ?? 'Not Available') ?>
</div> -->
<div class="col-md-4">
<strong>Gender:</strong><br>
<?= esc($gender ?: 'Not Available') ?>
</div>
<!-- <div class="col-md-12">
<strong>Requested Service:</strong><br>
<?= esc($serviceSummary) ?>
</div>
<div class="col-md-12">
<strong>Preferred Date & Time:</strong><br>
<?= esc(($date ?? '-') . ' ' . ($time ?? '-')) ?>
</div> -->
</div>
</div>
<!-- RIGHT SIDE -->
<div class="col-md-4 ps-4">
<h6 class="section-title">Preferences</h6>
<div class="info-box">
<strong>Requested Service:</strong><br>
<?= esc($serviceSummary) ?>
</div>
<div class="info-box">
<strong>Preferred Date & Time:</strong><br>
<?php
$displayDate = $date ?? '-';
$displayTime = $time ?? '-';
if ($displayTime !== '-' && $displayTime !== '') {
$displayTime = date('h:i A', strtotime($displayTime));
}
echo esc($displayDate . ' ' . $displayTime);
?>
</div>
<div class="info-box">
<strong>Preferred Physician:</strong><br>
<?= esc($doctor ?? 'Not Available') ?>
</div>
<!-- <div class="info-box">
<strong>Preferred Languages:</strong><br>
English, French
</div> -->
</div>
</div>
</div>
<!-- <div class="card-body">
<?php
$patientRecord = is_array($patient ?? null) ? $patient : [];
$fullName = trim(((string) ($patientRecord['first_name'] ?? '')) . ' ' . ((string) ($patientRecord['last_name'] ?? '')));
if ($fullName === '') {
$fullName = 'Not Available';
}
$gender = $patientRecord['patient_gender'] ?? $patientRecord['user_gender'] ?? null;
$serviceNameList = is_array($service_names ?? null) ? $service_names : [];
$serviceSummary = $serviceNameList !== [] ? implode(', ', $serviceNameList) : 'Not Specified';
?>
<div class="row g-3">
<div class="col-md-4"><strong>Patient ID:</strong> <?= esc($patientRecord['formatted_user_id'] ?? 'N/A') ?></div>
<div class="col-md-4"><strong>Full Name:</strong> <?= esc($fullName) ?></div>
<div class="col-md-4"><strong>Email:</strong> <?= esc($patientRecord['email'] ?? 'Not Available') ?></div>
<div class="col-md-4"><strong>Phone:</strong> <?= esc($patientRecord['phone'] ?? 'Not Available') ?></div>
<div class="col-md-4"><strong>DOB:</strong> <?= esc($patientRecord['dob'] ?? 'Not Available') ?></div>
<div class="col-md-4"><strong>Gender:</strong> <?= esc($gender ?: 'Not Available') ?></div>
<div class="col-md-4"><strong>Status:</strong> <?= esc($patientRecord['status'] ?? 'Not Available') ?></div>
<div class="col-md-8"><strong>Requested Service:</strong> <?= esc($serviceSummary) ?></div>
<div class="col-md-4"><strong>Preferred Date & Time:</strong> <?= esc(($date ?? '-') . ' ' . ($time ?? '-')) ?></div>
</div>
</div> -->
</div>
<div class="card shadow-sm">
<div class="card-header bg-light fw-semibold">
<i class="bi bi-person-badge me-2"></i>Physician List
</div>
<div class="card-body">
<table id="doctorTable" class="table table-hover" style="width:100%">
<thead class="table-light">
<tr>
<th>#ID</th>
<th>Name</th>
<th>Specialization</th>
<th>Experience</th>
<th>Availability</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<!-- <div class="ov-panel">
<div class="ov-panel__header">
<h2 class="ov-panel__title">Appointment Preview</h2>
</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.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let doctorTable = null;
const previewPatientId = <?= json_encode((int) ($patient_id ?? 0)) ?>;
const previewDate = <?= json_encode((string) ($date ?? '')) ?>;
const previewTime = <?= json_encode((string) ($time ?? '')) ?>;
const previewServices = <?= json_encode(array_values(is_array($services ?? []) ? $services : [])) ?>;
const previewId = <?= json_encode((int) ($preview_id ?? 0)) ?>;
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
$(document).ready(function() {
doctorTable = $('#doctorTable').DataTable({
processing: true,
ajax: {
url: "<?= base_url('admin/available-doctors') ?>",
method: 'POST',
data: {
date: previewDate,
time: previewTime,
preview_id: previewId,
specialization_id: previewServices
},
dataSrc: 'data',
error: function() {
$('#doctorTable tbody').html('<tr><td colspan="7" class="text-danger text-center">Failed to load doctors. Please try again later.</td></tr>');
}
},
columns: [
{
data: 'formatted_doctor_id',
render: function(data, type, row) {
const formattedId = escapeHtml(data || 'N/A');
if (type !== 'display' || !row.doctor_edit_token) {
return formattedId;
}
const url = "<?= base_url('admin/doctors/edit') ?>/" + encodeURIComponent(row.doctor_edit_token);
return `<a href="${url}" class="text-decoration-none fw-semibold">${formattedId}</a>`;
}
},
{ data: 'name' },
{
data: 'specializations',
render: function(data, type, row) {
if (!Array.isArray(data) || data.length === 0) {
return '<span class="text-muted">Not Assigned</span>';
}
const limit = 2;
const hasMore = data.length > limit;
const rowId = row.id || Math.random().toString(36).substr(2, 9);
let html = `<div class="expandable-container" id="spec-container-${rowId}">`;
data.forEach((spec, index) => {
const bg = spec.bg_color || '#e9ecef';
const text = spec.text_color || '#212529';
const isHidden = index >= limit ? 'd-none' : '';
html += `<span class="badge me-1 mb-1 ${isHidden}" style="background-color: ${escapeHtml(bg)}; color: ${escapeHtml(text)}; border: 1px solid rgba(0,0,0,0.1);">
${escapeHtml(spec.name)}
</span>`;
});
html += '</div>';
if (hasMore) {
html += `<a href="javascript:void(0);" class="toggle-expand-btn" onclick="toggleExpand(this, 'spec-container-${rowId}')">Show more</a>`;
}
return html;
}
},
{
data: 'experience',
render: function(data) {
if (!data || data === '0' || data === 0) {
return '<span class="text-muted">No Experience</span>';
}
let display = escapeHtml(data);
// If it's just a number (e.g. "12"), format it as "12 Years"
if (/^\d+$/.test(data)) {
display = data + (parseInt(data) === 1 ? ' Year' : ' Years');
}
return `<span class="badge bg-info-subtle text-info-emphasis border border-info-subtle px-2 py-1">
<i class="bi bi-briefcase me-1"></i>${display}
</span>`;
}
},
{
data: null,
render: function(data, type, row) {
const days = Array.isArray(row.available_days) ? row.available_days : [];
const allSlots = Array.isArray(row.all_slots) ? row.all_slots : [];
if (days.length === 0) {
return '<span class="text-muted">Not Available</span>';
}
const limit = 2;
let slotCount = 0;
const rowId = row.id || Math.random().toString(36).substr(2, 9);
let html = `<div class="expandable-container d-flex flex-wrap gap-2" id="slot-container-${rowId}">`;
days.forEach(day => {
const daySlots = allSlots.filter(slot => slot.day === day);
if (daySlots.length > 0) {
daySlots.forEach(slot => {
slotCount++;
const isHidden = slotCount > limit ? 'd-none' : '';
const dayAbbrev = day.substring(0, 3);
html += `
<span class="badge bg-primary-subtle text-primary-emphasis border border-primary-subtle px-2 py-1 ${isHidden}">
<i class="bi bi-calendar-day me-1"></i>${escapeHtml(dayAbbrev)}
<span class="fw-bold mx-1">|</span>
<i class="bi bi-clock me-1"></i>${escapeHtml(slot.start_time_formatted)} - ${escapeHtml(slot.end_time_formatted)}
</span>
`;
});
} else {
slotCount++;
const isHidden = slotCount > limit ? 'd-none' : '';
const dayAbbrev = day.substring(0, 3);
html += `
<span class="badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle px-2 py-1 ${isHidden}">
<i class="bi bi-calendar-day me-1"></i>${escapeHtml(dayAbbrev)}
<span class="fw-bold mx-1">|</span>
<i class="bi bi-x-circle me-1"></i>No Slots
</span>
`;
}
});
html += '</div>';
if (slotCount > limit) {
html += `<a href="javascript:void(0);" class="toggle-expand-btn" onclick="toggleExpand(this, 'slot-container-${rowId}')">Show more</a>`;
}
return html;
}
},
// {
// data: 'available_days',
// render: function(data, type, row) {
// const days = Array.isArray(data) ? data : [];
// const text = row.available_days_text || 'Not Assigned';
// if (type !== 'display') {
// return text;
// }
// if (days.length === 0) {
// return '<span class="text-muted">Not Assigned</span>';
// }
// return days.map(day => `<span class="badge bg-info-subtle text-info-emphasis border border-info-subtle me-1 mb-1">${escapeHtml(day)}</span>`).join('');
// }
// },
// {
// data: 'time_slots',
// render: function(data) {
// if (!Array.isArray(data) || data.length === 0) {
// return '<span class="badge bg-warning text-dark">No Available Slots</span>';
// }
// return data.map(slot => `<span class="badge bg-info me-1">${escapeHtml(slot.start_time_formatted)} - ${escapeHtml(slot.end_time_formatted)}</span>`).join('<br>');
// }
// },
{
data: 'status',
render: function(data) {
if (data === 'Available') return '<span class="badge bg-success">Available</span>';
if (data === 'Booked') return '<span class="badge bg-danger">Assigned</span>';
return '<span class="badge bg-warning text-dark">Not Available</span>';
}
},
{
data: null,
render: function(data) {
if (data.status === 'Available') {
return `<button class="book-btn btn btn-success btn-sm" data-doctor="${escapeHtml(data.id)}">Assign</button>`;
} else if (data.status === 'Booked') {
return '<button class="btn btn-secondary btn-sm" disabled>Assigned</button>';
} else {
return '<button class="btn btn-secondary btn-sm" disabled>Not Available</button>';
}
}
}
],
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',
language: {
search: 'Search doctors:',
lengthMenu: 'Show _MENU_ doctors',
info: 'Showing _START_ to _END_ of _TOTAL_ doctors',
paginate: { first: 'First', last: 'Last', next: 'Next', previous: 'Previous' }
}
});
// Real-time update every 3 seconds
setInterval(function() {
if (doctorTable) {
doctorTable.ajax.reload(null, false); // false = don't reset paging
}
}, 3000);
});
$(document).on('click', '.book-btn', function() {
const btn = $(this);
const doctorId = btn.data('doctor');
btn.prop('disabled', true).text('Assigning...');
$.ajax({
url: "<?= base_url('admin/appointments/create') ?>",
method: 'POST',
contentType: 'application/json',
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': "<?= csrf_hash() ?>"
},
data: JSON.stringify({
doctor_id: doctorId,
patient_id: previewPatientId,
appointment_date: previewDate,
appointment_time: previewTime,
services: previewServices,
preview_id: previewId
}),
success: function(res) {
if (!res || !res.success) {
alert(res && res.message ? res.message : 'Assignment failed. Please try again.');
btn.prop('disabled', false).text('Assign');
return;
}
alert(res.message || 'Appointment assigned successfully.');
doctorTable.ajax.reload();
},
error: function(xhr) {
const message = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Assignment failed. Please try again.';
alert(message);
btn.prop('disabled', false).text('Assign');
}
});
});
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 toggleExpand(btn, containerId) {
const container = document.getElementById(containerId);
const hiddenItems = container.querySelectorAll('.d-none');
const shownItems = container.querySelectorAll('.badge:not(.d-none)');
if (btn.innerText === 'Show more') {
// Find all badges that were hidden and show them
// We'll add a temporary class to track them
container.querySelectorAll('.badge.d-none').forEach(el => {
el.classList.remove('d-none');
el.classList.add('was-hidden');
});
btn.innerText = 'Show less';
} else {
// Find all badges that were originally hidden and hide them again
container.querySelectorAll('.badge.was-hidden').forEach(el => {
el.classList.add('d-none');
el.classList.remove('was-hidden');
});
btn.innerText = 'Show more';
}
}
function toggleNavDropdown(event, element) {
event.preventDefault();
element.parentElement.classList.toggle('active');
}
</script>
</body>
</html>