Improve doctor document workflow and print performance

This commit is contained in:
DevSoftTech Server 2026-05-06 02:06:18 +05:30
parent b20d5b3ae8
commit 3c4dff6285
9 changed files with 456 additions and 47 deletions

View File

@ -605,6 +605,7 @@ $routes->post('Opd_prescription/section_template_track_usage', 'Opd_prescription
$routes->get('Opd_prescription/section_template_list', 'Opd_prescription::section_template_list');
$routes->get('Opd_prescription/section_past_data', 'Opd_prescription::section_past_data');
$routes->get('Opd_prescription/template_workspace', 'Opd_prescription::template_workspace', ['filter' => 'permission:doctor_work.template_workspace.access,doctor_work.access']);
$routes->get('doctor_work/patient_search', 'DoctorDocument::patient_search', ['filter' => 'permission:doctor_work.template_workspace.access,doctor_work.access']);
$routes->get('doctor_work/document_workspace', 'DoctorDocument::workspace', ['filter' => 'permission:doctor_work.template_workspace.access,doctor_work.access']);
// Legacy-compatible doctor document templates and issue flow.

View File

@ -148,8 +148,15 @@ class DoctorDocument extends BaseController
$logoFile = defined('H_logo') ? (string) constant('H_logo') : 'logo.png';
$logoAbsPath = FCPATH . 'assets/images/' . $logoFile;
$logoSrc = $this->buildImageDataUriFromPath($logoAbsPath);
if ($logoSrc === '' && is_file($logoAbsPath)) {
$logoSrc = str_replace('\\', '/', $logoAbsPath);
}
if ($logoSrc === '') {
$logoSrc = base_url('assets/images/' . $logoFile);
}
$logoHtml = '<img style="width:100px;vertical-align:top;" src="'
. base_url('assets/images/' . $logoFile) . '" />';
. $logoSrc . '" />';
$age = (string) ($doc['age'] ?? '');
$gender = (string) ($doc['gender'] ?? '');
@ -158,7 +165,16 @@ class DoctorDocument extends BaseController
$drSignImg = '';
$drSignFile = (string) ($doc['dr_sign'] ?? $doc['sign_image'] ?? $doc['doctor_sign'] ?? '');
if ($drSignFile !== '') {
$drSignImg = '<img style="height:40px;" src="' . base_url('assets/images/' . $drSignFile) . '" />';
$drSignAbsPath = FCPATH . 'assets/images/' . ltrim($drSignFile, '/\\');
$drSignSrc = $this->buildImageDataUriFromPath($drSignAbsPath);
if ($drSignSrc === '' && is_file($drSignAbsPath)) {
$drSignSrc = str_replace('\\', '/', $drSignAbsPath);
}
if ($drSignSrc === '') {
$drSignSrc = base_url('assets/images/' . $drSignFile);
}
$drSignImg = '<img style="height:40px;" src="' . $drSignSrc . '" />';
}
$map = [
@ -187,6 +203,29 @@ class DoctorDocument extends BaseController
return str_replace(array_keys($map), array_values($map), $html);
}
private function buildImageDataUriFromPath(string $absolutePath): string
{
$absolutePath = trim($absolutePath);
if ($absolutePath === '' || ! is_file($absolutePath) || ! is_readable($absolutePath)) {
return '';
}
$bytes = @file_get_contents($absolutePath);
if ($bytes === false || $bytes === '') {
return '';
}
$mime = 'image/png';
if (function_exists('mime_content_type')) {
$detected = @mime_content_type($absolutePath);
if (is_string($detected) && str_starts_with($detected, 'image/')) {
$mime = $detected;
}
}
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}
private function hasColumn(string $table, string $column): bool
{
if (! $this->db->tableExists($table)) {
@ -226,6 +265,128 @@ class DoctorDocument extends BaseController
return view('doctor_document/workspace');
}
public function patient_search()
{
if ($resp = $this->ensureAccess()) {
return $resp;
}
$search = trim((string) $this->request->getGet('q'));
$patients = $this->searchDoctorWorkPatients($search);
$countsByPatient = $this->getDoctorWorkPatientCounts(array_map(static fn(array $row): int => (int) ($row['id'] ?? 0), $patients));
foreach ($patients as &$patientRow) {
$patientId = (int) ($patientRow['id'] ?? 0);
$patientCounts = $countsByPatient[$patientId] ?? [];
$patientRow['opd_count'] = (int) ($patientCounts['opd_count'] ?? 0);
$patientRow['lab_count'] = (int) ($patientCounts['lab_count'] ?? 0);
$patientRow['ipd_count'] = (int) ($patientCounts['ipd_count'] ?? 0);
}
unset($patientRow);
return view('doctor_document/patient_search', [
'search' => $search,
'patients' => $patients,
]);
}
/**
* @return list<array<string, mixed>>
*/
private function searchDoctorWorkPatients(string $search): array
{
$search = preg_replace('/[^A-Za-z0-9 _.@\-]/', '', trim($search)) ?? '';
if ($search === '') {
return [];
}
$searchTokens = preg_split('/\s+/', $search) ?: [];
$whereParts = ['1=1'];
foreach ($searchTokens as $token) {
$token = trim((string) $token);
if ($token === '') {
continue;
}
$escaped = $this->db->escape($token);
$likeToken = $this->db->escapeLikeString($token);
if (is_numeric($token)) {
$whereParts[] = "(p.p_code LIKE '%{$likeToken}' ESCAPE '!' OR p.mphone1 = {$escaped} OR p.udai = {$escaped})";
continue;
}
if (ctype_alpha($token)) {
$whereParts[] = "(p.p_fname LIKE '%{$likeToken}%' ESCAPE '!' OR p.email1 = {$escaped})";
continue;
}
$whereParts[] = "(p.p_code LIKE '{$likeToken}' ESCAPE '!' OR p.email1 = {$escaped})";
}
$sql = "SELECT p.*, DATE_FORMAT(p.last_visit,'%d-%m-%Y') AS Last_Visit
FROM patient_master p
WHERE " . implode(' AND ', $whereParts) . "
GROUP BY p.id
ORDER BY p.last_visit DESC
LIMIT 100";
return $this->db->query($sql)->getResultArray();
}
/**
* @param list<int> $patientIds
* @return array<int, array<string, int>>
*/
private function getDoctorWorkPatientCounts(array $patientIds): array
{
$patientIds = array_values(array_unique(array_filter(array_map('intval', $patientIds), static fn(int $id): bool => $id > 0)));
if ($patientIds === []) {
return [];
}
$countsByPatient = [];
foreach ($patientIds as $patientId) {
$countsByPatient[$patientId] = [
'opd_count' => 0,
'lab_count' => 0,
'ipd_count' => 0,
];
}
$countSources = [
['table' => 'opd_master', 'column' => 'p_id', 'alias' => 'opd_count'],
['table' => 'lab_request', 'column' => 'patient_id', 'alias' => 'lab_count'],
['table' => 'ipd_master', 'column' => 'p_id', 'alias' => 'ipd_count'],
];
foreach ($countSources as $source) {
if (! $this->hasColumn($source['table'], $source['column'])) {
continue;
}
$rows = $this->db->table($source['table'])
->select($source['column'] . ' AS patient_id, COUNT(*) AS total_count', false)
->whereIn($source['column'], $patientIds)
->groupBy($source['column'])
->get()
->getResultArray();
foreach ($rows as $row) {
$patientId = (int) ($row['patient_id'] ?? 0);
if ($patientId <= 0 || ! array_key_exists($patientId, $countsByPatient)) {
continue;
}
$countsByPatient[$patientId][$source['alias']] = (int) ($row['total_count'] ?? 0);
}
}
return $countsByPatient;
}
public function open_by_key()
{
if ($resp = $this->ensureAccess()) {
@ -1087,6 +1248,16 @@ class DoctorDocument extends BaseController
$patientDoc
);
$logoFile = defined('H_logo') ? (string) constant('H_logo') : 'logo.png';
$logoAbsPath = FCPATH . 'assets/images/' . $logoFile;
$hospitalLogoSrc = $this->buildImageDataUriFromPath($logoAbsPath);
if ($hospitalLogoSrc === '' && is_file($logoAbsPath)) {
$hospitalLogoSrc = str_replace('\\', '/', $logoAbsPath);
}
if ($hospitalLogoSrc === '') {
$hospitalLogoSrc = base_url('assets/images/' . $logoFile);
}
$data = [
'content' => $content,
'print_on_type' => $resolvedPrintType,
@ -1102,6 +1273,7 @@ class DoctorDocument extends BaseController
'custom_header_html' => $customHeaderHtml,
'custom_footer_html' => $customFooterHtml,
'has_selected_print_template' => is_array($selectedPrintTemplate),
'hospital_logo_src' => $hospitalLogoSrc,
];
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';

View File

@ -9,6 +9,10 @@ $footerMargin = number_format((float) ($print_footer_margin ?? 1.50), 2, '.', ''
$customHeaderHtml = (string) ($custom_header_html ?? '');
$customFooterHtml = (string) ($custom_footer_html ?? '');
$hasSelectedPrintTemplate = ! empty($has_selected_print_template);
$hospitalLogoSrc = trim((string) ($hospital_logo_src ?? ''));
if ($hospitalLogoSrc === '') {
$hospitalLogoSrc = base_url('assets/images/' . (defined('H_logo') ? constant('H_logo') : 'logo.png'));
}
?>
<style>
@page {
@ -33,7 +37,7 @@ body, p, td, th, div, span, li {
<table cellspacing="0" style="font-size:10px;width:100%;border-style:inset;">
<tr>
<td style="width:20%;vertical-align:top;">
<img style="width:100px;vertical-align:top;" src="<?= esc(base_url('assets/images/' . (defined('H_logo') ? constant('H_logo') : 'logo.png'))) ?>" />
<img style="width:100px;vertical-align:top;" src="<?= esc($hospitalLogoSrc) ?>" />
</td>
<td style="width:70%;vertical-align:top;">
<p align="center" style="font-size:26px;"><?= esc(defined('H_Name') ? constant('H_Name') : 'Hospital') ?></p>

View File

@ -2,14 +2,29 @@
$patientDoc = $patient_doc ?? [];
$patient = $person_info ?? [];
$patientDocId = (int) ($patient_doc_id ?? 0);
$request = service('request');
$backUrl = trim((string) $request->getGet('back_url'));
$backTitle = trim((string) $request->getGet('back_title'));
if ($backUrl === '') {
$backUrl = base_url('doctor_work/document_workspace');
}
if ($backTitle === '') {
$backTitle = 'Doctor Documents Workspace';
}
$docListQuery = http_build_query([
'back_url' => $backUrl,
'back_title' => $backTitle,
]);
$docListUrl = base_url('/Document_Patient/p_doc_record/' . (int) ($patient['id'] ?? 0));
$docListUrlWithQuery = $docListUrl . ($docListQuery !== '' ? '?' . $docListQuery : '');
?>
<div class="pagetitle">
<h1>Document Input Preview</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="<?= base_url('dashboard') ?>">Home</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('/Patient/person_record/' . (int) ($patient['id'] ?? 0)) ?>');">Person</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('/Document_Patient/p_doc_record/' . (int) ($patient['id'] ?? 0)) ?>');">Document List</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>');"><?= esc($backTitle) ?></a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($docListUrlWithQuery, 'js') ?>');">Document List</a></li>
<li class="breadcrumb-item active">Pre-Data</li>
</ol>
</nav>
@ -24,18 +39,31 @@ $patientDocId = (int) ($patient_doc_id ?? 0);
<div style="max-height:560px;overflow-y:auto;">
<?php foreach (($doc_format_sub ?? []) as $row): ?>
<div class="row g-2 align-items-start mb-3">
<?php
$inputType = strtolower((string) ($row['input_type'] ?? 'text'));
$rawValue = (string) ($row['p_doc_raw_value'] ?? '');
$inputValue = $rawValue;
if ($inputType === 'date') {
if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $rawValue, $matches) === 1) {
$inputValue = $matches[3] . '-' . $matches[2] . '-' . $matches[1];
} elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $rawValue, $matches) === 1) {
$inputValue = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
}
}
?>
<div class="col-md-3"><label class="form-label mb-0"><?= esc((string) ($row['input_name'] ?? '')) ?></label></div>
<div class="col-md-3"><div class="small text-muted"><?= esc((string) ($row['p_doc_raw_value'] ?? '')) ?></div></div>
<div class="col-md-3"><div class="small text-muted" id="saved_value_<?= (int) ($row['id'] ?? 0) ?>"><?= esc($rawValue) ?></div></div>
<div class="col-md-4">
<?php $inputType = strtolower((string) ($row['input_type'] ?? 'text')); ?>
<?php if ($inputType === 'textarea'): ?>
<textarea class="form-control" id="input_id_<?= (int) ($row['id'] ?? 0) ?>"><?= esc((string) ($row['p_doc_raw_value'] ?? '')) ?></textarea>
<textarea class="form-control" id="input_id_<?= (int) ($row['id'] ?? 0) ?>" data-input-type="<?= esc($inputType) ?>"><?= esc($rawValue) ?></textarea>
<?php elseif ($inputType === 'date'): ?>
<input class="form-control" id="input_id_<?= (int) ($row['id'] ?? 0) ?>" data-input-type="<?= esc($inputType) ?>" type="date" value="<?= esc($inputValue) ?>">
<?php else: ?>
<input class="form-control" id="input_id_<?= (int) ($row['id'] ?? 0) ?>" type="text" value="<?= esc((string) ($row['p_doc_raw_value'] ?? '')) ?>">
<input class="form-control" id="input_id_<?= (int) ($row['id'] ?? 0) ?>" data-input-type="<?= esc($inputType) ?>" type="text" value="<?= esc($rawValue) ?>">
<?php endif; ?>
</div>
<div class="col-md-2">
<button onclick="update_data_value(<?= (int) ($row['id'] ?? 0) ?>, document.getElementById('input_id_<?= (int) ($row['id'] ?? 0) ?>').value)" type="button" class="btn btn-outline-primary btn-sm">Save</button>
<button onclick="update_data_value(<?= (int) ($row['id'] ?? 0) ?>)" type="button" class="btn btn-outline-primary btn-sm">Save</button>
</div>
</div>
<?php endforeach; ?>
@ -43,14 +71,38 @@ $patientDocId = (int) ($patient_doc_id ?? 0);
<div class="d-flex flex-wrap gap-2 mt-3">
<button onclick="report_create()" type="button" class="btn btn-primary btn-sm">Report Compile</button>
<button onclick="load_form('<?= base_url('/Document_Patient/load_doc/' . $patientDocId) ?>')" type="button" class="btn btn-secondary btn-sm">Edit Document</button>
<button onclick="load_form('<?= base_url('/Document_Patient/load_doc/' . $patientDocId) ?>?<?= esc($docListQuery, 'js') ?>')" type="button" class="btn btn-secondary btn-sm">Edit Document</button>
</div>
</div>
</div>
</section>
<script>
function update_data_value(test_id, test_value) {
function formatInputValueForSave(inputElement) {
if (!inputElement) {
return '';
}
var rawValue = String(inputElement.value || '').trim();
if (rawValue === '') {
return '';
}
if ((inputElement.dataset.inputType || '').toLowerCase() !== 'date') {
return rawValue;
}
var match = rawValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return rawValue;
}
return match[3] + '/' + match[2] + '/' + match[1];
}
function update_data_value(test_id) {
var inputElement = document.getElementById('input_id_' + test_id);
var test_value = formatInputValueForSave(inputElement);
var csrfName = $('input[name="<?= csrf_token() ?>"]').attr('name');
var csrfHash = $('input[name="<?= csrf_token() ?>"]').val();
@ -59,7 +111,10 @@ function update_data_value(test_id, test_value) {
test_value: test_value,
[csrfName]: csrfHash
}, function() {
alert('Saved');
var savedCell = document.getElementById('saved_value_' + test_id);
if (savedCell) {
savedCell.textContent = test_value;
}
});
}
@ -78,7 +133,7 @@ function report_create() {
}, function(data) {
alert(data.error_text || 'Done');
if (parseInt(data.update || '0', 10) === 1) {
load_form('<?= base_url('/Document_Patient/load_doc') ?>/' + patient_doc_id);
load_form('<?= base_url('/Document_Patient/load_doc') ?>/' + patient_doc_id + '?<?= esc($docListQuery, 'js') ?>');
}
}, 'json');
}

View File

@ -5,14 +5,29 @@ $docId = (int) ($patientDoc['id'] ?? 0);
$html = (string) ($patientDoc['raw_data'] ?? '');
$printTemplates = $print_templates ?? [];
$defaultPrintTemplateId = (int) ($default_print_template_id ?? 0);
$request = service('request');
$backUrl = trim((string) $request->getGet('back_url'));
$backTitle = trim((string) $request->getGet('back_title'));
if ($backUrl === '') {
$backUrl = base_url('doctor_work/document_workspace');
}
if ($backTitle === '') {
$backTitle = 'Doctor Documents Workspace';
}
$docListUrl = base_url('/Document_Patient/p_doc_record/' . (int) ($patient['id'] ?? 0));
$docListQuery = http_build_query([
'back_url' => $backUrl,
'back_title' => $backTitle,
]);
$docListUrlWithQuery = $docListUrl . ($docListQuery !== '' ? '?' . $docListQuery : '');
?>
<div class="pagetitle">
<h1>Document Editor</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="<?= base_url('dashboard') ?>">Home</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('/Patient/person_record/' . (int) ($patient['id'] ?? 0)) ?>');">Person</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('/Document_Patient/p_doc_record/' . (int) ($patient['id'] ?? 0)) ?>');">Document List</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>');"><?= esc($backTitle) ?></a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($docListUrlWithQuery, 'js') ?>');">Document List</a></li>
<li class="breadcrumb-item active">Editor</li>
</ol>
</nav>
@ -121,7 +136,7 @@ $('#updatereport').off('click').on('click', function() {
$('#editreport').off('click').on('click', function() {
var document_id = $('#document_id').val();
load_form('<?= base_url('/Document_Patient/load_doc') ?>/' + document_id);
load_form('<?= base_url('/Document_Patient/load_doc') ?>/' + document_id + '?<?= esc($docListQuery, 'js') ?>');
});
$(document).off('click', '.btn-print-tpl, .btn-print-tpl-item').on('click', '.btn-print-tpl, .btn-print-tpl-item', function(e) {
@ -137,33 +152,9 @@ $(document).off('click', '.btn-print-tpl, .btn-print-tpl-item').on('click', '.bt
var url = '<?= base_url('/Document_Patient/create_final') ?>/' + document_id + '?ptid=' + encodeURIComponent(ptid);
fetch(url, { credentials: 'same-origin' })
.then(function(response) {
var ct = response.headers.get('Content-Type') || '';
if (!response.ok || ct.indexOf('application/pdf') === -1) {
return response.text().then(function(txt) {
throw new Error('PDF generation failed (HTTP ' + response.status + '). ' + txt.substring(0, 300));
});
}
return response.blob();
})
.then(function(blob) {
var blobUrl = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = blobUrl;
a.target = '_blank';
a.rel = 'noopener';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function() { URL.revokeObjectURL(blobUrl); }, 60000);
})
.catch(function(err) {
alert('Print error: ' + err.message);
})
.finally(function() {
$btn.prop('disabled', origDisabled).html(origHtml);
});
window.open(url, '_blank');
$btn.prop('disabled', origDisabled).html(origHtml);
});
$('#Re_Create').off('click').on('click', function() {

View File

@ -5,13 +5,26 @@ $patientName = (string) ($patient['p_fname'] ?? '');
$relativeName = (string) ($patient['p_rname'] ?? '');
$ageText = (string) ($age_text ?? '-');
$genderText = ((int) ($patient['gender'] ?? 0) === 1) ? 'Male' : (((int) ($patient['gender'] ?? 0) === 2) ? 'Female' : 'Other');
$request = service('request');
$backUrl = trim((string) $request->getGet('back_url'));
$backTitle = trim((string) $request->getGet('back_title'));
if ($backUrl === '') {
$backUrl = base_url('doctor_work/document_workspace');
}
if ($backTitle === '') {
$backTitle = 'Doctor Documents Workspace';
}
$backQuery = http_build_query([
'back_url' => $backUrl,
'back_title' => $backTitle,
]);
?>
<div class="pagetitle">
<h1>Patient Document Data</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="<?= base_url('dashboard') ?>">Home</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('/Patient/person_record/' . $patientId) ?>');">Person</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>');"><?= esc($backTitle) ?></a></li>
<li class="breadcrumb-item active">Documents</li>
</ol>
</nav>
@ -31,7 +44,11 @@ $genderText = ((int) ($patient['gender'] ?? 0) === 1) ? 'Male' : (((int) ($patie
<strong>/ P Code :</strong><?= esc((string) ($patient['p_code'] ?? '')) ?>
</p>
<input type="hidden" id="pid" value="<?= $patientId ?>">
<input type="hidden" id="doc_back_query" value="<?= esc($backQuery) ?>">
<div class="mt-2">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>')">
Back
</button>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="load_form('<?= base_url('Report/document_list') ?>', 'Document Issue Report')">
Open Document Issue Report
</button>
@ -91,7 +108,7 @@ $genderText = ((int) ($patient['gender'] ?? 0) === 1) ? 'Male' : (((int) ($patie
<td><?= $n++ ?></td>
<td><?= !empty($row['date_issue']) ? esc(date('d-m-Y', strtotime((string) $row['date_issue']))) : '' ?></td>
<td><?= esc((string) ($row['doc_name'] ?? '')) ?></td>
<td><button type="button" class="btn btn-primary btn-sm" onclick="load_form('<?= base_url('Document_Patient/load_doc/' . (int) ($row['id'] ?? 0)) ?>')">Edit</button></td>
<td><button type="button" class="btn btn-primary btn-sm" onclick="load_form('<?= base_url('Document_Patient/load_doc/' . (int) ($row['id'] ?? 0)) ?>?<?= esc($backQuery, 'js') ?>')">Edit</button></td>
</tr>
<?php endforeach; ?>
</table>
@ -122,8 +139,13 @@ $('#createdoc').off('click').on('click', function() {
[csrfName]: csrfHash
}, function(data) {
var id = parseInt((data || '').toString(), 10);
var backQuery = $('#doc_back_query').val() || '';
if (id > 0) {
load_form('<?= base_url('Document_Patient/Pre_Data') ?>/' + id);
var url = '<?= base_url('Document_Patient/Pre_Data') ?>/' + id;
if (backQuery) {
url += '?' + backQuery;
}
load_form(url);
} else {
alert('Unable to create document');
}

View File

@ -0,0 +1,157 @@
<?php
$search = trim((string) ($search ?? ''));
$patients = isset($patients) && is_array($patients) ? $patients : [];
$selfUrl = base_url('doctor_work/patient_search');
?>
<div class="pagetitle">
<h1>Doctor Patient Search</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="<?= base_url('dashboard') ?>">Home</a></li>
<li class="breadcrumb-item active">Doctor Patient Search</li>
</ol>
</nav>
</div>
<section class="section">
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Search Patient</h5>
<form onsubmit="return loadDoctorPatientSearch(this);" class="row g-2 align-items-end">
<div class="col-lg-8">
<label class="form-label" for="doctor_patient_search_q">Name / UHID / Mobile / Patient ID</label>
<input type="text" class="form-control" id="doctor_patient_search_q" name="q" value="<?= esc($search) ?>" placeholder="Enter patient name, UHID, mobile, or patient id">
</div>
<div class="col-lg-4 d-flex gap-2 flex-wrap">
<button type="submit" class="btn btn-primary">Search</button>
<button type="button" class="btn btn-outline-secondary" onclick="load_form('<?= esc($selfUrl, 'js') ?>','Doctor Patient Search');">Reset</button>
</div>
</form>
<div class="small text-muted mt-2">Each result includes consult history, patient document issue, and summary counts for OPD, lab tests, and IPD admissions.</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<h5 class="card-title mb-0">Patient Results</h5>
<span class="badge bg-secondary"><?= count($patients) ?> record(s)</span>
</div>
<?php if ($patients === []) : ?>
<div class="alert alert-info mb-0">No patient found for the current search.</div>
<?php else : ?>
<div class="table-responsive">
<table class="table table-sm table-striped align-middle mb-0" id="doctorPatientSearchTable">
<thead>
<tr>
<th>UHID</th>
<th>Patient</th>
<th>Age</th>
<th>Last Visit</th>
<th>History</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($patients as $patient) : ?>
<?php
$patientId = (int) ($patient['id'] ?? 0);
$historyBackUrl = $selfUrl;
if ($search !== '') {
$historyBackUrl .= '?' . http_build_query(['q' => $search]);
}
$historyUrl = base_url('billing/patient/show_profile_opd') . '/' . $patientId . '/1?' . http_build_query([
'back_url' => $historyBackUrl,
'back_title' => 'Doctor Patient Search',
]);
$documentUrl = base_url('Document_Patient/p_doc_record') . '/' . $patientId;
$documentUrl = base_url('Document_Patient/p_doc_record') . '/' . $patientId . '?' . http_build_query([
'back_url' => $historyBackUrl,
'back_title' => 'Doctor Patient Search',
]);
$patientRawName = trim((string) ($patient['p_fname'] ?? '') . ' { ' . (string) ($patient['p_rname'] ?? '') . ' }');
$patientAge = function_exists('get_age_1')
? trim((string) get_age_1($patient['dob'] ?? null, $patient['age'] ?? '', $patient['age_in_month'] ?? '', $patient['estimate_dob'] ?? '', $patient['last_visit'] ?? null))
: trim((string) ($patient['age'] ?? ''));
?>
<tr>
<td><?= esc((string) ($patient['p_code'] ?? '')) ?></td>
<td><?= esc($patientRawName) ?></td>
<td><?= esc($patientAge) ?></td>
<td><?= esc((string) ($patient['Last_Visit'] ?? '')) ?></td>
<td>
<div class="d-flex gap-2 flex-wrap">
<span class="badge bg-primary-subtle text-primary border">OPD: <?= (int) ($patient['opd_count'] ?? 0) ?></span>
<span class="badge bg-success-subtle text-success border">Lab: <?= (int) ($patient['lab_count'] ?? 0) ?></span>
<span class="badge bg-warning-subtle text-dark border">IPD: <?= (int) ($patient['ipd_count'] ?? 0) ?></span>
</div>
</td>
<td>
<div class="d-flex gap-2 flex-wrap">
<a href="javascript:load_form('<?= esc($historyUrl, 'js') ?>','Consult History');" class="btn btn-info btn-sm">
<i class="bi bi-clock-history"></i> History
</a>
<a href="javascript:load_form('<?= esc($documentUrl, 'js') ?>','Patient Documents');" class="btn btn-primary btn-sm">
<i class="bi bi-file-earmark-text"></i> Issue Document
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</section>
<script>
function loadDoctorPatientSearch(form) {
var query = '';
if (form && form.q) {
query = String(form.q.value || '').trim();
}
var url = '<?= esc($selfUrl, 'js') ?>';
if (query !== '') {
url += '?q=' + encodeURIComponent(query);
}
load_form(url, 'Doctor Patient Search');
return false;
}
(function() {
var table = document.getElementById('doctorPatientSearchTable');
if (!table || table.dataset.dtInit === '1') {
return;
}
if (window.simpleDatatables && window.simpleDatatables.DataTable) {
try {
new window.simpleDatatables.DataTable(table);
table.dataset.dtInit = '1';
return;
} catch (error) {
console.warn('simple-datatables init failed for doctor patient search table', error);
}
}
if (window.jQuery && $.fn && $.fn.DataTable && $.fn.dataTable && $.fn.dataTable.defaults) {
try {
if (!$.fn.DataTable.isDataTable('#doctorPatientSearchTable')) {
$('#doctorPatientSearchTable').DataTable({
order: [[3, 'desc']],
pageLength: 25
});
}
table.dataset.dtInit = '1';
} catch (error) {
console.warn('jQuery DataTables init failed for doctor patient search table', error);
}
}
})();
</script>

View File

@ -31,6 +31,7 @@
</div>
<div class="d-flex gap-2 flex-wrap">
<button type="button" class="btn btn-success" onclick="openPatientDoc()">Open Patient Documents</button>
<button type="button" class="btn btn-outline-secondary" onclick="load_form('<?= base_url('doctor_work/patient_search') ?>','Doctor Patient Search')">Search Patient with History</button>
<button type="button" class="btn btn-outline-primary" onclick="load_form('<?= base_url('Report/document_list') ?>', 'Document Issue Report')">Document Issue Report</button>
</div>
</div>

View File

@ -372,6 +372,12 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
<span>Clinical Templates</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('doctor_work/patient_search') ?>','Doctor Patient Search')">
<i class="bi bi-search"></i>
<span>Patient Search</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('doctor_work/document_workspace') ?>','Doctor Documents Workspace')">
<i class="bi bi-file-earmark-text"></i>