feat(abdm): add professional PDF export and print button for fetched health records
This commit is contained in:
parent
92acaee56f
commit
6c8e062558
|
|
@ -569,6 +569,7 @@ $routes->group('billing', function($routes) {
|
|||
$routes->get('patient/show_profile_opd/(:num)', 'Patient::show_profile_opd/$1');
|
||||
$routes->get('patient/show_profile_opd/(:num)/(:num)', 'Patient::show_profile_opd/$1/$2');
|
||||
$routes->get('patient/abdm_documents/(:num)', 'Patient::abdm_documents/$1');
|
||||
$routes->get('patient/abdm_records_pdf/(:num)', 'Patient::abdm_records_pdf/$1');
|
||||
$routes->get('patient/abdm_document_detail/(:num)/(:num)', 'Patient::abdm_document_detail/$1/$2');
|
||||
$routes->get('patient/abdm_content_request/(:num)', 'Patient::abdm_content_request/$1');
|
||||
$routes->get('patient/abdm_content_auto_flow/(:num)', 'Patient::abdm_content_auto_flow/$1');
|
||||
|
|
|
|||
|
|
@ -2128,6 +2128,173 @@ class Patient extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function abdm_records_pdf(int $pno)
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
return $this->response->setStatusCode(500)->setBody('patient_master table not found');
|
||||
}
|
||||
|
||||
$patientRow = $this->db->table('patient_master')->where('id', $pno)->get()->getRowArray();
|
||||
if (! is_array($patientRow)) {
|
||||
return $this->response->setStatusCode(404)->setBody('Patient not found');
|
||||
}
|
||||
|
||||
$abhaContext = $this->buildAbhaProfileContext($patientRow);
|
||||
$patientAbha = trim((string) ($abhaContext['abha_address'] ?? ''));
|
||||
|
||||
if (! $this->db->tableExists('abdm_hiu_documents')) {
|
||||
return $this->response->setStatusCode(404)->setBody('No ABDM documents table found');
|
||||
}
|
||||
|
||||
$docId = (int) ($this->request->getGet('doc_id') ?? 0);
|
||||
$consentRequestId = trim((string) ($this->request->getGet('consent_request_id') ?? ''));
|
||||
|
||||
$builder = $this->db->table('abdm_hiu_documents d')
|
||||
->select('*')
|
||||
->orderBy('d.document_date', 'DESC')
|
||||
->orderBy('d.id', 'DESC');
|
||||
|
||||
$builder->groupStart()
|
||||
->where('d.patient_id', $pno);
|
||||
if ($patientAbha !== '') {
|
||||
$builder->orWhere('d.abha_address', $patientAbha);
|
||||
}
|
||||
$builder->groupEnd();
|
||||
|
||||
if ($docId > 0) {
|
||||
$builder->where('d.id', $docId);
|
||||
} elseif ($consentRequestId !== '') {
|
||||
$docFields = $this->db->getFieldNames('abdm_hiu_documents') ?? [];
|
||||
$sessionIds = $this->resolveAllRelatedConsentIds($consentRequestId);
|
||||
if ($sessionIds !== []) {
|
||||
$builder->groupStart();
|
||||
$hasClause = false;
|
||||
if (in_array('consent_request_id', $docFields, true)) {
|
||||
$builder->whereIn('d.consent_request_id', $sessionIds);
|
||||
$hasClause = true;
|
||||
}
|
||||
if (in_array('consent_artifact_id', $docFields, true)) {
|
||||
if ($hasClause) {
|
||||
$builder->orWhereIn('d.consent_artifact_id', $sessionIds);
|
||||
} else {
|
||||
$builder->whereIn('d.consent_artifact_id', $sessionIds);
|
||||
$hasClause = true;
|
||||
}
|
||||
}
|
||||
if (in_array('consent_ref', $docFields, true)) {
|
||||
if ($hasClause) {
|
||||
$builder->orWhereIn('d.consent_ref', $sessionIds);
|
||||
} else {
|
||||
$builder->whereIn('d.consent_ref', $sessionIds);
|
||||
$hasClause = true;
|
||||
}
|
||||
}
|
||||
$builder->groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $builder->get()->getResultArray();
|
||||
|
||||
foreach ($rows as &$r) {
|
||||
$summary = json_decode((string) ($r['summary_json'] ?? ''), true);
|
||||
$r['summary'] = is_array($summary) ? $summary : [];
|
||||
}
|
||||
unset($r);
|
||||
|
||||
$hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital';
|
||||
$hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : '';
|
||||
$hospitalAddress2 = defined('H_address_2') ? (string) constant('H_address_2') : '';
|
||||
$hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : '';
|
||||
$hospitalEmail = defined('H_Email') ? (string) constant('H_Email') : '';
|
||||
$hospitalLogoName = defined('H_logo') ? trim((string) constant('H_logo')) : '';
|
||||
|
||||
$logoPathCandidates = [];
|
||||
if ($hospitalLogoName !== '') {
|
||||
$logoPathCandidates[] = FCPATH . 'assets/images/' . $hospitalLogoName;
|
||||
$logoPathCandidates[] = FCPATH . 'assets/img/' . $hospitalLogoName;
|
||||
}
|
||||
$logoPathCandidates[] = FCPATH . 'assets/img/logo.png';
|
||||
$logoPathCandidates[] = FCPATH . 'assets/images/logo.png';
|
||||
|
||||
$logoSrc = '';
|
||||
foreach ($logoPathCandidates as $candidate) {
|
||||
if (is_file($candidate)) {
|
||||
$logoSrc = str_replace('\\', '/', $candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$patientName = trim((string) (($patientRow['title'] ?? '') . ' ' . ($patientRow['p_fname'] ?? '') . ' ' . ($patientRow['p_lname'] ?? '')));
|
||||
if ($patientName === '' || $patientName === '0') {
|
||||
$patientName = trim((string) ($patientRow['p_fname'] ?? ''));
|
||||
}
|
||||
$genderVal = (int) ($patientRow['gender'] ?? 0);
|
||||
$genderStr = $genderVal === 1 ? 'Male' : ($genderVal === 2 ? 'Female' : ($genderVal === 3 ? 'Other' : '-'));
|
||||
$ageStr = ! empty($patientRow['age']) ? ($patientRow['age'] . ' Year') : '';
|
||||
$ageGender = trim($ageStr . ($ageStr !== '' && $genderStr !== '-' ? ' / ' : '') . ($genderStr !== '-' ? $genderStr : ''));
|
||||
|
||||
$data = [
|
||||
'title' => 'ABDM Health Records - ' . $patientName,
|
||||
'patient' => $patientRow,
|
||||
'patientId' => (int) ($patientRow['id'] ?? $pno),
|
||||
'patientCode' => (string) ($patientRow['p_code'] ?? ('#' . $pno)),
|
||||
'patientName' => $patientName,
|
||||
'ageGender' => $ageGender,
|
||||
'mobile' => (string) ($patientRow['mphone1'] ?? ''),
|
||||
'abhaAddress' => $patientAbha,
|
||||
'abhaNumber' => (string) ($patientRow['abha_id'] ?? ''),
|
||||
'consentRequestId' => $consentRequestId,
|
||||
'documents' => $rows,
|
||||
'hospitalName' => $hospitalName,
|
||||
'hospitalAddress' => trim($hospitalAddress1 . ($hospitalAddress2 !== '' ? ', ' . $hospitalAddress2 : '')),
|
||||
'hospitalPhone' => $hospitalPhone,
|
||||
'hospitalEmail' => $hospitalEmail,
|
||||
'logoSrc' => $logoSrc,
|
||||
'printedOn' => date('d-m-Y h:i A'),
|
||||
];
|
||||
|
||||
$html = view('billing/abdm_records_pdf_v', $data);
|
||||
|
||||
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
|
||||
if (! is_dir($mpdfTempDir)) {
|
||||
mkdir($mpdfTempDir, 0755, true);
|
||||
}
|
||||
|
||||
$mpdf = new \Mpdf\Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'orientation' => 'P',
|
||||
'margin_left' => 10,
|
||||
'margin_right' => 10,
|
||||
'margin_top' => 10,
|
||||
'margin_bottom' => 12,
|
||||
'default_font' => 'freeserif',
|
||||
'tempDir' => $mpdfTempDir,
|
||||
]);
|
||||
$mpdf->autoScriptToLang = true;
|
||||
$mpdf->autoLangToFont = true;
|
||||
|
||||
$footerHtml = '<table style="width: 100%; border-top: 1px solid #cbd5e1; font-size: 7.5pt; color: #64748b; padding-top: 3px;">'
|
||||
. '<tr>'
|
||||
. '<td style="text-align: left; width: 75%;">Ayushman Bharat Digital Mission (ABDM) HIU • Confidential • For Clinical Evaluation Only</td>'
|
||||
. '<td style="text-align: right; width: 25%;">Page {PAGENO} of {nbpg}</td>'
|
||||
. '</tr>'
|
||||
. '</table>';
|
||||
$mpdf->SetHTMLFooter($footerHtml, 'O');
|
||||
$mpdf->SetHTMLFooter($footerHtml, 'E');
|
||||
|
||||
$mpdf->SetTitle('ABDM Health Records - ' . $patientName);
|
||||
$mpdf->WriteHTML($html);
|
||||
|
||||
$safeName = preg_replace('/[^A-Za-z0-9_-]/', '_', $patientName);
|
||||
$fileName = 'ABDM_Records_' . $safeName . '_' . date('Ymd_His') . '.pdf';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
||||
->setBody($mpdf->Output($fileName, 'S'));
|
||||
}
|
||||
|
||||
public function abdm_content_request(int $pno)
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ class M3HiuGatewayClient
|
|||
if (isset($payload['consentId']) && is_string($payload['consentId']) && str_contains($payload['consentId'], ':')) {
|
||||
$payload['consentId'] = trim((string) explode(':', $payload['consentId'])[0]);
|
||||
}
|
||||
if (empty($payload['consent_request_id']) && !empty($payload['abdm_consent_request_id'])) {
|
||||
$payload['consent_request_id'] = trim((string) $payload['abdm_consent_request_id']);
|
||||
}
|
||||
return $this->callGet('/v1/hiu/consent/status', $payload, 'consent.status.reconcile');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ class M3HiuWorkflowService
|
|||
$this->rememberArtifact($resolvedConsentId, $singleArtifactId, $payload['abha_address'] ?? '', $payload['hfr_id'] ?? '');
|
||||
}
|
||||
|
||||
foreach ($this->getKnownArtifactIds($resolvedConsentId) as $artifactId) {
|
||||
foreach ($this->getKnownArtifactIds($resolvedConsentId, true) as $artifactId) {
|
||||
if ($artifactId === '' || $artifactId === $resolvedConsentId) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -534,16 +534,20 @@ class M3HiuWorkflowService
|
|||
->update($update);
|
||||
}
|
||||
|
||||
private function getKnownArtifactIds(string $consentRequestId): array
|
||||
private function getKnownArtifactIds(string $consentRequestId, bool $pendingOnly = false): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_consent_artifacts') || $consentRequestId === '') {
|
||||
return [];
|
||||
}
|
||||
$rows = $this->db->table('abdm_hiu_consent_artifacts')
|
||||
$builder = $this->db->table('abdm_hiu_consent_artifacts')
|
||||
->select('artifact_id')
|
||||
->where('consent_request_id', $consentRequestId)
|
||||
->get()
|
||||
->getResultArray();
|
||||
->where('consent_request_id', $consentRequestId);
|
||||
|
||||
if ($pendingOnly) {
|
||||
$builder->whereNotIn('last_status', ['RECEIVED', 'DATA_RECEIVED', 'COMPLETED', 'EXPIRED', 'REVOKED', 'DENIED']);
|
||||
}
|
||||
|
||||
$rows = $builder->get()->getResultArray();
|
||||
|
||||
return array_values(array_unique(array_map(
|
||||
static fn (array $row): string => trim((string) ($row['artifact_id'] ?? '')),
|
||||
|
|
|
|||
|
|
@ -376,7 +376,10 @@ if ($patientPhotoPath === '') {
|
|||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="fw-semibold">Consent Request History</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="<?= base_url('billing/patient/abdm_records_pdf/' . (int) ($patient->id ?? 0)) ?>" target="_blank" class="btn btn-outline-primary btn-sm py-0 px-2 me-3" title="Print all fetched health records for this patient as PDF">
|
||||
<i class="fa fa-print me-1"></i> Print All Records
|
||||
</a>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 me-3" id="btnCheckLiveAbdmStatus">
|
||||
<span class="spinner-border spinner-border-sm d-none" id="abdmLiveStatusSpinner"></span>
|
||||
Check Live Status
|
||||
|
|
@ -606,9 +609,14 @@ if ($patientPhotoPath === '') {
|
|||
<div class="modal fade" id="abdmFetchResultModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Fetched Health Records</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="modal-title"><i class="fa fa-folder-open text-primary me-2"></i>Fetched Health Records</h5>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-sm btn-primary" id="abdmPrintFetchedRecordsBtn" style="display:none;" title="Print or Download PDF of all fetched records">
|
||||
<i class="fa fa-print me-1"></i> Print / PDF
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" id="abdmFetchResultModalBody">
|
||||
<div class="text-muted small text-center">Loading...</div>
|
||||
|
|
@ -1129,6 +1137,7 @@ if ($patientPhotoPath === '') {
|
|||
$(function() {
|
||||
var patientId = <?= (int) ($patient->id ?? 0) ?>;
|
||||
var abdmDocumentsUrl = '<?= base_url('billing/patient/abdm_documents/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmPrintPdfUrl = '<?= base_url('billing/patient/abdm_records_pdf/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmAutoFlowUrl = '<?= base_url('billing/patient/abdm_content_auto_flow/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmFetchOnlyUrl = '<?= base_url('billing/patient/abdm_content_fetch_only/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmConsentDetailUrl = '<?= base_url('billing/patient/abdm_consent_detail/' . (int) ($patient->id ?? 0)) ?>';
|
||||
|
|
@ -1138,6 +1147,7 @@ $(function() {
|
|||
var abdmDocDetailBaseUrl = '<?= base_url('billing/patient/abdm_document_detail/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var currentAbdmRows = [];
|
||||
var currentAbdmRequests = [];
|
||||
var currentAbdmFetchConsentRequestId = '';
|
||||
var autoFlowTimer = null;
|
||||
var autoFlowRequestId = '';
|
||||
var autoFlowAttempts = 0;
|
||||
|
|
@ -1371,8 +1381,9 @@ $(function() {
|
|||
}
|
||||
|
||||
function loadAbdmFetchResultModalData(consent) {
|
||||
var url = abdmDocumentsUrl + '?limit=200&include_summary=1';
|
||||
var reqId = (consent && consent.consent_request_id) ? consent.consent_request_id : '';
|
||||
currentAbdmFetchConsentRequestId = reqId;
|
||||
var url = abdmDocumentsUrl + '?limit=200&include_summary=1';
|
||||
if (reqId !== '') {
|
||||
url += '&consent_request_id=' + encodeURIComponent(reqId);
|
||||
}
|
||||
|
|
@ -1386,12 +1397,14 @@ $(function() {
|
|||
renderAbdmFetchResultModal(Array.isArray(data.items) ? data.items : []);
|
||||
})
|
||||
.catch(function(err) {
|
||||
$('#abdmPrintFetchedRecordsBtn').hide();
|
||||
$('#abdmFetchResultModalBody').html('<div class="text-danger small text-center">' + escHtml('Failed to load records: ' + (err.message || err)) + '</div>');
|
||||
});
|
||||
}
|
||||
|
||||
function renderAbdmFetchResultModal(docs) {
|
||||
if (!docs || !docs.length) {
|
||||
$('#abdmPrintFetchedRecordsBtn').hide();
|
||||
var fetchBtnHtml = currentAbdmFetchModalIdx >= 0
|
||||
? '<div class="text-center mt-2"><button type="button" class="btn btn-sm btn-primary abdm-fetch-again-btn" data-idx="' + currentAbdmFetchModalIdx + '">Fetch Data</button></div>'
|
||||
: '';
|
||||
|
|
@ -1399,6 +1412,8 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
$('#abdmPrintFetchedRecordsBtn').show();
|
||||
|
||||
var html = '';
|
||||
var docIds = [];
|
||||
docs.forEach(function(doc, di) {
|
||||
|
|
@ -1409,13 +1424,24 @@ $(function() {
|
|||
docIds.push(docId);
|
||||
}
|
||||
|
||||
html += '<div class="card mb-3">'
|
||||
var singlePrintUrl = abdmPrintPdfUrl + '?doc_id=' + encodeURIComponent(docId);
|
||||
|
||||
html += '<div class="card mb-3 shadow-sm border">'
|
||||
+ '<div class="card-body">'
|
||||
+ '<div class="fw-semibold">' + escHtml(doc.document_title || 'ABDM Document') + '</div>'
|
||||
+ '<div class="small text-muted mb-2">'
|
||||
+ escHtml(doc.organization_name || '-') + ' | '
|
||||
+ escHtml(doc.practitioner_name || '-') + ' | '
|
||||
+ 'Date: ' + escHtml(fmtDateTime(doc.document_date || doc.created_at))
|
||||
+ '<div class="d-flex justify-content-between align-items-start mb-2">'
|
||||
+ ' <div>'
|
||||
+ ' <div class="fw-bold text-primary fs-6">' + escHtml(doc.document_title || 'ABDM Document') + '</div>'
|
||||
+ ' <div class="small text-muted">'
|
||||
+ escHtml(doc.organization_name || '-') + ' | '
|
||||
+ escHtml(doc.practitioner_name || '-') + ' | '
|
||||
+ 'Date: ' + escHtml(fmtDateTime(doc.document_date || doc.created_at))
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ ' <div>'
|
||||
+ ' <a href="' + escHtml(singlePrintUrl) + '" target="_blank" class="btn btn-outline-primary btn-sm py-1 px-2" title="Print this health record as PDF">'
|
||||
+ ' <i class="fa fa-print me-1"></i> Print'
|
||||
+ ' </a>'
|
||||
+ ' </div>'
|
||||
+ '</div>';
|
||||
|
||||
if (sections.length) {
|
||||
|
|
@ -1878,6 +1904,14 @@ $(function() {
|
|||
runFetchRecordsForRow(idx);
|
||||
});
|
||||
|
||||
$(document).off('click.abdmOpd', '#abdmPrintFetchedRecordsBtn').on('click.abdmOpd', '#abdmPrintFetchedRecordsBtn', function() {
|
||||
var printUrl = abdmPrintPdfUrl;
|
||||
if (currentAbdmFetchConsentRequestId) {
|
||||
printUrl += '?consent_request_id=' + encodeURIComponent(currentAbdmFetchConsentRequestId);
|
||||
}
|
||||
window.open(printUrl, '_blank');
|
||||
});
|
||||
|
||||
$(document).off('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]').on('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]', function() {
|
||||
if (!currentAbdmRows.length) {
|
||||
loadAbdmDocs();
|
||||
|
|
|
|||
569
app/Views/billing/abdm_records_pdf_v.php
Normal file
569
app/Views/billing/abdm_records_pdf_v.php
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?= esc($title ?? 'ABDM Fetched Health Records') ?></title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'freeserif', 'DejaVu Sans', sans-serif;
|
||||
font-size: 9.5pt;
|
||||
color: #1e293b;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Header Layout */
|
||||
table.header-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
table.header-table td {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
}
|
||||
.hospital-logo {
|
||||
max-height: 48px;
|
||||
max-width: 170px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.hospital-name {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #0f2d59;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.hospital-meta {
|
||||
font-size: 8pt;
|
||||
color: #475569;
|
||||
line-height: 1.35;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.abdm-badge-box {
|
||||
background-color: #f0f7ff;
|
||||
border: 1px solid #bae6fd;
|
||||
padding: 6px 10px;
|
||||
text-align: right;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.abdm-badge-title {
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
color: #0284c7;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.abdm-badge-sub {
|
||||
font-size: 7.5pt;
|
||||
color: #0369a1;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.abdm-report-type {
|
||||
font-size: 10.5pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.report-meta-text {
|
||||
font-size: 8pt;
|
||||
color: #64748b;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.accent-divider {
|
||||
height: 3px;
|
||||
background-color: #0284c7;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
|
||||
/* Patient Info Card */
|
||||
table.patient-card {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
table.patient-card td {
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
font-size: 8.5pt;
|
||||
vertical-align: top;
|
||||
}
|
||||
.p-lbl {
|
||||
font-size: 7pt;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.p-val {
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
}
|
||||
.p-val-lg {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #0f2d59;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.abha-val {
|
||||
color: #0284c7;
|
||||
font-weight: bold;
|
||||
}
|
||||
.badge-verified {
|
||||
display: inline-block;
|
||||
background-color: #dcfce7;
|
||||
color: #15803d;
|
||||
border: 1px solid #86efac;
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Document Card */
|
||||
.doc-card {
|
||||
margin-bottom: 14px;
|
||||
border: 1px solid #cbd5e1;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
table.doc-header {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #0f2d59;
|
||||
color: #ffffff;
|
||||
}
|
||||
table.doc-header td {
|
||||
padding: 6px 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.doc-title {
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.doc-badge {
|
||||
background-color: #0284c7;
|
||||
color: #ffffff;
|
||||
font-size: 7.5pt;
|
||||
font-weight: bold;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.doc-date {
|
||||
font-size: 8.5pt;
|
||||
color: #e0f2fe;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.doc-submeta {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #f1f5f9;
|
||||
border-bottom: 1px solid #cbd5e1;
|
||||
}
|
||||
table.doc-submeta td {
|
||||
padding: 4px 10px;
|
||||
font-size: 8.5pt;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.doc-body {
|
||||
padding: 8px 10px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Section Styling */
|
||||
.section-wrap {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.section-heading {
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
color: #0f2d59;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
background-color: #f0f7ff;
|
||||
border-left: 3px solid #0284c7;
|
||||
padding: 3px 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
ul.section-list {
|
||||
margin: 3px 0 6px 16px;
|
||||
padding: 0;
|
||||
}
|
||||
ul.section-list li {
|
||||
margin-bottom: 2px;
|
||||
font-size: 8.5pt;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
/* Tables (Vitals, Medications, Lab) */
|
||||
table.grid-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 4px 0 8px 0;
|
||||
border: 1px solid #cbd5e1;
|
||||
}
|
||||
table.grid-table th {
|
||||
background-color: #f8fafc;
|
||||
color: #334155;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
text-align: left;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #cbd5e1;
|
||||
}
|
||||
table.grid-table td {
|
||||
padding: 4px 8px;
|
||||
font-size: 8.5pt;
|
||||
border: 1px solid #e2e8f0;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.grid-table tr:nth-child(even) td {
|
||||
background-color: #fbfcfe;
|
||||
}
|
||||
|
||||
/* Diagnosis Highlight Box */
|
||||
.diagnosis-box {
|
||||
background-color: #fefce8;
|
||||
border: 1px solid #fde047;
|
||||
border-left: 3px solid #ca8a04;
|
||||
padding: 6px 8px;
|
||||
margin: 4px 0 8px 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.diagnosis-item {
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
color: #713f12;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Care Plan / Follow-up Box */
|
||||
.careplan-box {
|
||||
background-color: #f0fdf4;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-left: 3px solid #16a34a;
|
||||
padding: 6px 8px;
|
||||
margin: 4px 0 8px 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Attached files note */
|
||||
.attachments-box {
|
||||
background-color: #f8fafc;
|
||||
border: 1px dashed #94a3b8;
|
||||
padding: 5px 8px;
|
||||
margin-top: 6px;
|
||||
font-size: 8pt;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.no-records-box {
|
||||
border: 1px solid #cbd5e1;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
background-color: #f8fafc;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Hospital Header -->
|
||||
<table class="header-table">
|
||||
<tr>
|
||||
<td style="width: 58%;">
|
||||
<?php if (!empty($logoSrc)): ?>
|
||||
<img src="<?= $logoSrc ?>" class="hospital-logo" alt="Logo" /><br>
|
||||
<?php endif; ?>
|
||||
<div class="hospital-name"><?= esc($hospitalName) ?></div>
|
||||
<div class="hospital-meta">
|
||||
<?php if (!empty($hospitalAddress)): ?>
|
||||
<?= esc($hospitalAddress) ?><br>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$contactParts = [];
|
||||
if (!empty($hospitalPhone)) { $contactParts[] = 'Phone: ' . esc($hospitalPhone); }
|
||||
if (!empty($hospitalEmail)) { $contactParts[] = 'Email: ' . esc($hospitalEmail); }
|
||||
echo implode(' | ', $contactParts);
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 42%;">
|
||||
<div class="abdm-badge-box">
|
||||
<div class="abdm-badge-title">Ayushman Bharat Digital Mission (ABDM)</div>
|
||||
<div class="abdm-badge-sub">Health Information User (HIU) Service</div>
|
||||
<div class="abdm-report-type">Fetched Health Records</div>
|
||||
<div class="report-meta-text">
|
||||
Generated: <strong><?= esc($printedOn) ?></strong>
|
||||
<?php if (!empty($consentRequestId)): ?>
|
||||
<br>Consent Ref: <strong><?= esc($consentRequestId) ?></strong>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="accent-divider"></div>
|
||||
|
||||
<!-- Patient Demographic Banner -->
|
||||
<table class="patient-card">
|
||||
<tr>
|
||||
<td style="width: 32%;">
|
||||
<span class="p-lbl">Patient Name</span>
|
||||
<span class="p-val-lg"><?= esc($patientName ?: 'Unknown Patient') ?></span>
|
||||
</td>
|
||||
<td style="width: 18%;">
|
||||
<span class="p-lbl">UHID / Patient ID</span>
|
||||
<span class="p-val"><?= esc($patientCode ?: ('#' . $patientId)) ?></span>
|
||||
</td>
|
||||
<td style="width: 25%;">
|
||||
<span class="p-lbl">Age / Gender</span>
|
||||
<span class="p-val"><?= esc($ageGender ?: '-') ?></span>
|
||||
</td>
|
||||
<td style="width: 25%;">
|
||||
<span class="p-lbl">Contact / Mobile</span>
|
||||
<span class="p-val"><?= esc($mobile ?: '-') ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="background-color: #f0f7ff;">
|
||||
<span class="p-lbl">ABHA Address (PHR)</span>
|
||||
<span class="p-val abha-val"><?= esc($abhaAddress ?: 'Not Linked') ?></span>
|
||||
<?php if (!empty($abhaAddress)): ?>
|
||||
<span class="badge-verified">✓ VERIFIED</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td colspan="2" style="background-color: #f0f7ff;">
|
||||
<span class="p-lbl">ABHA Number</span>
|
||||
<span class="p-val"><?= esc($abhaNumber ?: '-') ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fetched Documents Loop -->
|
||||
<?php if (empty($documents)): ?>
|
||||
<div class="no-records-box">
|
||||
<h4 style="margin: 0 0 4px 0; color: #334155;">No Fetched Health Records Found</h4>
|
||||
<p style="margin: 0; font-size: 8.5pt;">There are no clinical records stored under this consent request for the patient.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($documents as $docIdx => $doc): ?>
|
||||
<?php
|
||||
$summary = $doc['summary'] ?? [];
|
||||
$sections = is_array($summary['sections'] ?? null) ? $summary['sections'] : [];
|
||||
$docTitle = trim((string) ($doc['document_title'] ?? 'Health Record'));
|
||||
$docType = trim((string) ($doc['bundle_type'] ?? 'RECORD'));
|
||||
$docDate = !empty($doc['document_date']) ? date('d-m-Y h:i A', strtotime($doc['document_date'])) : (!empty($doc['created_at']) ? date('d-m-Y h:i A', strtotime($doc['created_at'])) : '-');
|
||||
$docFacility = trim((string) ($doc['organization_name'] ?? ''));
|
||||
$docDoctor = trim((string) ($doc['practitioner_name'] ?? ''));
|
||||
$careContext = trim((string) ($doc['care_context_reference'] ?? ''));
|
||||
$attachments = is_array($summary['attachments'] ?? null) ? $summary['attachments'] : [];
|
||||
?>
|
||||
|
||||
<div class="doc-card">
|
||||
<!-- Document Header -->
|
||||
<table class="doc-header">
|
||||
<tr>
|
||||
<td style="width: 70%;">
|
||||
<span class="doc-badge"><?= esc($docType) ?></span>
|
||||
<span class="doc-title"><?= esc($docTitle) ?></span>
|
||||
</td>
|
||||
<td style="width: 30%;" class="doc-date">
|
||||
Encounter Date: <strong><?= esc($docDate) ?></strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Submeta info -->
|
||||
<table class="doc-submeta">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Facility:</strong> <?= esc($docFacility ?: 'Healthcare Facility') ?>
|
||||
•
|
||||
<strong>Doctor:</strong> <?= esc($docDoctor ?: 'Treating Physician') ?>
|
||||
<?php if ($careContext !== ''): ?>
|
||||
•
|
||||
<strong>Context Ref:</strong> <?= esc($careContext) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Document Body -->
|
||||
<div class="doc-body">
|
||||
<?php if (empty($sections)): ?>
|
||||
<div style="font-size: 8.5pt; color: #64748b; font-style: italic;">
|
||||
No structured clinical sections were returned in this bundle.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $section): ?>
|
||||
<?php
|
||||
$secTitle = trim((string) ($section['title'] ?? 'Clinical Section'));
|
||||
$items = is_array($section['items'] ?? null) ? $section['items'] : [];
|
||||
$narrative = trim((string) ($section['narrative'] ?? ''));
|
||||
$secLower = strtolower($secTitle);
|
||||
|
||||
if (empty($items) && $narrative === '') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="section-wrap">
|
||||
<div class="section-heading"><?= esc($secTitle) ?></div>
|
||||
|
||||
<?php if (str_contains($secLower, 'vital') || str_contains($secLower, 'examination')): ?>
|
||||
<!-- Vitals / Physical Examination Table -->
|
||||
<table class="grid-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 58%;">Parameter / Vital Sign</th>
|
||||
<th style="width: 42%;">Finding / Measurement</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php
|
||||
$itemStr = trim((string) $item);
|
||||
$param = $itemStr;
|
||||
$val = '';
|
||||
if (str_contains($itemStr, ':')) {
|
||||
$parts = explode(':', $itemStr, 2);
|
||||
$param = trim($parts[0]);
|
||||
$val = trim($parts[1] ?? '');
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= esc($param) ?></strong></td>
|
||||
<td><?= esc($val !== '' ? $val : '-') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php elseif (str_contains($secLower, 'medication') || str_contains($secLower, 'prescription')): ?>
|
||||
<!-- Medications / Prescription Table -->
|
||||
<table class="grid-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 6%; text-align: center;">#</th>
|
||||
<th style="width: 50%;">Medicine / Formulation</th>
|
||||
<th style="width: 44%;">Dosage / Instructions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items as $mIdx => $item): ?>
|
||||
<?php
|
||||
$itemStr = trim((string) $item);
|
||||
$medName = $itemStr;
|
||||
$dosage = '';
|
||||
if (str_contains($itemStr, '—')) {
|
||||
$parts = explode('—', $itemStr, 2);
|
||||
$medName = trim($parts[0]);
|
||||
$dosage = trim($parts[1] ?? '');
|
||||
} elseif (str_contains($itemStr, ' - ')) {
|
||||
$parts = explode(' - ', $itemStr, 2);
|
||||
$medName = trim($parts[0]);
|
||||
$dosage = trim($parts[1] ?? '');
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td style="text-align: center; color: #64748b;"><?= $mIdx + 1 ?></td>
|
||||
<td><strong style="color: #0f2d59;"><?= esc($medName) ?></strong></td>
|
||||
<td><?= esc($dosage !== '' ? $dosage : 'As directed') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php elseif (str_contains($secLower, 'diagnos') || str_contains($secLower, 'problem')): ?>
|
||||
<!-- Diagnoses Highlight Box -->
|
||||
<div class="diagnosis-box">
|
||||
<?php if (!empty($items)): ?>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div class="diagnosis-item">• <?= esc($item) ?></div>
|
||||
<?php endforeach; ?>
|
||||
<?php elseif ($narrative !== ''): ?>
|
||||
<div class="diagnosis-item"><?= nl2br(esc($narrative)) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php elseif (str_contains($secLower, 'care plan') || str_contains($secLower, 'follow up') || str_contains($secLower, 'advice')): ?>
|
||||
<!-- Care Plan / Follow-up Box -->
|
||||
<div class="careplan-box">
|
||||
<?php if (!empty($items)): ?>
|
||||
<ul class="section-list" style="margin-left: 12px;">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li><?= esc($item) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php elseif ($narrative !== ''): ?>
|
||||
<div style="font-size: 8.5pt; color: #14532d;"><?= nl2br(esc($narrative)) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<!-- Standard List or Narrative -->
|
||||
<?php if (!empty($items)): ?>
|
||||
<ul class="section-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li><?= esc($item) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php elseif ($narrative !== ''): ?>
|
||||
<div style="font-size: 8.5pt; color: #334155; margin-left: 4px;"><?= nl2br(esc($narrative)) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Attached files note -->
|
||||
<?php if (!empty($attachments)): ?>
|
||||
<div class="attachments-box">
|
||||
<strong>Attached Files / Scanned Documents:</strong>
|
||||
<?= count($attachments) ?> file(s) attached in digital record
|
||||
<?php
|
||||
$attTitles = [];
|
||||
foreach ($attachments as $att) {
|
||||
if (!empty($att['title'])) {
|
||||
$attTitles[] = esc($att['title']);
|
||||
}
|
||||
}
|
||||
if (!empty($attTitles)) {
|
||||
echo ' (' . implode(', ', $attTitles) . ')';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Break between major documents (except after last document) -->
|
||||
<?php if ($docIdx < count($documents) - 1): ?>
|
||||
<pagebreak />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user