Enhance OPD and patient workflows with live updates and UX improvements

This commit is contained in:
DevSoftTech Server 2026-04-07 03:27:39 +05:30
parent 16f654289e
commit 19db43762d
8 changed files with 1124 additions and 242 deletions

View File

@ -470,6 +470,10 @@ $routes->get('Opd/get_appointment_data', 'Opd::get_appointment_data');
$routes->get('Opd/get_appointment_data/(:segment)', 'Opd::get_appointment_data/$1');
$routes->get('Opd/get_appointment_list/(:num)', 'Opd::get_appointment_list/$1');
$routes->get('Opd/get_appointment_list/(:num)/(:segment)', 'Opd::get_appointment_list/$1/$2');
$routes->get('Opd/get_appointment_digest/(:num)', 'Opd::get_appointment_digest/$1');
$routes->get('Opd/get_appointment_digest/(:num)/(:segment)', 'Opd::get_appointment_digest/$1/$2');
$routes->get('Opd/get_appointment_live_rows/(:num)', 'Opd::get_appointment_live_rows/$1');
$routes->get('Opd/get_appointment_live_rows/(:num)/(:segment)', 'Opd::get_appointment_live_rows/$1/$2');
$routes->match(['get', 'post'], 'Opd/opd_status/(:num)/(:num)', 'Opd::opd_status/$1/$2');
$routes->post('Opd/showfee', 'Opd::showfee');
$routes->post('Opd/confirm_opd', 'Opd::confirm_opd');

View File

@ -934,6 +934,202 @@ class Opd extends BaseController
]);
}
/**
* Lightweight digest endpoint for live appointment screen refresh checks.
*/
public function get_appointment_digest(int $docId, string $opdDate = '')
{
if (! $this->canAccessDoctorWorkPanel()) {
return $this->response->setStatusCode(403)->setJSON([
'ok' => 0,
'message' => 'Access denied',
]);
}
if ($opdDate === '') {
$opdDate = date('Y-m-d');
}
$docId = (int) $docId;
$opdDateSql = $this->db->escape($opdDate);
$sql = "select
count(*) as total_rows,
sum(case when o.opd_status = 1 and pr.id is null then 1 else 0 end) as count_booking,
sum(case when o.opd_status = 1 and pr.id is not null then 1 else 0 end) as count_wait,
sum(case when o.opd_status = 2 then 1 else 0 end) as count_visit,
sum(case when o.opd_status = 3 then 1 else 0 end) as count_cancel,
coalesce(
bit_xor(
cast(
crc32(
concat_ws('|',
o.opd_id,
o.opd_status,
if(pr.id is null, 0, 1),
if(
coalesce(trim(pr.pulse),'')<>''
or coalesce(trim(pr.spo2),'')<>''
or coalesce(trim(pr.bp),'')<>''
or coalesce(trim(pr.diastolic),'')<>''
or coalesce(trim(pr.temp),'')<>''
or coalesce(trim(pr.rr_min),'')<>''
or coalesce(trim(pr.height),'')<>''
or coalesce(trim(pr.weight),'')<>''
or coalesce(trim(pr.waist),'')<>'',
1,0
),
coalesce(pr.queue_no,0),
coalesce(o.payment_status,0),
coalesce(o.confirm_pay_opd,'')
)
) as unsigned
)
),
0
) as digest_crc
from opd_master o
left join opd_prescription pr on o.opd_id = pr.opd_id
where o.apointment_date = " . $opdDateSql . " and o.doc_id = " . $docId;
$row = $this->db->query($sql)->getRow();
$payload = [
'doc_id' => $docId,
'opd_date' => $opdDate,
'total' => (int) ($row->total_rows ?? 0),
'booked' => (int) ($row->count_booking ?? 0),
'waiting' => (int) ($row->count_wait ?? 0),
'visited' => (int) ($row->count_visit ?? 0),
'cancelled' => (int) ($row->count_cancel ?? 0),
'crc' => (string) ($row->digest_crc ?? '0'),
];
$digest = sha1(json_encode($payload));
return $this->response->setJSON([
'ok' => 1,
'digest' => $digest,
'payload' => $payload,
'server_time' => date('c'),
]);
}
/**
* Live snapshot endpoint for in-place row merge on OPD appointment screen.
*/
public function get_appointment_live_rows(int $docId, string $opdDate = '')
{
if (! $this->canAccessDoctorWorkPanel()) {
return $this->response->setStatusCode(403)->setJSON([
'ok' => 0,
'message' => 'Access denied',
]);
}
if ($opdDate === '') {
$opdDate = date('Y-m-d');
}
$docId = (int) $docId;
$opdDateSql = $this->db->escape($opdDate);
$sql = "select
o.opd_id,
o.opd_status,
o.payment_status,
o.opd_fee_type,
o.opd_fee_amount,
o.opd_fee_desc,
coalesce(o.running_opd,0) as running_opd,
o.confirm_pay_opd,
if(pr.id is null, 0, 1) as has_prescription,
if(
coalesce(trim(pr.pulse),'')<>''
or coalesce(trim(pr.spo2),'')<>''
or coalesce(trim(pr.bp),'')<>''
or coalesce(trim(pr.diastolic),'')<>''
or coalesce(trim(pr.temp),'')<>''
or coalesce(trim(pr.rr_min),'')<>''
or coalesce(trim(pr.height),'')<>''
or coalesce(trim(pr.weight),'')<>''
or coalesce(trim(pr.waist),'')<>'',
1,0
) as has_vitals,
coalesce(pr.queue_no,0) as queue_no
from opd_master o
left join opd_prescription pr on o.opd_id = pr.opd_id
where o.apointment_date = " . $opdDateSql . " and o.doc_id = " . $docId;
$rows = $this->db->query($sql)->getResult();
$normalized = [];
$counts = [
'all' => 0,
'booked' => 0,
'waiting' => 0,
'visited' => 0,
'cancelled' => 0,
];
foreach ($rows as $row) {
$status = (int) ($row->opd_status ?? 0);
$hasPrescription = (int) ($row->has_prescription ?? 0) === 1;
$paymentStatus = (int) ($row->payment_status ?? 0);
$runningOpd = (int) ($row->running_opd ?? 0) === 1;
$opdFeeType = (int) ($row->opd_fee_type ?? 0);
$opdFeeAmount = (float) ($row->opd_fee_amount ?? 0);
$opdFeeDesc = strtoupper(trim((string) ($row->opd_fee_desc ?? '')));
$isRunningVisit = $runningOpd || $opdFeeType === 3 || str_contains($opdFeeDesc, 'RUNNING');
$isConfirmedZeroAmount = $isRunningVisit && $opdFeeAmount <= 0;
$isConfirmedForQueue = $paymentStatus === 1 || $isConfirmedZeroAmount || !empty($row->confirm_pay_opd);
$statusLabel = 'booked';
if ($status === 1 && ($hasPrescription || $isConfirmedForQueue)) {
$statusLabel = 'waiting';
} elseif ($status === 2) {
$statusLabel = 'visited';
} elseif ($status === 3) {
$statusLabel = 'cancelled';
}
$normalized[] = [
'opd_id' => (int) ($row->opd_id ?? 0),
'status' => $statusLabel,
'has_prescription' => $hasPrescription ? 1 : 0,
'has_vitals' => (int) ($row->has_vitals ?? 0),
'queue_no' => (int) ($row->queue_no ?? 0),
'is_confirmed_for_queue' => $isConfirmedForQueue ? 1 : 0,
];
$counts['all']++;
if (array_key_exists($statusLabel, $counts)) {
$counts[$statusLabel]++;
}
}
usort($normalized, static function ($left, $right): int {
return ((int) ($left['opd_id'] ?? 0)) <=> ((int) ($right['opd_id'] ?? 0));
});
$digest = sha1(json_encode([
'doc_id' => $docId,
'opd_date' => $opdDate,
'counts' => $counts,
'rows' => $normalized,
]));
return $this->response->setJSON([
'ok' => 1,
'digest' => $digest,
'doc_id' => $docId,
'opd_date' => $opdDate,
'counts' => $counts,
'rows' => $normalized,
'server_time' => date('c'),
]);
}
/**
* Update OPD status from queue actions.
*/
@ -3749,6 +3945,8 @@ class Opd extends BaseController
$this->db->table('opd_master')->where('opd_id', $oid)->update($data);
$this->autoCreateOpdQueue($oid);
return $this->response->setJSON([
'update' => 1,
'showcontent' => 'Zero Cost',
@ -3785,6 +3983,8 @@ class Opd extends BaseController
$this->db->table('opd_master')->where('opd_id', $oid)->update($data);
$this->autoCreateOpdQueue($oid);
return $this->response->setJSON([
'update' => 1,
'showcontent' => 'CASH',
@ -3832,6 +4032,8 @@ class Opd extends BaseController
$this->db->table('opd_master')->where('opd_id', $oid)->update($data);
$this->autoCreateOpdQueue($oid);
return $this->response->setJSON([
'update' => 1,
'showcontent' => 'Bank Card',
@ -3852,6 +4054,8 @@ class Opd extends BaseController
$this->db->table('opd_master')->where('opd_id', $oid)->update($data);
$this->autoCreateOpdQueue($oid);
return $this->response->setJSON([
'update' => 1,
'showcontent' => 'Org. Case Credit',
@ -3868,6 +4072,64 @@ class Opd extends BaseController
]);
}
private function autoCreateOpdQueue(int $opdId): void
{
if ($opdId <= 0 || ! $this->db->tableExists('opd_prescription')) {
return;
}
// If a queue row already exists for this OPD, do nothing.
$existing = $this->db->table('opd_prescription')
->where('opd_id', $opdId)
->get(1)
->getRow();
if ($existing) {
return;
}
$opdRow = $this->db->table('opd_master')
->where('opd_id', $opdId)
->get(1)
->getRow();
if (! $opdRow) {
return;
}
$fields = $this->db->getFieldNames('opd_prescription');
$insert = [];
if (in_array('opd_id', $fields, true)) {
$insert['opd_id'] = $opdId;
}
if (in_array('p_id', $fields, true)) {
$insert['p_id'] = (int) ($opdRow->p_id ?? 0);
}
if (in_array('doc_id', $fields, true)) {
$insert['doc_id'] = (int) ($opdRow->doc_id ?? 0);
}
if (in_array('date_opd_visit', $fields, true)) {
$insert['date_opd_visit'] = date('Y-m-d');
}
if (in_array('visit_status', $fields, true)) {
$insert['visit_status'] = 0;
}
if (in_array('session_id', $fields, true)) {
$insert['session_id'] = 0;
}
if (in_array('queue_no', $fields, true)) {
$maxQueue = $this->db->table('opd_prescription')
->selectMax('queue_no', 'max_queue')
->where('date_opd_visit', date('Y-m-d'))
->where('doc_id', (int) ($opdRow->doc_id ?? 0))
->get()
->getRow();
$insert['queue_no'] = (int) ($maxQueue->max_queue ?? 0) + 1;
}
if (! empty($insert)) {
$this->db->table('opd_prescription')->insert($insert);
}
}
public function opd_discount_update(int $opdId)
{
if (!$this->request->isAJAX()) {

View File

@ -192,37 +192,62 @@ class Patient extends BaseController
{
$inputMphone1 = trim((string) $this->request->getPost('input_mphone1'));
$inputAadhar = trim((string) $this->request->getPost('input_udai'));
$inputAbhaId = trim((string) $this->request->getPost('input_abha_id'));
$inputRelativeName = trim((string) $this->request->getPost('input_relative_name'));
$inputName = trim((string) $this->request->getPost('input_name'));
$abhaField = $this->resolvePatientAbhaIdField();
$builder = $this->db->table('patient_master');
$builder->select("id, CONCAT(p_fname,'/',IF(gender=1,'M','F'),'/',p_relative,' ',p_rname) AS Sresult");
$builder->select("id,p_fname,p_relative,p_rname,gender,add1,city,district,state,zip,mphone1,udai,last_visit,dob,age,age_in_month,estimate_dob");
if ($abhaField !== null) {
$builder->select($abhaField . ' AS abha_id');
}
$hasCondition = false;
if ($inputMphone1 !== '') {
$builder->orWhere('mphone1', $inputMphone1);
$hasCondition = true;
}
if ($inputMphone1 !== '' || $inputAadhar !== '' || $inputAbhaId !== '' || ($inputRelativeName !== '' && $inputName !== '')) {
$builder->groupStart();
if ($inputAadhar !== '') {
$builder->orWhere('udai', $inputAadhar);
$hasCondition = true;
}
if ($inputMphone1 !== '') {
$builder->where('mphone1', $inputMphone1);
$hasCondition = true;
}
if ($inputRelativeName !== '') {
$builder->groupStart()
->where('p_rname', $inputRelativeName)
->where('p_fname', $inputName)
->groupEnd();
$hasCondition = true;
if ($inputAadhar !== '') {
$builder->orWhere('udai', $inputAadhar);
$hasCondition = true;
}
if ($inputAbhaId !== '' && $abhaField !== null) {
$builder->orWhere($abhaField, $inputAbhaId);
$hasCondition = true;
}
if ($inputRelativeName !== '' && $inputName !== '') {
$builder->orGroupStart()
->where('p_rname', trim(strtoupper($inputRelativeName)))
->where('p_fname', trim(strtoupper($inputName)))
->groupEnd();
$hasCondition = true;
}
$builder->groupEnd();
}
$searchResult = [];
if ($hasCondition) {
$searchResult = $builder->orderBy('id', 'DESC')->get()->getResult();
$searchResult = $builder->orderBy('id', 'DESC')->limit(25)->get()->getResult();
}
return view('billing/search_adv_data', ['search_result' => $searchResult]);
return view('billing/search_adv_data', [
'search_result' => $searchResult,
'filters' => [
'input_mphone1' => $inputMphone1,
'input_udai' => strtoupper($inputAadhar),
'input_abha_id' => $inputAbhaId,
'input_relative_name' => strtoupper($inputRelativeName),
'input_name' => strtoupper($inputName),
],
]);
}
public function abha_fetch_profile()

View File

@ -282,6 +282,14 @@
$('#input_abha_id').on('change blur', function() {
var abhaId = ($(this).val() || '').toString().trim();
if (abhaId !== '') {
refreshAdvancedSearchResult({
input_abha_id: abhaId
});
} else {
$('#search_result_update').html('');
}
if (!/^\d{14}$/.test(abhaId)) {
return;
}
@ -470,41 +478,36 @@
};
}
function refreshAdvancedSearchResult(payload) {
var csrf = getCsrfData();
var postData = $.extend({}, payload || {});
postData[csrf.name] = csrf.value;
$.post('<?= base_url('billing/patient/search_adv') ?>', postData, function(data) {
$('#search_result_update').html(data);
});
}
function onchange_feild_number(control_button) {
var input_mphone1 = $('#input_mphone1').val();
var csrf = getCsrfData();
$.post('<?= base_url('billing/patient/search_adv') ?>', {
"input_mphone1": input_mphone1,
[csrf.name]: csrf.value
}, function(data) {
$('#search_result_update').html(data);
refreshAdvancedSearchResult({
"input_mphone1": input_mphone1
});
}
function onchange_aadhar(control_button) {
var input_udai = $('#input_udai').val();
var csrf = getCsrfData();
$.post('<?= base_url('billing/patient/search_adv') ?>', {
"input_udai": input_udai,
[csrf.name]: csrf.value
}, function(data) {
$('#search_result_update').html(data);
refreshAdvancedSearchResult({
"input_udai": input_udai
});
}
function onchange_relative_name(control_button) {
var input_relative_name = $('#input_relative_name').val();
var input_name = $('#input_name').val();
var csrf = getCsrfData();
$.post('<?= base_url('billing/patient/search_adv') ?>', {
refreshAdvancedSearchResult({
"input_relative_name": input_relative_name,
"input_name": input_name,
[csrf.name]: csrf.value
}, function(data) {
$('#search_result_update').html(data);
"input_name": input_name
});
}

View File

@ -29,9 +29,10 @@
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<tr><td>Booked</td><td class="text-end"><span class="badge bg-secondary\"><?= esc((int) ($row->count_booking ?? 0)) ?></span></td></tr>
<tr><td>Waiting</td><td class="text-end"><span class="badge bg-primary\"><?= esc((int) ($row->count_wait ?? 0)) ?></span></td></tr>
<tr><td>Visited</td><td class="text-end"><span class="badge bg-info\"><?= esc((int) ($row->count_visit ?? 0)) ?></span></td></tr>
<tr><td>Booked</td><td class="text-end"><span class="badge bg-secondary"><?= esc((int) ($row->count_booking ?? 0)) ?></span></td></tr>
<tr><td>Waiting</td><td class="text-end"><span class="badge bg-primary"><?= esc((int) ($row->count_wait ?? 0)) ?></span></td></tr>
<tr><td>Visited</td><td class="text-end"><span class="badge bg-info"><?= esc((int) ($row->count_visit ?? 0)) ?></span></td></tr>
<tr><td>Cancelled</td><td class="text-end"><span class="badge bg-warning text-dark"><?= esc((int) ($row->count_cancel ?? 0)) ?></span></td></tr>
<tr><td>Total</td><td class="text-end"><span class="badge bg-danger"><?= esc($total) ?></span></td></tr>
</table>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,14 @@ if ($user) {
$paymentHistoryRows = $payment_history_rows ?? [];
?>
<section class="content-header">
<h1>OPD Invoice</h1>
<ol class="breadcrumb">
<li><a href="javascript:load_form('<?= base_url('billing/patient/person_record') ?>/<?= esc($opd_master[0]->p_id ?? 0) ?>/0');">Person Home</a></li>
</ol>
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<h1 class="mb-0">OPD Invoice</h1>
<button type="button"
class="btn btn-outline-primary"
onclick="load_form('<?= base_url('billing/patient/person_record') ?>/<?= esc($opd_master[0]->p_id ?? 0) ?>/0','Patient Record');">
Person Home
</button>
</div>
</section>
<section class="invoice">

View File

@ -1,11 +1,193 @@
<?php if (!empty($search_result)) : ?>
<ul class="list-group">
<?php
$formatDate = static function ($value): string {
$value = trim((string) $value);
if ($value === '' || $value === '0000-00-00') {
return '';
}
$dt = \DateTime::createFromFormat('Y-m-d', $value);
if (! $dt) {
return '';
}
return $dt->format('d-m-Y');
};
$getAgeDisplay = static function ($row): string {
$dob = trim((string) ($row->dob ?? ''));
if ($dob !== '' && $dob !== '0000-00-00') {
$dobDate = date_create($dob);
if ($dobDate !== false) {
$ageYears = date_diff($dobDate, new \DateTime('today'))->y;
return (string) $ageYears;
}
}
$years = trim((string) ($row->age ?? ''));
$months = trim((string) ($row->age_in_month ?? ''));
if ($years !== '') {
return $months !== '' ? ($years . 'y ' . $months . 'm') : $years;
}
return '';
};
$genderLabel = static function ($gender): string {
$gender = trim((string) $gender);
if ($gender === '1') {
return 'Male';
}
if ($gender === '2') {
return 'Female';
}
return $gender !== '' ? $gender : 'NA';
};
$normalize = static function ($value): string {
return strtoupper(trim((string) $value));
};
$filters = $filters ?? [];
$filterPhone = trim((string) ($filters['input_mphone1'] ?? ''));
$filterAadhaar = $normalize($filters['input_udai'] ?? '');
$filterAbha = trim((string) ($filters['input_abha_id'] ?? ''));
$filterName = $normalize($filters['input_name'] ?? '');
$filterRelativeName = $normalize($filters['input_relative_name'] ?? '');
$getMatchScore = static function ($row) use ($normalize, $filterPhone, $filterAadhaar, $filterAbha, $filterName, $filterRelativeName): int {
$score = 0;
if ($filterPhone !== '' && trim((string) ($row->mphone1 ?? '')) === $filterPhone) {
$score++;
}
if ($filterAadhaar !== '' && $normalize($row->udai ?? '') === $filterAadhaar) {
$score++;
}
if ($filterAbha !== '' && trim((string) ($row->abha_id ?? '')) === $filterAbha) {
$score++;
}
if (
$filterName !== ''
&& $filterRelativeName !== ''
&& $normalize($row->p_fname ?? '') === $filterName
&& $normalize($row->p_rname ?? '') === $filterRelativeName
) {
$score++;
}
return $score;
};
$getConfidence = static function (int $score): array {
if ($score >= 3) {
return ['High', 'bg-danger'];
}
if ($score === 2) {
return ['Medium', 'bg-warning text-dark'];
}
return ['Low', 'bg-secondary'];
};
if (! empty($search_result) && is_array($search_result)) {
usort($search_result, static function ($a, $b) use ($getMatchScore) {
$scoreA = $getMatchScore($a);
$scoreB = $getMatchScore($b);
if ($scoreA !== $scoreB) {
return $scoreB <=> $scoreA;
}
return ((int) ($b->id ?? 0)) <=> ((int) ($a->id ?? 0));
});
}
?>
<?php if (! empty($search_result)) : ?>
<div class="list-group">
<?php foreach ($search_result as $row) : ?>
<li class="list-group-item">
<?= esc($row->Sresult ?? '') ?>
</li>
<?php endforeach ?>
</ul>
<?php
$profileUrl = base_url('billing/patient/person_record') . '/' . (int) ($row->id ?? 0);
$fullName = trim((string) ($row->p_fname ?? ''));
$relative = trim((string) ($row->p_relative ?? '')) . ' ' . trim((string) ($row->p_rname ?? ''));
$relative = trim($relative);
$addressParts = array_filter([
trim((string) ($row->add1 ?? '')),
trim((string) ($row->city ?? '')),
trim((string) ($row->district ?? '')),
trim((string) ($row->state ?? '')),
trim((string) ($row->zip ?? '')),
], static fn($v) => $v !== '');
$addressText = implode(', ', $addressParts);
$lastVisit = $formatDate($row->last_visit ?? '');
$ageDisplay = $getAgeDisplay($row);
$gender = $genderLabel($row->gender ?? '');
$matchReasons = [];
if ($filterPhone !== '' && trim((string) ($row->mphone1 ?? '')) === $filterPhone) {
$matchReasons[] = 'Phone';
}
if ($filterAadhaar !== '' && $normalize($row->udai ?? '') === $filterAadhaar) {
$matchReasons[] = 'Aadhaar';
}
if ($filterAbha !== '' && trim((string) ($row->abha_id ?? '')) === $filterAbha) {
$matchReasons[] = 'ABHA';
}
if (
$filterName !== ''
&& $filterRelativeName !== ''
&& $normalize($row->p_fname ?? '') === $filterName
&& $normalize($row->p_rname ?? '') === $filterRelativeName
) {
$matchReasons[] = 'Name + Relative';
}
$isStrongDuplicate = count($matchReasons) > 1;
$matchScore = $getMatchScore($row);
[$confidenceText, $confidenceClass] = $getConfidence($matchScore);
?>
<div class="list-group-item list-group-item-action <?= $isStrongDuplicate ? 'border border-warning' : '' ?>"
role="button"
tabindex="0"
onclick="load_form('<?= esc($profileUrl) ?>', 'Patient Record');"
onkeydown="if(event.key==='Enter' || event.key===' '){event.preventDefault();load_form('<?= esc($profileUrl) ?>', 'Patient Record');}">
<div class="d-flex justify-content-between align-items-start gap-2">
<div class="fw-semibold"><?= esc($fullName !== '' ? $fullName : 'Unknown') ?></div>
<button type="button"
class="btn btn-sm btn-outline-primary"
onclick="event.stopPropagation(); load_form('<?= esc($profileUrl) ?>', 'Patient Record');">
View Profile
</button>
</div>
<div class="small text-muted"><?= esc($relative !== '' ? $relative : 'Relative: NA') ?></div>
<?php if (! empty($matchReasons)) : ?>
<div class="mt-1">
<span class="badge <?= esc($confidenceClass) ?> me-1">Confidence: <?= esc($confidenceText) ?></span>
<?php foreach ($matchReasons as $reason) : ?>
<span class="badge <?= $isStrongDuplicate ? 'bg-warning text-dark' : 'bg-info text-dark' ?> me-1">Match: <?= esc($reason) ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="mt-1 small">
<span class="me-2"><strong>Gender:</strong> <?= esc($gender) ?></span>
<?php if ($ageDisplay !== '') : ?>
<span class="me-2"><strong>Age:</strong> <?= esc($ageDisplay) ?></span>
<?php endif; ?>
<?php if ($lastVisit !== '') : ?>
<span><strong>Last Visit:</strong> <?= esc($lastVisit) ?></span>
<?php endif; ?>
</div>
<?php if ($addressText !== '') : ?>
<div class="small mt-1"><strong>Address:</strong> <?= esc($addressText) ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php else : ?>
<div class="text-muted">No matching records.</div>
<?php endif ?>
<?php endif; ?>