billing: redesign ABHA wizard, enhance patient info panels and consult page photo/layout

This commit is contained in:
DevSoftTech Server 2026-05-08 13:29:57 +05:30
parent eac78b7014
commit fbb03a6abb
6 changed files with 547 additions and 66 deletions

View File

@ -49,6 +49,32 @@ class Opd_prescription extends BaseController
$query = $this->db->query($sql);
$patient = $query->getResult();
$patientProfileFilePath = '/assets/images/no_image.svg';
if (!empty($patient)) {
$profilePicturePath = '';
if ($this->db->fieldExists('profile_picture', 'patient_master')) {
$profilePicturePath = trim((string) ($patient[0]->profile_picture ?? ''));
}
$profileFileId = (int) ($patient[0]->profile_file_id ?? 0);
if ($profileFileId > 0 && $this->db->tableExists('file_upload_data')) {
$fileRow = $this->db->table('file_upload_data')
->select('full_path')
->where('id', $profileFileId)
->get(1)
->getRow();
$fullPath = trim((string) ($fileRow->full_path ?? ''));
if ($fullPath !== '') {
$pos = strpos($fullPath, '/uploads/', 1);
$patientProfileFilePath = $pos !== false ? substr($fullPath, $pos) : $fullPath;
}
} elseif ($profilePicturePath !== '') {
$pos = strpos($profilePicturePath, '/uploads/', 1);
$patientProfileFilePath = $pos !== false ? substr($profilePicturePath, $pos) : $profilePicturePath;
}
}
if (!empty($patient)) {
$patient[0]->str_age = get_age_1($patient[0]->dob ?? null, $patient[0]->age ?? '', $patient[0]->age_in_month ?? '', $patient[0]->estimate_dob ?? '', $opdRow->apointment_date ?? null);
}
@ -110,6 +136,7 @@ class Opd_prescription extends BaseController
'opd_id' => $opdId,
'opd_master' => $opdMaster,
'patient_master' => $patient,
'patient_profile_file_path' => $patientProfileFilePath,
'opd_prescription' => $prescription,
'next_visit_options' => $this->getNextVisitOptions(date('Y-m-d')),
'addiction_flags' => $this->getPatientAddictionFlags($patientId),

View File

@ -717,11 +717,23 @@ class Patient extends BaseController
return $this->response->setStatusCode(404)->setBody('Patient not found');
}
$defaultBackUrl = base_url('billing/patient/person_record') . '/' . (int) ($patient->id ?? 0) . '/0';
$backUrl = trim((string) $this->request->getGet('back_url'));
$backTitle = trim((string) $this->request->getGet('back_title'));
if ($backUrl === '') {
$backUrl = $defaultBackUrl;
}
if ($backTitle === '') {
$backTitle = 'Profile';
}
$profileFilePath = $this->getProfileFilePath((int) ($patient->profile_file_id ?? 0));
return view('billing/Patient_Profile_Image_V', [
'patient' => $patient,
'profileFilePath' => $profileFilePath,
'backUrl' => $backUrl,
'backTitle' => $backTitle,
]);
}

View File

@ -2,7 +2,7 @@
<h1>Profile Image</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="javascript:load_form('<?= base_url('billing/patient/person_record') ?>/<?= esc($patient->id) ?>/0');">Profile</a></li>
<li class="breadcrumb-item"><a href="javascript:load_form('<?= esc($backUrl ?? (base_url('billing/patient/person_record') . '/' . (int) ($patient->id ?? 0) . '/0'), 'js') ?>','<?= esc($backTitle ?? 'Profile', 'js') ?>');"><?= esc($backTitle ?? 'Profile') ?></a></li>
<li class="breadcrumb-item active">Image</li>
</ol>
</nav>
@ -11,7 +11,10 @@
<section class="section profile">
<div class="card">
<div class="card-header">
<h3 class="card-title mb-0">Profile Image</h3>
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<h3 class="card-title mb-0">Profile Image</h3>
<a href="javascript:load_form('<?= esc($backUrl ?? (base_url('billing/patient/person_record') . '/' . (int) ($patient->id ?? 0) . '/0'), 'js') ?>','<?= esc($backTitle ?? 'Profile', 'js') ?>');" class="btn btn-outline-secondary btn-sm">Back</a>
</div>
</div>
<div class="card-body">
<p class="mb-3">Patient: <strong><?= esc($patient->p_fname ?? '') ?></strong></p>

View File

@ -10,8 +10,94 @@
if ($backTitle === '') {
$backTitle = 'Profile';
}
$patientName = trim((string) (($patient->title ?? '') . ' ' . ($patient->p_fname ?? '')));
$relationText = trim((string) ($patient->p_relative ?? ''));
$relativeName = trim((string) ($patient->p_rname ?? ''));
$relationWithName = trim($relationText . ($relativeName !== '' ? ' ' . $relativeName : ''));
$ageYears = trim((string) ($patient->age ?? ''));
$ageMonths = trim((string) ($patient->age_in_month ?? ''));
$ageDisplay = '';
if ($ageYears !== '' || $ageMonths !== '') {
$ageDisplay = trim(($ageYears !== '' ? ($ageYears . ' Year') : '') . ($ageMonths !== '' ? (' ' . $ageMonths . ' Month') : ''));
}
// Fallback: derive age from DOB when explicit age fields are empty.
if ($ageDisplay === '' && !empty($patient->dob)) {
$dobTs = strtotime((string) $patient->dob);
if ($dobTs !== false) {
$today = new DateTime(date('Y-m-d'));
$dobDate = new DateTime(date('Y-m-d', $dobTs));
$diff = $dobDate->diff($today);
if ($diff->y > 0) {
$ageDisplay = $diff->y . ' Year';
} elseif ($diff->m > 0) {
$ageDisplay = $diff->m . ' Month';
} else {
$ageDisplay = $diff->d . ' Day';
}
}
}
$firstVisitRaw = '';
$patientData = (array) ($patient ?? []);
foreach (['date_of_registration', 'date_registration', 'insert_date', 'created_at', 'created_on', 'register_date'] as $candidate) {
if (!empty($patientData[$candidate])) {
$firstVisitRaw = (string) $patientData[$candidate];
break;
}
}
$firstVisitDisplay = '-';
if ($firstVisitRaw !== '') {
$firstVisitTs = strtotime($firstVisitRaw);
if ($firstVisitTs !== false) {
$firstVisitDisplay = date('d-m-Y', $firstVisitTs);
}
}
?>
<style>
.patient-info-card {
background: #f5f9ff;
border: 1px solid #d8e7ff;
border-left: 5px solid #0d6efd;
border-radius: 8px;
padding: 12px 14px;
margin-top: 10px;
width: 100%;
}
.patient-info-title {
font-size: 1rem;
font-weight: 700;
color: #0b3b91;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.2px;
}
.patient-info-row {
display: flex;
flex-wrap: wrap;
gap: 10px 22px;
align-items: center;
}
.patient-info-item {
font-size: 1.02rem;
line-height: 1.3;
white-space: nowrap;
}
.patient-info-item .label {
font-weight: 700;
color: #08306b;
margin-right: 4px;
}
.patient-info-item .value {
font-weight: 600;
color: #111827;
}
</style>
<div class="pagetitle">
<h1>Consult History</h1>
<nav>
@ -25,13 +111,35 @@
<section class="section profile">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<h3 class="card-title mb-0">Old Prescription With Scanned Records</h3>
<div class="small text-muted mt-1">Patient: <?= esc($patient->p_fname ?? '') ?></div>
</div>
<div class="d-flex align-items-center gap-2">
<a href="javascript:load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>');" class="btn btn-outline-secondary btn-sm">Back</a>
<span class="badge bg-secondary"><?= count($opdGroups ?? []) ?> OPD Record(s)</span>
<div class="w-100">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<h3 class="card-title mb-0">Old Prescription With Scanned Records</h3>
<div class="d-flex align-items-center gap-2">
<a href="javascript:load_form('<?= esc($backUrl, 'js') ?>','<?= esc($backTitle, 'js') ?>');" class="btn btn-outline-secondary btn-sm">Back</a>
<span class="badge bg-secondary"><?= count($opdGroups ?? []) ?> OPD Record(s)</span>
</div>
</div>
<div class="patient-info-card">
<div class="patient-info-title">Patient Information</div>
<div class="patient-info-row">
<div class="patient-info-item">
<span class="label">Name:</span>
<span class="value"><?= esc($patientName !== '' ? $patientName : '-') ?></span>
</div>
<div class="patient-info-item">
<span class="label">Relation:</span>
<span class="value"><?= esc($relationWithName !== '' ? $relationWithName : '-') ?></span>
</div>
<div class="patient-info-item">
<span class="label">Age:</span>
<span class="value"><?= esc($ageDisplay !== '' ? $ageDisplay : '-') ?></span>
</div>
<div class="patient-info-item">
<span class="label">First Visit Date:</span>
<span class="value"><?= esc($firstVisitDisplay) ?></span>
</div>
</div>
</div>
</div>
</div>
<div class="card-body">

View File

@ -12,6 +12,33 @@
input:autofill {
background-color: white !important;
}
/* ABHA wizard progress track */
.abha-progress-wrap { display:flex; align-items:flex-start; margin-bottom:24px; }
.abha-progress-wrap .abha-node {
display:flex; flex-direction:column; align-items:center; flex:1; position:relative;
}
.abha-progress-wrap .abha-node:not(:last-child)::after {
content:''; position:absolute; top:16px; left:calc(50% + 20px);
right:calc(-50% + 20px); height:2px; background:#dee2e6; z-index:0;
}
.abha-progress-wrap .abha-node.done:not(:last-child)::after { background:#28a745; }
.abha-badge {
width:32px; height:32px; border-radius:50%;
display:flex; align-items:center; justify-content:center;
font-weight:700; font-size:13px; z-index:1;
background:#dee2e6; color:#6c757d; border:2px solid #dee2e6;
transition: all 0.25s;
}
.abha-node.active .abha-badge { background:#fff; color:#007bff; border-color:#007bff; }
.abha-node.done .abha-badge { background:#28a745; color:#fff; border-color:#28a745; }
.abha-node-label {
font-size:0.72rem; text-align:center; margin-top:5px;
color:#aaa; max-width:80px; line-height:1.2;
}
.abha-node.active .abha-node-label { color:#007bff; font-weight:600; }
.abha-node.done .abha-node-label { color:#28a745; }
.abha-panel { border-left:3px solid #007bff; padding-left:16px; }
</style>
<section class="content">
<div class="row">
@ -294,61 +321,133 @@
<div class="tab-content pt-3" id="abhaSubTabsContent">
<div class="tab-pane fade show active" id="abha-create" role="tabpanel" aria-labelledby="abha-create-tab">
<form id="abha_create_form" class="needs-validation" novalidate>
<?= csrf_field() ?>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Aadhaar Number</label>
<input type="text" class="form-control" id="abha_create_aadhaar"
name="aadhaar" placeholder="12-digit Aadhaar" required
data-inputmask='"mask": "9999-9999-9999"' data-mask>
</div>
<!-- Step progress indicator -->
<div class="abha-progress-wrap mt-3 mb-4">
<div class="abha-node active" id="abha_node_1">
<div class="abha-badge" id="abha_badge_1">1</div>
<div class="abha-node-label">Aadhaar &amp; Consent</div>
</div>
<div class="abha-node" id="abha_node_2">
<div class="abha-badge" id="abha_badge_2">2</div>
<div class="abha-node-label">OTP Verify</div>
</div>
<div class="abha-node" id="abha_node_3">
<div class="abha-badge" id="abha_badge_3">3</div>
<div class="abha-node-label">Mobile Confirm</div>
</div>
<div class="abha-node" id="abha_node_4">
<div class="abha-badge" id="abha_badge_4">4</div>
<div class="abha-node-label">ABHA Ready</div>
</div>
</div>
<!-- Step 1: Aadhaar entry + consent -->
<div id="abha_step_1" class="abha-panel">
<p class="text-muted small mb-3">Enter the patient's Aadhaar number. An OTP will be sent to the mobile linked with it.</p>
<div class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label">Aadhaar Number <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="abha_aadhaar_masked"
placeholder="0000-0000-0000" autocomplete="off"
data-inputmask='"mask": "9999-9999-9999"' data-mask>
<small class="text-muted">Mobile must be linked with Aadhaar for OTP.</small>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Mobile Number</label>
<input type="text" class="form-control" id="abha_create_mobile"
name="mobile" placeholder="10-digit Mobile" required
data-inputmask='"mask": "9999999999"' data-mask>
<div class="col-md-3">
<label class="form-label">Authentication Via</label>
<select class="form-select" id="abha_auth_type">
<option value="aadhaar_otp">OTP</option>
<option value="biometric">Biometric</option>
</select>
</div>
</div>
<div class="mt-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="abha_consent_chk">
<label class="form-check-label" for="abha_consent_chk">
I confirm the patient has given consent to share their Aadhaar details with NHA/ABDM for ABHA number creation.
<a href="#" data-bs-toggle="collapse" data-bs-target="#abha_full_consent" class="ms-1 small">(read full consent)</a>
</label>
</div>
<div class="collapse mt-2" id="abha_full_consent">
<div class="card card-body bg-light py-2 small text-muted" style="max-height:100px;overflow-y:auto;">
I hereby declare that I am voluntarily sharing my Aadhaar number and demographic information issued by UIDAI with the National Health Authority (NHA) for the sole purpose of creating an ABHA number. I understand this number may be used for healthcare service purposes as notified by ABDM. My personal identifiable information (Name, Address, Age, DOB, Gender, Photo) may be made available to entities in the National Digital Health Ecosystem. I consent to use and disclosure of my health information to NDHE entities as permitted by law.
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Full Name</label>
<input type="text" class="form-control" id="abha_create_name"
name="full_name" placeholder="Patient Full Name" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Gender</label>
<select class="form-select" id="abha_create_gender" name="gender" required>
<option value="">Select Gender</option>
<option value="M">Male</option>
<option value="F">Female</option>
<option value="O">Other</option>
</select>
</div>
<div id="abha_step1_alert" class="mt-2"></div>
<div class="mt-3">
<button type="button" class="btn btn-primary" id="abha_get_otp_btn">
<span id="abha_get_otp_spinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
Send OTP
</button>
</div>
</div>
<!-- Step 2: OTP verification -->
<div id="abha_step_2" class="abha-panel" style="display:none;">
<p class="text-muted small mb-3">Enter the OTP sent to the mobile number linked with Aadhaar.</p>
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">One-Time Password <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-lg text-center letter-spacing-2"
id="abha_otp_input" placeholder="— — — — — —"
maxlength="6" autocomplete="one-time-code" inputmode="numeric">
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Date of Birth</label>
<input type="date" class="form-control" id="abha_create_dob"
name="dob" required>
<div id="abha_step2_alert" class="mt-2"></div>
<div class="mt-3 d-flex gap-2 flex-wrap">
<button type="button" class="btn btn-success" id="abha_verify_otp_btn">
<span id="abha_verify_otp_spinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
Confirm OTP
</button>
<button type="button" class="btn btn-outline-secondary" id="abha_resend_otp_btn">Resend OTP</button>
<button type="button" class="btn btn-link text-secondary px-0" id="abha_back_step1_btn">&larr; Back</button>
</div>
</div>
<!-- Step 3: Mobile confirmation -->
<div id="abha_step_3" class="abha-panel" style="display:none;">
<p class="text-muted small mb-3">Verify or update the communication mobile number for this patient.</p>
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">Mobile Number <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="abha_comm_mobile"
placeholder="10-digit number" maxlength="10" inputmode="numeric" autocomplete="off">
</div>
</div>
<div id="abha_step3_alert" class="mt-2"></div>
<div class="mt-3 d-flex gap-2">
<button type="button" class="btn btn-primary" id="abha_comm_next_btn">
<span id="abha_comm_spinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
Confirm &amp; Continue
</button>
<button type="button" class="btn btn-link text-secondary px-0" id="abha_back_step2_btn">&larr; Back</button>
</div>
</div>
<!-- Step 4: Done / ABHA address -->
<div id="abha_step_4" class="abha-panel" style="display:none;">
<div id="abha_created_result" class="mb-3"></div>
<div class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label">Health ID Address <small class="text-muted">(optional)</small></label>
<div class="input-group">
<input type="text" class="form-control" id="abha_address_input" placeholder="e.g. ramesh.kumar">
<span class="input-group-text">@abdm</span>
</div>
<small class="text-muted">Choose from suggested addresses or type your own.</small>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-primary">Generate OTP & Create ABHA</button>
</div>
<div id="abha_step4_alert" class="mt-2"></div>
<div class="mt-3">
<button type="button" class="btn btn-success" id="abha_finalise_btn">
<span id="abha_finalise_spinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
Save ABHA Address
</button>
</div>
</form>
</div>
</div>
<div class="tab-pane fade" id="abha-verify" role="tabpanel" aria-labelledby="abha-verify-tab">
@ -427,6 +526,198 @@
});
});
/* ===================== ABHA CREATION WIZARD ===================== */
(function () {
var txnId = null; // transaction id from ABDM API
var csrfToken = function() { return $('input[name="<?= csrf_token() ?>"]').first().val(); };
function abhaStep(step) {
$('#abha_step_1,#abha_step_2,#abha_step_3,#abha_step_4').hide();
$('#abha_step_' + step).show();
// update progress nodes
for (var i = 1; i <= 4; i++) {
var $n = $('#abha_node_' + i);
var $b = $('#abha_badge_' + i);
$n.removeClass('active done');
if (i < step) {
$n.addClass('done');
$b.html('<i class="fas fa-check fa-xs"></i>');
} else if (i === step) {
$n.addClass('active');
$b.text(i);
} else {
$b.text(i);
}
}
}
function showAlert(containerId, type, msg) {
$('#' + containerId).html('<div class="alert alert-' + type + ' py-2 mt-2">' + msg + '</div>');
}
// Apply inputmask to Aadhaar field
$('#abha_aadhaar_masked').inputmask('9999-9999-9999');
// Step 1 → Get OTP
$('#abha_get_otp_btn').on('click', function() {
var aadhaar = $('#abha_aadhaar_masked').val().replace(/\D/g, '');
if (aadhaar.length !== 12) {
showAlert('abha_step1_alert', 'danger', 'Please enter a valid 12-digit Aadhaar number.');
return;
}
if (!$('#abha_consent_chk').is(':checked')) {
showAlert('abha_step1_alert', 'warning', 'You must agree to the Terms and Conditions to proceed.');
return;
}
$('#abha_step1_alert').html('');
$('#abha_get_otp_spinner').removeClass('d-none');
$('#abha_get_otp_btn').prop('disabled', true);
$.post('<?= base_url('abha/create/initiate') ?>', {
aadhaar: aadhaar,
auth_type: $('#abha_auth_type').val(),
'<?= csrf_token() ?>': csrfToken()
}, function(resp) {
$('#abha_get_otp_spinner').addClass('d-none');
$('#abha_get_otp_btn').prop('disabled', false);
if (resp && resp.ok == 1) {
txnId = resp.txn_id || null;
abhaStep(2);
} else {
showAlert('abha_step1_alert', 'danger', (resp && resp.error_text) ? resp.error_text : 'Failed to send OTP. Please try again.');
}
}, 'json').fail(function() {
$('#abha_get_otp_spinner').addClass('d-none');
$('#abha_get_otp_btn').prop('disabled', false);
showAlert('abha_step1_alert', 'danger', 'Server error. Please try again.');
});
});
// Back to step 1
$('#abha_back_step1_btn').on('click', function() { abhaStep(1); });
// Resend OTP
$('#abha_resend_otp_btn').on('click', function() {
$('#abha_get_otp_btn').trigger('click');
});
// Step 2 → Verify OTP
$('#abha_verify_otp_btn').on('click', function() {
var otp = $('#abha_otp_input').val().trim();
if (!/^\d{6}$/.test(otp)) {
showAlert('abha_step2_alert', 'danger', 'Please enter the 6-digit OTP.');
return;
}
$('#abha_step2_alert').html('');
$('#abha_verify_otp_spinner').removeClass('d-none');
$('#abha_verify_otp_btn').prop('disabled', true);
$.post('<?= base_url('abha/create/verify_otp') ?>', {
otp: otp,
txn_id: txnId,
'<?= csrf_token() ?>': csrfToken()
}, function(resp) {
$('#abha_verify_otp_spinner').addClass('d-none');
$('#abha_verify_otp_btn').prop('disabled', false);
if (resp && resp.ok == 1) {
txnId = resp.txn_id || txnId;
if (resp.mobile) $('#abha_comm_mobile').val(resp.mobile);
abhaStep(3);
} else {
showAlert('abha_step2_alert', 'danger', (resp && resp.error_text) ? resp.error_text : 'Invalid OTP. Please try again.');
}
}, 'json').fail(function() {
$('#abha_verify_otp_spinner').addClass('d-none');
$('#abha_verify_otp_btn').prop('disabled', false);
showAlert('abha_step2_alert', 'danger', 'Server error. Please try again.');
});
});
// Back to step 2
$('#abha_back_step2_btn').on('click', function() { abhaStep(2); });
// Step 3 → Communication next
$('#abha_comm_next_btn').on('click', function() {
var mobile = $('#abha_comm_mobile').val().trim();
if (!/^\d{10}$/.test(mobile)) {
showAlert('abha_step3_alert', 'danger', 'Please enter a valid 10-digit mobile number.');
return;
}
$('#abha_step3_alert').html('');
$('#abha_comm_spinner').removeClass('d-none');
$('#abha_comm_next_btn').prop('disabled', true);
$.post('<?= base_url('abha/create/communication') ?>', {
mobile: mobile,
txn_id: txnId,
'<?= csrf_token() ?>': csrfToken()
}, function(resp) {
$('#abha_comm_spinner').addClass('d-none');
$('#abha_comm_next_btn').prop('disabled', false);
if (resp && resp.ok == 1) {
txnId = resp.txn_id || txnId;
// Show created ABHA details
if (resp.abha_number) {
$('#abha_created_result').html(
'<div class="alert alert-success">' +
'<strong>ABHA Number Created!</strong><br>' +
'ABHA Number: <strong>' + resp.abha_number + '</strong><br>' +
(resp.name ? 'Name: ' + resp.name + '<br>' : '') +
'</div>'
);
}
if (resp.suggested_addresses && resp.suggested_addresses.length) {
var opts = resp.suggested_addresses.map(function(a) {
return '<option value="' + a + '">' + a + '</option>';
}).join('');
$('#abha_address_input').replaceWith(
'<select class="form-select" id="abha_address_input"><option value="">Select an address...</option>' + opts + '</select>'
);
}
abhaStep(4);
} else {
showAlert('abha_step3_alert', 'danger', (resp && resp.error_text) ? resp.error_text : 'Failed to proceed. Please try again.');
}
}, 'json').fail(function() {
$('#abha_comm_spinner').addClass('d-none');
$('#abha_comm_next_btn').prop('disabled', false);
showAlert('abha_step3_alert', 'danger', 'Server error. Please try again.');
});
});
// Step 4 → Finalise ABHA address
$('#abha_finalise_btn').on('click', function() {
var addr = $('#abha_address_input').val().trim();
if (!addr) {
showAlert('abha_step4_alert', 'warning', 'Please choose an ABHA address.');
return;
}
$('#abha_step4_alert').html('');
$('#abha_finalise_spinner').removeClass('d-none');
$('#abha_finalise_btn').prop('disabled', true);
$.post('<?= base_url('abha/create/address') ?>', {
abha_address: addr,
txn_id: txnId,
'<?= csrf_token() ?>': csrfToken()
}, function(resp) {
$('#abha_finalise_spinner').addClass('d-none');
$('#abha_finalise_btn').prop('disabled', false);
if (resp && resp.ok == 1) {
showAlert('abha_step4_alert', 'success', 'ABHA Address <strong>' + addr + '@abdm</strong> successfully created!');
$('#abha_finalise_btn').hide();
} else {
showAlert('abha_step4_alert', 'danger', (resp && resp.error_text) ? resp.error_text : 'Failed to create ABHA address.');
}
}, 'json').fail(function() {
$('#abha_finalise_spinner').addClass('d-none');
$('#abha_finalise_btn').prop('disabled', false);
showAlert('abha_step4_alert', 'danger', 'Server error. Please try again.');
});
});
})();
/* =============================================================== */
$('#input_abha_id').on('change blur', function() {
var abhaId = ($(this).val() || '').toString().trim();
if (abhaId !== '') {

View File

@ -54,6 +54,24 @@
border-radius: .5rem;
padding: .75rem;
}
.rx-patient-photo {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #d7e3f4;
background: #fff;
}
.rx-meta-line {
margin-bottom: .2rem;
line-height: 1.35;
}
.rx-meta-photo-col {
text-align: center;
}
.rx-meta-photo-actions {
margin-top: .4rem;
}
.rx-counter {
font-size: .8rem;
color: #6c757d;
@ -346,6 +364,19 @@
'back_url' => $historyBackUrl,
'back_title' => 'Consult',
]);
$profileImageUrl = base_url('billing/patient/show_profile_image') . '/' . (int) ($patient_master[0]->id ?? 0) . '/1?' . http_build_query([
'back_url' => $historyBackUrl,
'back_title' => 'Consult',
]);
$patientRelation = trim((string) ($patient_master[0]->p_relative ?? ''));
$patientRelativeName = trim((string) ($patient_master[0]->p_rname ?? ''));
$patientName = trim((string) ($patient_master[0]->p_fname ?? ''));
$patientNameWithRelation = $patientName;
$relationLabel = trim($patientRelation . ($patientRelativeName !== '' ? (' ' . $patientRelativeName) : ''));
if ($relationLabel !== '') {
$patientNameWithRelation .= ' (' . $relationLabel . ')';
}
$patientAbhaAddress = trim((string) ($patient_master[0]->abha_address ?? $patient_master[0]->abha ?? ''));
?>
<a href="javascript:load_form('<?= esc($historyUrl, 'js') ?>','Consult History');"
class="btn btn-info btn-sm"
@ -377,19 +408,28 @@
<div class="row g-3 rx-two-panel">
<div class="col-lg-4 rx-left-panel">
<div class="rx-meta-box mb-3">
<div><strong>Name:</strong> <?= esc($patient_master[0]->p_fname ?? '') ?></div>
<div><strong>UHID:</strong> <?= esc($patient_master[0]->p_code ?? '') ?></div>
<div><strong>Age:</strong> <?= esc($patient_master[0]->str_age ?? '') ?></div>
<div><strong>Gender:</strong> <?= esc($patient_master[0]->xgender ?? '') ?></div>
<div class="mt-2">
<label class="form-label mb-1"><strong>ABHA Address:</strong></label>
<input type="text" class="form-control form-control-sm" id="abha_address" maxlength="18"
value="<?= esc($patient_master[0]->abha_address ?? $patient_master[0]->abha ?? '') ?>"
placeholder="Enter ABHA Address">
<div class="row g-2 align-items-start">
<div class="col-8">
<div class="rx-meta-line"><strong>Name:</strong> <?= esc($patientNameWithRelation !== '' ? $patientNameWithRelation : '-') ?></div>
<div class="rx-meta-line"><strong>UHID:</strong> <?= esc($patient_master[0]->p_code ?? '') ?></div>
<div class="rx-meta-line"><strong>Age:</strong> <?= esc($patient_master[0]->str_age ?? '') ?></div>
<div class="rx-meta-line"><strong>Gender:</strong> <?= esc($patient_master[0]->xgender ?? '') ?></div>
<?php if ($patientAbhaAddress !== '') { ?>
<div class="rx-meta-line"><strong>ABHA Address:</strong> <?= esc($patientAbhaAddress) ?></div>
<?php } ?>
<div class="rx-meta-line"><strong>OPD:</strong> <?= esc($opd_master[0]->opd_code ?? '') ?></div>
<div class="rx-meta-line"><strong>Date:</strong> <?= esc($opd_master[0]->apointment_date ?? '') ?></div>
<div class="rx-meta-line mb-0"><strong>Doctor:</strong> <?= esc($opd_master[0]->doc_name ?? '') ?></div>
</div>
<div class="col-4 rx-meta-photo-col">
<img src="<?= esc($patient_profile_file_path ?? '/assets/images/no_image.svg') ?>" alt="Patient Profile" class="rx-patient-photo">
<div class="rx-meta-photo-actions">
<a href="javascript:load_form('<?= esc($profileImageUrl, 'js') ?>','Profile Image');" class="btn btn-success btn-sm">
<i class="bi bi-camera-fill"></i> Edit Picture
</a>
</div>
</div>
</div>
<div><strong>OPD:</strong> <?= esc($opd_master[0]->opd_code ?? '') ?></div>
<div><strong>Date:</strong> <?= esc($opd_master[0]->apointment_date ?? '') ?></div>
<div><strong>Doctor:</strong> <?= esc($opd_master[0]->doc_name ?? '') ?></div>
</div>
<div class="card">
<div class="card-header"><strong>Quick Tips</strong></div>