899 lines
44 KiB
PHP
899 lines
44 KiB
PHP
<?php
|
|
// --- AJAX HANDLER FOR PATIENT SEARCH ---
|
|
if (isset($_GET['ajax_search'])) {
|
|
$search_query = $_GET['search'] ?? '';
|
|
$limit = 10;
|
|
|
|
$where = "WHERE 1=1";
|
|
$params = [];
|
|
if ($search_query) {
|
|
$where .= " AND (p.name LIKE ? OR p.phone LIKE ? OR p.civil_id LIKE ?)";
|
|
$params[] = "%$search_query%";
|
|
$params[] = "%$search_query%";
|
|
$params[] = "%$search_query%";
|
|
}
|
|
|
|
$query = "
|
|
SELECT p.*, ic.name_$lang as insurance_name
|
|
FROM patients p
|
|
LEFT JOIN insurance_companies ic ON p.insurance_company_id = ic.id
|
|
$where
|
|
ORDER BY p.id DESC
|
|
LIMIT $limit";
|
|
|
|
$stmt = $db->prepare($query);
|
|
$stmt->execute($params);
|
|
$patients = $stmt->fetchAll();
|
|
|
|
ob_start();
|
|
if (empty($patients)): ?>
|
|
<tr><td colspan="5" class="text-center py-3 text-muted"><?php echo __('no_patients_found'); ?></td></tr>
|
|
<?php else:
|
|
foreach ($patients as $p): ?>
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
|
|
<small class="text-muted"><?php echo $p['dob']; ?> (<?php echo calculate_age($p['dob']); ?>)</small>
|
|
</td>
|
|
<td><?php echo htmlspecialchars($p['phone']); ?></td>
|
|
<td>
|
|
<span class="badge <?php echo $p['insurance_name'] ? 'bg-primary' : 'bg-secondary'; ?>">
|
|
<?php echo $p['insurance_name'] ?: __('not_insured'); ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $p['id']; ?>)" title="<?php echo __('add_visit'); ?>">
|
|
<i class="bi bi-clipboard-plus"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="showEditPatientModal(<?php echo htmlspecialchars(json_encode($p, JSON_UNESCAPED_UNICODE)); ?>)" title="<?php echo __('edit'); ?>">
|
|
<i class="bi bi-pencil-square"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach;
|
|
endif;
|
|
$html = ob_get_clean();
|
|
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['html' => $html]);
|
|
exit;
|
|
}
|
|
|
|
// Fetch Stats
|
|
$total_patients = $db->query("SELECT COUNT(*) FROM patients")->fetchColumn();
|
|
$today_appointments_count = $db->query("SELECT COUNT(*) FROM appointments WHERE DATE(start_time) = CURDATE()")->fetchColumn();
|
|
$total_visits = $db->query("SELECT COUNT(*) FROM visits")->fetchColumn();
|
|
$total_revenue = $db->query("SELECT SUM(total_amount) FROM bills WHERE status = 'Paid'")->fetchColumn() ?: 0;
|
|
$pending_revenue = $db->query("SELECT SUM(total_amount) FROM bills WHERE status = 'Pending'")->fetchColumn() ?: 0;
|
|
$total_xrays = $db->query("SELECT COUNT(*) FROM xray_inquiries")->fetchColumn();
|
|
$total_labs = $db->query("SELECT COUNT(*) FROM laboratory_inquiries")->fetchColumn();
|
|
|
|
// Fetch Running Visits (Not Completed/Cancelled)
|
|
$running_visits_sql = "
|
|
SELECT v.*, p.name as patient_name, doc.name_$lang as doctor_name, nur.name_$lang as nurse_name
|
|
FROM visits v
|
|
JOIN patients p ON v.patient_id = p.id
|
|
LEFT JOIN employees doc ON v.doctor_id = doc.id
|
|
LEFT JOIN employees nur ON v.nurse_id = nur.id
|
|
WHERE v.status IN ('CheckIn', 'In Progress')
|
|
ORDER BY v.visit_date ASC";
|
|
$running_visits = $db->query($running_visits_sql)->fetchAll();
|
|
|
|
// Initial Patients Load
|
|
$patients_sql = "
|
|
SELECT p.*, ic.name_$lang as insurance_name
|
|
FROM patients p
|
|
LEFT JOIN insurance_companies ic ON p.insurance_company_id = ic.id
|
|
ORDER BY p.id DESC LIMIT 5";
|
|
$patients = $db->query($patients_sql)->fetchAll();
|
|
|
|
// Today's Appointments
|
|
$appointments_sql = "
|
|
SELECT a.*, p.name as patient_name, doc.name_$lang as doctor_name, nur.name_$lang as nurse_name
|
|
FROM appointments a
|
|
JOIN patients p ON a.patient_id = p.id
|
|
LEFT JOIN employees doc ON a.doctor_id = doc.id
|
|
LEFT JOIN employees nur ON a.nurse_id = nur.id
|
|
WHERE DATE(a.start_time) = CURDATE()
|
|
ORDER BY a.start_time ASC";
|
|
$appointments = $db->query($appointments_sql)->fetchAll();
|
|
?>
|
|
|
|
<!-- Dashboard Stats -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-people"></i>
|
|
<h3><?php echo $total_patients; ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('total_patients'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-calendar-check"></i>
|
|
<h3><?php echo $today_appointments_count; ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('today_appointments'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-flask text-info"></i>
|
|
<h3><?php echo $total_labs; ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('laboratory'); ?> <?php echo __('inquiries'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-x-diamond text-primary"></i>
|
|
<h3><?php echo $total_xrays; ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('xray'); ?> <?php echo __('inquiries'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-currency-dollar text-success"></i>
|
|
<h3><?php echo format_currency($total_revenue); ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('revenue'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card stat-card h-100">
|
|
<i class="bi bi-hourglass-split text-warning"></i>
|
|
<h3><?php echo format_currency($pending_revenue); ?></h3>
|
|
<p class="text-muted mb-0"><?php echo __('pending'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card p-3 d-flex flex-row justify-content-between align-items-center">
|
|
<h5 class="mb-0 fw-bold"><?php echo __('dashboard'); ?></h5>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addPatientModal">
|
|
<i class="bi bi-plus-lg"></i> <?php echo __('add_patient'); ?>
|
|
</button>
|
|
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#bookAppointmentModal">
|
|
<i class="bi bi-calendar-plus"></i> <?php echo __('book_appointment'); ?>
|
|
</button>
|
|
<button class="btn btn-info btn-sm text-white" onclick="showReceptionistVisitModal()">
|
|
<i class="bi bi-clipboard-plus"></i> <?php echo __('add_visit'); ?>
|
|
</button>
|
|
<button class="btn btn-warning btn-sm text-white" data-bs-toggle="modal" data-bs-target="#addXrayInquiryModal">
|
|
<i class="bi bi-x-diamond"></i> <?php echo __('add_xray_inquiry'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Running Visits Table -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header py-3 d-flex justify-content-between align-items-center bg-primary text-white">
|
|
<h6 class="mb-0 fw-bold"><i class="bi bi-play-circle-fill me-2"></i> <?php echo __('running_visits'); ?></h6>
|
|
<span class="badge bg-white text-primary"><?php echo count($running_visits); ?></span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0 align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th><?php echo __('time'); ?></th>
|
|
<th><?php echo __('patient'); ?></th>
|
|
<th><?php echo __('doctor'); ?></th>
|
|
<th><?php echo __('status'); ?></th>
|
|
<th class="text-end"><?php echo __('actions'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($running_visits as $v): ?>
|
|
<tr>
|
|
<td><?php echo date('Y-m-d H:i', strtotime($v['visit_date'])); ?></td>
|
|
<td>
|
|
<div class="fw-bold"><?php echo htmlspecialchars($v['patient_name']); ?></div>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
if ($v['doctor_name']) echo htmlspecialchars($v['doctor_name']);
|
|
elseif ($v['nurse_name']) echo htmlspecialchars($v['nurse_name']) . ' (' . __('nurse') . ')';
|
|
else echo '-';
|
|
?>
|
|
</td>
|
|
<td><span class="badge bg-warning text-dark"><?php echo __($v['status']); ?></span></td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-success" onclick="showCheckoutModal(<?php echo $v['id']; ?>)" title="<?php echo __('checkout_payment'); ?>">
|
|
<i class="bi bi-cash-stack"></i> <?php echo __('checkout_payment'); ?>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; if (empty($running_visits)): ?>
|
|
<tr><td colspan="5" class="text-center py-4 text-muted"><?php echo __('no_running_visits'); ?></td></tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content Area -->
|
|
<div class="row">
|
|
<!-- Today's Appointments -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-calendar-event-fill me-2"></i> <?php echo __('today_appointments'); ?></h6>
|
|
<span class="badge bg-white text-primary"><?php echo count($appointments); ?></span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light sticky-top">
|
|
<tr>
|
|
<th><?php echo __('time'); ?></th>
|
|
<th><?php echo __('patient'); ?></th>
|
|
<th><?php echo __('doctor'); ?></th>
|
|
<th><?php echo __('status'); ?></th>
|
|
<th class="text-end"><?php echo __('actions'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($appointments as $a): ?>
|
|
<tr>
|
|
<td><?php echo date('H:i', strtotime($a['start_time'])); ?></td>
|
|
<td>
|
|
<div class="fw-bold"><?php echo htmlspecialchars($a['patient_name']); ?></div>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
if ($a['doctor_name']) echo htmlspecialchars($a['doctor_name']);
|
|
elseif ($a['nurse_name']) echo htmlspecialchars($a['nurse_name']) . ' (' . __('nurse') . ')';
|
|
else echo '-';
|
|
?>
|
|
</td>
|
|
<td><span class="badge <?php echo $a['status'] === 'Completed' ? 'bg-success' : 'bg-secondary'; ?>"><?php echo __($a['status']); ?></span></td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $a['patient_id']; ?>)" title="<?php echo __('add_visit'); ?>">
|
|
<i class="bi bi-clipboard-plus"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-primary" onclick='showDashboardEditModal(<?php echo json_encode($a, JSON_HEX_APOS | JSON_HEX_QUOT); ?>)' title="<?php echo __('edit'); ?>">
|
|
<i class="bi bi-pencil-square"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; if (empty($appointments)): ?>
|
|
<tr><td colspan="5" class="text-center py-4 text-muted"><?php echo __('no_appointments_today'); ?></td></tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patient List / Search -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header py-2">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-people-fill me-2"></i> <?php echo __('patients'); ?></h6>
|
|
<div class="input-group input-group-sm" style="width: 200px;">
|
|
<input type="text" id="dashboardPatientSearch" class="form-control" placeholder="<?php echo __('search'); ?>...">
|
|
<button class="btn btn-light" type="button"><i class="bi bi-search"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
|
<table class="table table-hover mb-0 align-middle">
|
|
<thead class="table-light sticky-top">
|
|
<tr>
|
|
<th><?php echo __('patient'); ?></th>
|
|
<th><?php echo __('phone'); ?></th>
|
|
<th><?php echo __('insurance'); ?></th>
|
|
<th class="text-end"><?php echo __('actions'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="dashboardPatientsTable">
|
|
<?php foreach ($patients as $p): ?>
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
|
|
<small class="text-muted"><?php echo $p['dob']; ?> (<?php echo calculate_age($p['dob']); ?>)</small>
|
|
</td>
|
|
<td><?php echo htmlspecialchars($p['phone']); ?></td>
|
|
<td><span class="badge <?php echo $p['insurance_name'] ? 'bg-primary' : 'bg-secondary'; ?>"><?php echo $p['insurance_name'] ?: __('not_insured'); ?></span></td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $p['id']; ?>)" title="<?php echo __('add_visit'); ?>">
|
|
<i class="bi bi-clipboard-plus"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="showEditPatientModal(<?php echo htmlspecialchars(json_encode($p, JSON_UNESCAPED_UNICODE)); ?>)" title="<?php echo __('edit'); ?>">
|
|
<i class="bi bi-pencil-square"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; if (empty($patients)): ?>
|
|
<tr><td colspan="4" class="text-center py-4 text-muted">No patients found.</td></tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dashboard Appointment Edit Modal -->
|
|
<div class="modal fade" id="dashboardAppointmentModal" 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="dashModalTitle">Edit Appointment</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="dash_apt_id">
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('patient'); ?></label>
|
|
<select id="dash_apt_patient_id" class="form-select select2-modal-dash">
|
|
<?php foreach ($all_patients as $p): ?>
|
|
<option value="<?php echo $p['id']; ?>" data-address="<?php echo htmlspecialchars($p['address'] ?? ''); ?>"><?php echo htmlspecialchars($p['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Visit Type</label>
|
|
<select id="dash_apt_visit_type" class="form-select" onchange="dashboard_toggleAddressField()">
|
|
<option value="Clinic">Clinic Visit</option>
|
|
<option value="Home">Home Visit</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Provider Type</label>
|
|
<select id="dash_apt_provider_type" class="form-select" onchange="dashboard_toggleProviderField()">
|
|
<option value="Doctor">Doctor</option>
|
|
<option value="Nurse">Nurse</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 d-none" id="dash_div_address">
|
|
<label class="form-label small text-muted">Address (For Home Visit)</label>
|
|
<textarea id="dash_apt_address" class="form-control" rows="2" placeholder="Enter patient address..."></textarea>
|
|
</div>
|
|
|
|
<div class="mb-3" id="dash_div_doctor">
|
|
<label class="form-label small text-muted"><?php echo __('doctor'); ?></label>
|
|
<select id="dash_apt_doctor_id" class="form-select select2-modal-dash">
|
|
<option value="">Select Doctor</option>
|
|
<?php foreach ($all_doctors as $d): ?>
|
|
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3 d-none" id="dash_div_nurse">
|
|
<label class="form-label small text-muted"><?php echo __('nurse'); ?></label>
|
|
<select id="dash_apt_nurse_id" class="form-select select2-modal-dash">
|
|
<option value="">Select Nurse</option>
|
|
<?php foreach ($all_nurses as $n): ?>
|
|
<option value="<?php echo $n['id']; ?>"><?php echo htmlspecialchars($n['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('date'); ?> & <?php echo __('time'); ?></label>
|
|
<input type="datetime-local" id="dash_apt_start_time" class="form-control">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('status'); ?></label>
|
|
<select id="dash_apt_status" class="form-select">
|
|
<option value="Scheduled">Scheduled</option>
|
|
<option value="Completed">Completed</option>
|
|
<option value="Cancelled">Cancelled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('reason'); ?></label>
|
|
<textarea id="dash_apt_reason" class="form-control" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 bg-light rounded-bottom">
|
|
<div class="d-flex justify-content-between w-100">
|
|
<button type="button" class="btn btn-outline-danger shadow-sm" onclick="dashboard_deleteAppointment()">
|
|
<i class="bi bi-trash"></i> <?php echo __('delete'); ?>
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-secondary shadow-sm me-2" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
|
<button type="button" class="btn btn-primary shadow-sm px-4" onclick="dashboard_saveAppointment()">
|
|
<i class="bi bi-check2-circle me-1"></i> <?php echo __('save'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Receptionist Quick Visit Modal -->
|
|
<div class="modal fade" id="receptionistVisitModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<form action="dashboard.php" method="POST">
|
|
<input type="hidden" name="action" value="record_visit">
|
|
<div class="modal-content border-0 shadow">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title fw-bold text-secondary"><?php echo __('new_visit'); ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('patient'); ?></label>
|
|
<select name="patient_id" id="quick_visit_patient_id" class="form-select select2-modal-quick" required>
|
|
<option value=""><?php echo __('select'); ?>...</option>
|
|
<?php foreach ($all_patients as $p): ?>
|
|
<option value="<?php echo $p['id']; ?>">
|
|
<?php echo htmlspecialchars($p['name']) . ($p['phone'] ? ' - ' . htmlspecialchars($p['phone']) : '') . ($p['civil_id'] ? ' (' . htmlspecialchars($p['civil_id']) . ')' : ''); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('doctor'); ?></label>
|
|
<select name="doctor_id" id="quick_visit_doctor_id" class="form-select select2-modal-quick" required>
|
|
<option value=""><?php echo __('select'); ?>...</option>
|
|
<?php foreach ($all_doctors as $d): ?>
|
|
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="form-check form-switch mb-3">
|
|
<input class="form-check-input" type="checkbox" id="quick_visit_token" name="generate_token" value="1" checked>
|
|
<label class="form-check-label" for="quick_visit_token"><?php echo __('issue_token'); ?></label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer bg-light">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
|
<button type="submit" class="btn btn-primary px-4"><?php echo __('save'); ?></button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Checkout/Billing Modal -->
|
|
<div class="modal fade" id="checkoutModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content border-0 shadow">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold text-secondary"><?php echo __('checkout_payment'); ?></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="checkout_bill_id">
|
|
|
|
<div class="alert alert-light border d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<div class="small text-muted"><?php echo __('patient'); ?></div>
|
|
<div class="fw-bold" id="checkout_patient_name">-</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<div class="small text-muted"><?php echo __('insurance'); ?></div>
|
|
<div class="fw-bold text-primary" id="checkout_insurance_name">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Items -->
|
|
<div class="card bg-light border-0 mb-3">
|
|
<div class="card-body p-2">
|
|
<div class="row g-2">
|
|
<div class="col-md-4">
|
|
<select id="checkout_service_select" class="form-select form-select-sm select2-modal-checkout">
|
|
<option value=""><?php echo __('select_service'); ?>...</option>
|
|
<?php foreach ($all_services as $s): ?>
|
|
<option value="<?php echo $s['name']; ?>" data-price="<?php echo $s['price']; ?>"><?php echo htmlspecialchars($s['name']); ?> (<?php echo format_currency($s['price']); ?>)</option>
|
|
<?php endforeach; ?>
|
|
<?php foreach ($all_tests as $t): ?>
|
|
<option value="<?php echo $t['name']; ?>" data-price="<?php echo $t['price']; ?>"><?php echo htmlspecialchars($t['name']); ?> (<?php echo format_currency($t['price']); ?>)</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<input type="text" id="checkout_custom_item" class="form-control form-control-sm" placeholder="Custom Item Description">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input type="number" id="checkout_item_price" class="form-control form-control-sm" placeholder="Price">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button class="btn btn-primary btn-sm w-100" onclick="addBillItem()"><i class="bi bi-plus-lg"></i> Add</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Items Table -->
|
|
<div class="table-responsive mb-3">
|
|
<table class="table table-sm table-bordered">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Description</th>
|
|
<th class="text-end" width="100">Amount</th>
|
|
<th width="50"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="checkout_items_table">
|
|
<!-- Items go here -->
|
|
</tbody>
|
|
<tfoot class="table-light fw-bold">
|
|
<tr>
|
|
<td class="text-end">Total</td>
|
|
<td class="text-end" id="checkout_total_amount">$0.00</td>
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Payment Details -->
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted">Insurance Coverage %</label>
|
|
<div class="input-group input-group-sm">
|
|
<input type="number" id="checkout_insurance_percent" class="form-control" value="0" min="0" max="100" onchange="calculateTotals()">
|
|
<span class="input-group-text">%</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted">Insurance Pays</label>
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text"><?php echo $currency_symbol ?? '$'; ?></span>
|
|
<input type="number" id="checkout_insurance_amount" class="form-control" readonly>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted fw-bold text-dark">Patient Pays</label>
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text"><?php echo $currency_symbol ?? '$'; ?></span>
|
|
<input type="number" id="checkout_patient_amount" class="form-control fw-bold" readonly>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Payment Method</label>
|
|
<select id="checkout_payment_method" class="form-select">
|
|
<option value="Cash">Cash</option>
|
|
<option value="Card">Card</option>
|
|
<option value="Online">Online</option>
|
|
<option value="Insurance">Insurance Only</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Notes</label>
|
|
<input type="text" id="checkout_notes" class="form-control" placeholder="Optional notes...">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="modal-footer border-0 bg-light rounded-bottom">
|
|
<button type="button" class="btn btn-outline-dark me-auto" onclick="printBill()"><i class="bi bi-printer"></i> <?php echo __('print_bill'); ?></button>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('close'); ?></button>
|
|
<button type="button" class="btn btn-success px-4" onclick="completePayment()">
|
|
<i class="bi bi-check2-circle me-1"></i> Pay & Close Visit
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchInput = document.getElementById('dashboardPatientSearch');
|
|
const tableBody = document.getElementById('dashboardPatientsTable');
|
|
let timeout = null;
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(function() {
|
|
const query = searchInput.value;
|
|
fetch('dashboard.php?ajax_search=1&search=' + encodeURIComponent(query))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
tableBody.innerHTML = data.html;
|
|
})
|
|
.catch(error => console.error('Error:', error));
|
|
}, 300);
|
|
});
|
|
|
|
// Initialize Select2
|
|
$('#dashboardAppointmentModal').on('shown.bs.modal', function() {
|
|
$('.select2-modal-dash').each(function() {
|
|
$(this).select2({ dropdownParent: $('#dashboardAppointmentModal'), theme: 'bootstrap-5', width: '100%' });
|
|
});
|
|
});
|
|
|
|
$('#receptionistVisitModal').on('shown.bs.modal', function() {
|
|
$('.select2-modal-quick').each(function() {
|
|
$(this).select2({ dropdownParent: $('#receptionistVisitModal'), theme: 'bootstrap-5', width: '100%' });
|
|
});
|
|
});
|
|
|
|
$('#checkoutModal').on('shown.bs.modal', function() {
|
|
$('.select2-modal-checkout').each(function() {
|
|
$(this).select2({ dropdownParent: $('#checkoutModal'), theme: 'bootstrap-5', width: '100%' });
|
|
|
|
// Helper for filling inputs on selection
|
|
$(this).on('select2:select', function(e) {
|
|
var data = e.params.data;
|
|
// Since select2 data doesn't keep data attributes easily without extra work,
|
|
// we'll access the underlying option
|
|
var element = $(this).find('option:selected');
|
|
var price = element.data('price');
|
|
var text = element.text();
|
|
|
|
if (price) {
|
|
document.getElementById('checkout_custom_item').value = text.split(' (')[0];
|
|
document.getElementById('checkout_item_price').value = price;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Auto-fill address
|
|
$('#dash_apt_patient_id').on('change', function() {
|
|
if (document.getElementById('dash_apt_visit_type').value === 'Home') {
|
|
var addressField = document.getElementById('dash_apt_address');
|
|
if (!addressField.value) {
|
|
var selectedOption = this.options[this.selectedIndex];
|
|
if (selectedOption && selectedOption.dataset.address) {
|
|
addressField.value = selectedOption.dataset.address;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// ... existing functions ...
|
|
|
|
function dashboard_toggleAddressField() {
|
|
var type = document.getElementById('dash_apt_visit_type').value;
|
|
var div = document.getElementById('dash_div_address');
|
|
if (type === 'Home') div.classList.remove('d-none'); else div.classList.add('d-none');
|
|
}
|
|
|
|
function dashboard_toggleProviderField() {
|
|
var type = document.getElementById('dash_apt_provider_type').value;
|
|
var divDoc = document.getElementById('dash_div_doctor');
|
|
var divNurse = document.getElementById('dash_div_nurse');
|
|
if (type === 'Nurse') { divDoc.classList.add('d-none'); divNurse.classList.remove('d-none'); }
|
|
else { divDoc.classList.remove('d-none'); divNurse.classList.add('d-none'); }
|
|
}
|
|
|
|
function showDashboardEditModal(data) {
|
|
document.getElementById('dash_apt_id').value = data.id;
|
|
$('#dash_apt_patient_id').val(data.patient_id).trigger('change');
|
|
document.getElementById('dash_apt_visit_type').value = data.visit_type || 'Clinic';
|
|
document.getElementById('dash_apt_address').value = data.address || '';
|
|
dashboard_toggleAddressField();
|
|
|
|
var providerType = data.nurse_id ? 'Nurse' : 'Doctor';
|
|
document.getElementById('dash_apt_provider_type').value = providerType;
|
|
dashboard_toggleProviderField();
|
|
|
|
if (providerType === 'Doctor') $('#dash_apt_doctor_id').val(data.doctor_id).trigger('change');
|
|
else $('#dash_apt_nurse_id').val(data.nurse_id).trigger('change');
|
|
|
|
var start = new Date(data.start_time);
|
|
if (!isNaN(start)) {
|
|
var offset = start.getTimezoneOffset() * 60000;
|
|
document.getElementById('dash_apt_start_time').value = (new Date(start.getTime() - offset)).toISOString().slice(0, 16);
|
|
} else {
|
|
document.getElementById('dash_apt_start_time').value = data.start_time.replace(' ', 'T').slice(0, 16);
|
|
}
|
|
|
|
document.getElementById('dash_apt_status').value = data.status;
|
|
document.getElementById('dash_apt_reason').value = data.reason || '';
|
|
new bootstrap.Modal(document.getElementById('dashboardAppointmentModal')).show();
|
|
}
|
|
|
|
function showReceptionistVisitModal(patientId) {
|
|
$('#quick_visit_patient_id').val(patientId || '').trigger('change');
|
|
$('#quick_visit_doctor_id').val('').trigger('change');
|
|
new bootstrap.Modal(document.getElementById('receptionistVisitModal')).show();
|
|
}
|
|
|
|
function dashboard_saveAppointment() {
|
|
var id = document.getElementById('dash_apt_id').value;
|
|
var providerType = document.getElementById('dash_apt_provider_type').value;
|
|
var data = {
|
|
action: 'update',
|
|
id: id,
|
|
patient_id: document.getElementById('dash_apt_patient_id').value,
|
|
doctor_id: providerType === 'Doctor' ? document.getElementById('dash_apt_doctor_id').value : null,
|
|
nurse_id: providerType === 'Nurse' ? document.getElementById('dash_apt_nurse_id').value : null,
|
|
visit_type: document.getElementById('dash_apt_visit_type').value,
|
|
address: document.getElementById('dash_apt_address').value,
|
|
start_time: document.getElementById('dash_apt_start_time').value,
|
|
status: document.getElementById('dash_apt_status').value,
|
|
reason: document.getElementById('dash_apt_reason').value
|
|
};
|
|
|
|
fetch('api/appointments.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) })
|
|
.then(r => r.json()).then(res => { if(res.success) window.location.reload(); else alert(res.error); });
|
|
}
|
|
|
|
function dashboard_deleteAppointment() {
|
|
var id = document.getElementById('dash_apt_id').value;
|
|
if (id && confirm('Delete?')) {
|
|
fetch('api/appointments.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({action:'delete', id:id}) })
|
|
.then(r => r.json()).then(res => { if(res.success) window.location.reload(); else alert(res.error); });
|
|
}
|
|
}
|
|
|
|
// --- BILLING FUNCTIONS ---
|
|
let currentBillId = 0;
|
|
let currentTotal = 0;
|
|
|
|
function fetchBillDetails(id, type = 'visit') {
|
|
const param = type === 'visit' ? `visit_id=${id}` : `bill_id=${id}`;
|
|
return fetch(`api/billing.php?action=get_bill_details&${param}`)
|
|
.then(r => r.json());
|
|
}
|
|
|
|
function showCheckoutModal(visitId) {
|
|
fetchBillDetails(visitId, 'visit')
|
|
.then(data => {
|
|
if (!data.success) { alert(data.error); return; }
|
|
|
|
updateCheckoutModalUI(data);
|
|
new bootstrap.Modal(document.getElementById('checkoutModal')).show();
|
|
});
|
|
}
|
|
|
|
function updateCheckoutModalUI(data) {
|
|
currentBillId = data.bill.id;
|
|
document.getElementById('checkout_bill_id').value = currentBillId;
|
|
document.getElementById('checkout_patient_name').innerText = data.visit.patient_name;
|
|
document.getElementById('checkout_insurance_name').innerText = data.insurance_name || 'None';
|
|
|
|
// Auto-set generic insurance percent if insured (e.g., 80% coverage)
|
|
// Only set if value is 0 (initial load) to avoid overwriting user input on refresh
|
|
if (data.has_insurance && data.bill.status === 'Pending' && parseFloat(data.bill.insurance_covered) === 0) {
|
|
// Do not force overwrite if user might have changed it, but for now kept simple
|
|
// document.getElementById('checkout_insurance_percent').value = 0;
|
|
}
|
|
|
|
renderBillItems(data.items);
|
|
}
|
|
|
|
function renderBillItems(items) {
|
|
const tbody = document.getElementById('checkout_items_table');
|
|
tbody.innerHTML = '';
|
|
let total = 0;
|
|
|
|
items.forEach(item => {
|
|
total += parseFloat(item.amount);
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${item.description}</td>
|
|
<td class="text-end">${formatCurrency(item.amount)}</td>
|
|
<td class="text-center">
|
|
<button class="btn btn-sm btn-link text-danger p-0" onclick="removeBillItem(${item.id})"><i class="bi bi-x-lg"></i></button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
currentTotal = total;
|
|
document.getElementById('checkout_total_amount').innerText = formatCurrency(total);
|
|
calculateTotals();
|
|
}
|
|
|
|
function calculateTotals() {
|
|
const percent = parseFloat(document.getElementById('checkout_insurance_percent').value) || 0;
|
|
const insuranceAmount = (currentTotal * percent) / 100;
|
|
const patientAmount = currentTotal - insuranceAmount;
|
|
|
|
document.getElementById('checkout_insurance_amount').value = insuranceAmount.toFixed(2);
|
|
document.getElementById('checkout_patient_amount').value = patientAmount.toFixed(2);
|
|
}
|
|
|
|
function addBillItem() {
|
|
const desc = document.getElementById('checkout_custom_item').value;
|
|
const price = document.getElementById('checkout_item_price').value;
|
|
|
|
if (!desc || !price) { alert('Please enter description and price'); return; }
|
|
|
|
const formData = new FormData();
|
|
formData.append('action', 'add_item');
|
|
formData.append('bill_id', currentBillId);
|
|
formData.append('description', desc);
|
|
formData.append('amount', price);
|
|
|
|
fetch('api/billing.php', { method: 'POST', body: formData })
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
refreshBillItems();
|
|
// Clear inputs
|
|
document.getElementById('checkout_custom_item').value = '';
|
|
document.getElementById('checkout_item_price').value = '';
|
|
$('#checkout_service_select').val('').trigger('change'); // Reset Select2
|
|
} else { alert(data.error); }
|
|
});
|
|
}
|
|
|
|
function removeBillItem(itemId) {
|
|
if (!confirm('Remove item?')) return;
|
|
const formData = new FormData();
|
|
formData.append('action', 'remove_item');
|
|
formData.append('item_id', itemId);
|
|
|
|
fetch('api/billing.php', { method: 'POST', body: formData })
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) refreshBillItems();
|
|
else alert(data.error);
|
|
});
|
|
}
|
|
|
|
function refreshBillItems() {
|
|
fetchBillDetails(currentBillId, 'bill')
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateCheckoutModalUI(data);
|
|
} else {
|
|
alert(data.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function completePayment() {
|
|
const formData = new FormData();
|
|
formData.append('action', 'complete_payment');
|
|
formData.append('bill_id', currentBillId);
|
|
formData.append('payment_method', document.getElementById('checkout_payment_method').value);
|
|
formData.append('notes', document.getElementById('checkout_notes').value);
|
|
|
|
const insurance = document.getElementById('checkout_insurance_amount').value;
|
|
const patient = document.getElementById('checkout_patient_amount').value;
|
|
|
|
const updateData = new FormData();
|
|
updateData.append('action', 'update_totals');
|
|
updateData.append('bill_id', currentBillId);
|
|
updateData.append('insurance_covered', insurance);
|
|
updateData.append('patient_payable', patient);
|
|
|
|
fetch('api/billing.php', { method: 'POST', body: updateData })
|
|
.then(() => {
|
|
return fetch('api/billing.php', { method: 'POST', body: formData });
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Payment Recorded!');
|
|
window.location.reload();
|
|
} else { alert(data.error); }
|
|
});
|
|
}
|
|
|
|
function printBill() {
|
|
if (currentBillId) {
|
|
window.open('print_bill.php?bill_id=' + currentBillId, '_blank');
|
|
}
|
|
}
|
|
</script>
|