Convert patient search to DataTables with AJAX server-side processing
- Updated Patient::search() to only render view with search query - Created Patient::search_ajax() method for DataTables AJAX endpoint - Implemented server-side processing with pagination, sorting, and filtering - Results ordered by Last Visit DESC by default - Retrieves all patient data from patient_master table - Added proper SQL escaping to prevent injection - Updated Patient_Search_V.php to use DataTables with AJAX remote data - Added DataTables configuration with 25 rows per page default - Supports dynamic length menu (10, 25, 50, 100, All) - Added route for billing/patient/search_ajax endpoint - Improved user experience with loading spinner and customized messages
This commit is contained in:
parent
550e72eca8
commit
d82078c9ed
|
|
@ -457,6 +457,7 @@ $routes->group('billing', function($routes) {
|
|||
$routes->get('patient/search_opd', 'Patient::search_opd');
|
||||
$routes->post('patient/create', 'Patient::create');
|
||||
$routes->post('patient/search', 'Patient::search');
|
||||
$routes->get('billing/patient/search_ajax', 'Patient::search_ajax');
|
||||
$routes->post('patient/search_adv', 'Patient::search_adv');
|
||||
$routes->post('patient/abha_fetch_profile', 'Patient::abha_fetch_profile');
|
||||
$routes->post('patient/abha_aadhaar_generate_otp', 'Patient::abhaAadhaarGenerateOtp');
|
||||
|
|
|
|||
|
|
@ -408,6 +408,16 @@ class Patient extends BaseController
|
|||
$sdata = (string) $this->request->getPost('txtsearch');
|
||||
$sdata = preg_replace('/[^A-Za-z0-9 _.@\-]/', '', trim($sdata ?? ''));
|
||||
|
||||
$data['search_query'] = $sdata;
|
||||
return view('billing/Patient_Search_V', $data);
|
||||
}
|
||||
|
||||
public function search_ajax()
|
||||
{
|
||||
$request = $this->request->getGet();
|
||||
$sdata = trim((string) ($request['search_query'] ?? ''));
|
||||
$sdata = preg_replace('/[^A-Za-z0-9 _.@\-]/', '', $sdata);
|
||||
|
||||
// Detect ABHA column name in patient_master
|
||||
$abhaField = null;
|
||||
$pmFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
|
|
@ -415,16 +425,10 @@ class Patient extends BaseController
|
|||
if (in_array($f, $pmFields, true)) { $abhaField = $f; break; }
|
||||
}
|
||||
|
||||
if (strlen($sdata) === 0) {
|
||||
$sql = "SELECT p.*,
|
||||
Date_Format(p.last_visit,'%d-%m-%Y') AS Last_Visit
|
||||
FROM patient_master p
|
||||
GROUP BY p.id
|
||||
ORDER BY p.last_visit DESC
|
||||
LIMIT 100 ";
|
||||
} else {
|
||||
// Build WHERE clause based on search query
|
||||
$searchString = ' 1=1 ';
|
||||
if (strlen($sdata) > 0) {
|
||||
$sdateArray = explode(' ', $sdata);
|
||||
$searchString = ' 1=1 ';
|
||||
|
||||
foreach ($sdateArray as $rowData) {
|
||||
if ($rowData === '') {
|
||||
|
|
@ -432,37 +436,86 @@ class Patient extends BaseController
|
|||
}
|
||||
|
||||
if (is_numeric($rowData)) {
|
||||
$abhaClause = $abhaField ? " or p.{$abhaField} = '$rowData'" : '';
|
||||
$searchString .= " and (p.p_code like '%$rowData'
|
||||
or p.mphone1 = '$rowData'
|
||||
or p.udai='$rowData'$abhaClause)";
|
||||
$abhaClause = $abhaField ? " or p.{$abhaField} = " . $this->db->escape($rowData) : '';
|
||||
$searchString .= " and (p.p_code like " . $this->db->escape('%' . $rowData) . "
|
||||
or p.mphone1 = " . $this->db->escape($rowData) . "
|
||||
or p.udai=" . $this->db->escape($rowData) . $abhaClause . ")";
|
||||
} elseif (ctype_alpha($rowData)) {
|
||||
$searchString .= " and (p.p_fname like '%$rowData%'
|
||||
or p.email1 = '$rowData'
|
||||
or SUBSTRING_INDEX(p.p_fname,' ',1) sounds like '$rowData')";
|
||||
$searchString .= " and (p.p_fname like " . $this->db->escape('%' . $rowData . '%') . "
|
||||
or p.email1 = " . $this->db->escape($rowData) . "
|
||||
or SUBSTRING_INDEX(p.p_fname,' ',1) sounds like " . $this->db->escape($rowData) . ")";
|
||||
} else {
|
||||
// Handle dashed ABHA format: XX-XXXX-XXXX-XXXX
|
||||
$rawDigits = preg_replace('/\D/', '', $rowData);
|
||||
$abhaElse = ($abhaField && strlen($rawDigits) === 14)
|
||||
? " or p.{$abhaField} = '$rawDigits' or p.{$abhaField} = '$rowData'"
|
||||
? " or p.{$abhaField} = " . $this->db->escape($rawDigits) . " or p.{$abhaField} = " . $this->db->escape($rowData)
|
||||
: '';
|
||||
$searchString .= " and (p.p_code like '$rowData'
|
||||
or p.email1 = '$rowData'$abhaElse)";
|
||||
$searchString .= " and (p.p_code like " . $this->db->escape($rowData) . "
|
||||
or p.email1 = " . $this->db->escape($rowData) . $abhaElse . ")";
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT p.*,
|
||||
Date_Format(p.last_visit,'%d-%m-%Y') AS Last_Visit
|
||||
FROM patient_master p
|
||||
WHERE $searchString
|
||||
GROUP BY p.id
|
||||
ORDER BY p.last_visit DESC";
|
||||
}
|
||||
|
||||
$query = $this->db->query($sql);
|
||||
$data['data'] = $query->getResult();
|
||||
// Count total records (without filtering)
|
||||
$totalSql = "SELECT COUNT(DISTINCT p.id) as total FROM patient_master p";
|
||||
$totalData = (int) $this->db->query($totalSql)->getRow()->total;
|
||||
|
||||
return view('billing/Patient_Search_V', $data);
|
||||
// Count filtered records
|
||||
$filteredSql = "SELECT COUNT(DISTINCT p.id) as total FROM patient_master p WHERE $searchString";
|
||||
$totalFiltered = (int) $this->db->query($filteredSql)->getRow()->total;
|
||||
|
||||
// Get pagination and sorting parameters
|
||||
$start = isset($request['start']) ? (int) $request['start'] : 0;
|
||||
$length = isset($request['length']) ? (int) $request['length'] : 10;
|
||||
|
||||
// Columns for ordering
|
||||
$columns = ['', 'p.p_code', 'p.p_fname', 'p.age', 'p.last_visit', 'p.insurance_id', ''];
|
||||
$orderColumn = 'p.last_visit';
|
||||
$orderDir = 'DESC';
|
||||
|
||||
if (!empty($request['order'][0]['column']) && is_numeric($request['order'][0]['column'])) {
|
||||
$orderIndex = (int) $request['order'][0]['column'];
|
||||
if (isset($columns[$orderIndex]) && $columns[$orderIndex] !== '') {
|
||||
$orderColumn = $columns[$orderIndex];
|
||||
}
|
||||
$orderDir = strtoupper($request['order'][0]['dir'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';
|
||||
}
|
||||
|
||||
// Get data
|
||||
$sql = "SELECT p.*,
|
||||
Date_Format(p.last_visit,'%d-%m-%Y') AS Last_Visit,
|
||||
IF(p.insurance_id = 0 OR p.insurance_id IS NULL, 'Self', 'Insuranced') as insurance_status
|
||||
FROM patient_master p
|
||||
WHERE $searchString
|
||||
GROUP BY p.id
|
||||
ORDER BY $orderColumn $orderDir
|
||||
LIMIT $start, $length";
|
||||
|
||||
$records = $this->db->query($sql)->getResult();
|
||||
|
||||
// Format data for DataTables
|
||||
$data = [];
|
||||
foreach ($records as $index => $row) {
|
||||
$patientId = (int) ($row->id ?? 0);
|
||||
$age = esc(get_age_1($row->dob ?? null, $row->age ?? '', $row->age_in_month ?? '', $row->estimate_dob ?? '', $row->Last_Visit ?? null));
|
||||
|
||||
$data[] = [
|
||||
$start + $index + 1,
|
||||
'<a href="javascript:load_form(\'' . base_url('billing/patient/person_record/' . $patientId) . '\');">' . esc($row->p_code ?? '') . '</a>',
|
||||
esc(($row->p_fname ?? '') . ' {' . ($row->p_rname ?? '') . '}'),
|
||||
$age,
|
||||
esc($row->Last_Visit ?? ''),
|
||||
esc($row->insurance_status ?? 'Self'),
|
||||
'<a href="javascript:load_form(\'' . base_url('billing/patient/show_profile_opd/' . $patientId . '/1') . '\');" class="btn btn-info btn-xs"><span class="fa fa-history"></span> Patient History</a>'
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'draw' => isset($request['draw']) ? (int) $request['draw'] : 0,
|
||||
'recordsTotal' => $totalData,
|
||||
'recordsFiltered' => $totalFiltered,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
public function person_record(int $pno, int $edit = 0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<br /><br />
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Result</h3>
|
||||
<h3 class="box-title">Patient Search Results</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
|
|
@ -11,35 +11,20 @@
|
|||
<th>Sr.No.</th>
|
||||
<th>Patient/UHID Code</th>
|
||||
<th>Name {Relative Name}</th>
|
||||
<th>Age</th>
|
||||
<th>Age</th>
|
||||
<th>Last Visit</th>
|
||||
<th>Insurance</th>
|
||||
<th>Patient History</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($i = 0; $i < count($data); ++$i) { ?>
|
||||
<tr>
|
||||
<td><?=$i+1?></td>
|
||||
<td><a href="javascript:load_form('<?= base_url('billing/patient/person_record') ?>/<?=$data[$i]->id ?>');"><?=$data[$i]->p_code ?></a></td>
|
||||
<td><?=$data[$i]->p_fname ?> {<?=$data[$i]->p_rname ?>}</td>
|
||||
<td><?= esc(get_age_1($data[$i]->dob ?? null, $data[$i]->age ?? '', $data[$i]->age_in_month ?? '', $data[$i]->estimate_dob ?? '', $data[$i]->Last_Visit ?? null)) ?></td>
|
||||
<td><?=$data[$i]->Last_Visit ?></td>
|
||||
<td><?php echo ($data[$i]->insurance_id==0 ? 'Self': 'Insuranced'); ?></td>
|
||||
<td>
|
||||
<a href="javascript:load_form('<?= base_url('billing/patient/show_profile_opd') ?>/<?=$data[$i]->id ?>/1');" class="btn btn-info btn-xs">
|
||||
<span class="fa fa-history"></span> Patient History
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Sr.No.</th>
|
||||
<th>Patient/UHID Code</th>
|
||||
<th>Name {Relative Name}</th>
|
||||
<th>Age</th>
|
||||
<th>Age</th>
|
||||
<th>Last Visit</th>
|
||||
<th>Insurance</th>
|
||||
<th>Patient History</th>
|
||||
|
|
@ -49,4 +34,45 @@
|
|||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var searchQuery = '<?= esc($search_query ?? '', 'js') ?>';
|
||||
|
||||
if (window.jQuery && $.fn.DataTable) {
|
||||
$('#example1').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: '<?= base_url('billing/patient/search_ajax') ?>',
|
||||
type: 'GET',
|
||||
data: {
|
||||
search_query: searchQuery
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{ data: 0, orderable: false },
|
||||
{ data: 1, orderable: true },
|
||||
{ data: 2, orderable: true },
|
||||
{ data: 3, orderable: true },
|
||||
{ data: 4, orderable: true },
|
||||
{ data: 5, orderable: true },
|
||||
{ data: 6, orderable: false }
|
||||
],
|
||||
order: [[4, 'desc']], // Order by Last Visit (column index 4) DESC
|
||||
pageLength: 25,
|
||||
lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
|
||||
language: {
|
||||
processing: '<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i><span class="sr-only">Loading...</span>',
|
||||
emptyTable: searchQuery ? 'No patients found matching your search' : 'No patient records available',
|
||||
info: 'Showing _START_ to _END_ of _TOTAL_ patients',
|
||||
infoEmpty: 'Showing 0 to 0 of 0 patients',
|
||||
infoFiltered: '(filtered from _MAX_ total patients)',
|
||||
lengthMenu: 'Show _MENU_ patients per page',
|
||||
zeroRecords: 'No matching patients found'
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user