ABDM: add per-HI-type Consent Detail view (Phase 1)
- New Patient::getAbdmConsentArtifactDetail() helper builds a requested-vs-granted HI type breakdown for a patient's current ABDM consent artifact, using the bridge-team-confirmed 'Option A' model: overall status is single-valued (GRANTED/REVOKED/EXPIRED/DENIED), per-type status is derived by comparing the requested hiTypes (from the saved consent_request request_json) against the granted hi_types array (already flattened onto responses by M3HiuGatewayClient). No bridge changes or DB migrations required. - New Patient::abdm_consent_detail(int \) JSON endpoint + route patient/abdm_consent_detail/(:num). - New 'View Consent' link + Bootstrap modal on the OPD ABDM tab (Patient_Profile_Opd_V.php) rendering a lifecycle stepper, consent metadata (Consent ID, Purpose, Valid From/To, Erase Date), and a per-HI-type table (Document Type / Permission / Status / Timestamp). - Fixed a bug found during live testing: once a consent reaches COMPLETED (after a successful data_fetch), the highest-priority workflow row has no hi_types field (it's a FHIR data bundle), which was causing all granted types to show as DENIED. Now searches workflow history for the most recent row that actually carries the granted hi_types payload. - Live-verified against patient 15 (Meera Bisht): both requested HI types (OPConsultation, DiagnosticReport) correctly show GRANTED with timestamps.
This commit is contained in:
parent
37c3592f69
commit
7f47045c55
|
|
@ -501,6 +501,7 @@ $routes->group('billing', function($routes) {
|
|||
$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');
|
||||
$routes->get('patient/abdm_content_fetch_only/(:num)', 'Patient::abdm_content_fetch_only/$1');
|
||||
$routes->get('patient/abdm_consent_detail/(:num)', 'Patient::abdm_consent_detail/$1');
|
||||
$routes->get('patient/abdm_timeline/(:num)', 'Patient::abdm_timeline/$1');
|
||||
$routes->post('patient/save_profile_image/(:num)', 'Patient::save_profile_image/$1');
|
||||
$routes->match(['GET', 'POST'], 'patient/patient_file_upload/(:num)', 'Patient::patient_file_upload/$1');
|
||||
|
|
|
|||
|
|
@ -1861,6 +1861,45 @@ class Patient extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consent detail breakdown for the "View Consent" modal — shows the requested vs
|
||||
* granted Health Information Types for the current consent artifact, plus the
|
||||
* Requested -> Granted -> Revoked/Expired lifecycle timestamps for each type.
|
||||
*/
|
||||
public function abdm_consent_detail(int $pno)
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => '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)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Patient not found',
|
||||
]);
|
||||
}
|
||||
|
||||
$abhaContext = $this->buildAbhaProfileContext($patientRow);
|
||||
$abhaAddress = trim((string) ($abhaContext['abha_address'] ?? ''));
|
||||
if ($abhaAddress === '') {
|
||||
return $this->response->setStatusCode(422)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'ABHA Address not available for this patient.',
|
||||
]);
|
||||
}
|
||||
|
||||
$detail = $this->getAbdmConsentArtifactDetail($abhaAddress);
|
||||
if ((int) ($detail['ok'] ?? 0) !== 1) {
|
||||
return $this->response->setStatusCode(404)->setJSON($detail);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($detail);
|
||||
}
|
||||
|
||||
public function abdm_timeline(int $pno)
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
|
|
@ -3375,5 +3414,288 @@ class Patient extends BaseController
|
|||
return $snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the requested-vs-granted Health Information Type breakdown for a patient's
|
||||
* current ABDM consent artifact, for the "View Consent" detail modal.
|
||||
*
|
||||
* Per ABDM M3 spec (confirmed with bridge team 2026-07-28): revocation is
|
||||
* whole-artifact only (no partial per-HI-type revoke), so a single overall
|
||||
* `status` + the granted `hi_types` array is sufficient to compute each
|
||||
* requested type's effective status:
|
||||
* - GRANTED : type is in the granted hi_types array and artifact status is GRANTED
|
||||
* - DENIED : type was requested but is NOT in the granted hi_types array
|
||||
* - REVOKED : artifact status is REVOKED (applies to the whole artifact)
|
||||
* - EXPIRED : artifact status is EXPIRED
|
||||
* - REQUESTED: artifact is still pending (no GRANTED/REVOKED/EXPIRED yet)
|
||||
*/
|
||||
private function getAbdmConsentArtifactDetail(string $abhaAddress): array
|
||||
{
|
||||
$abhaAddress = trim($abhaAddress);
|
||||
if ($abhaAddress === '' || ! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.'];
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||
$select = ['id', 'operation', 'workflow_state', 'status', 'request_id', 'consent_id', 'created_at', 'updated_at', 'completed_at', 'expired_at', 'revoked_at', 'last_error', 'http_code', 'request_json', 'response_json'];
|
||||
foreach (['abdm_consent_request_id', 'abdm_consent_artifact_id', 'gateway_request_id'] as $optionalField) {
|
||||
if (in_array($optionalField, $fields, true)) {
|
||||
$select[] = $optionalField;
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $this->db->table('abdm_hiu_workflows')
|
||||
->select(implode(', ', $select))
|
||||
->where('abha_address', $abhaAddress)
|
||||
->whereIn('operation', [
|
||||
'consent_request',
|
||||
'consent_status',
|
||||
'consent_reconcile',
|
||||
'data_fetch',
|
||||
'consent_callback',
|
||||
'hi_on_request_callback',
|
||||
'hi_data_push_callback',
|
||||
])
|
||||
->orderBy('id', 'DESC')
|
||||
->get(60)
|
||||
->getResultArray();
|
||||
|
||||
if ($rows === []) {
|
||||
return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.'];
|
||||
}
|
||||
|
||||
$best = null;
|
||||
$bestPriority = -1;
|
||||
$bestDecoded = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
$decoded = [];
|
||||
}
|
||||
|
||||
$rawConsentStatus = strtoupper(trim((string) (
|
||||
$decoded['consent']['status']
|
||||
?? $decoded['consent_status']
|
||||
?? $decoded['status']
|
||||
?? $decoded['data']['consent']['status']
|
||||
?? ''
|
||||
)));
|
||||
$operation = strtoupper(trim((string) ($row['operation'] ?? '')));
|
||||
$status = strtoupper(trim((string) ($row['status'] ?? '')));
|
||||
$state = strtoupper(trim((string) ($row['workflow_state'] ?? '')));
|
||||
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 120;
|
||||
|
||||
if (($operation === 'DATA_FETCH' || $operation === 'HI_DATA_PUSH_CALLBACK') && $status === 'SUCCESS') {
|
||||
$phase = 'COMPLETED';
|
||||
$priority = 500;
|
||||
} elseif (in_array($rawConsentStatus, ['GRANTED', 'APPROVED', 'ACTIVE'], true)) {
|
||||
$phase = 'GRANTED';
|
||||
$priority = 430;
|
||||
} elseif ($rawConsentStatus === 'REVOKED') {
|
||||
$phase = 'REVOKED';
|
||||
$priority = 320;
|
||||
} elseif ($rawConsentStatus === 'EXPIRED') {
|
||||
$phase = 'EXPIRED';
|
||||
$priority = 310;
|
||||
} elseif ($rawConsentStatus === 'DENIED') {
|
||||
$phase = 'DENIED';
|
||||
$priority = 300;
|
||||
} elseif ($state === 'DATA_RECEIVED') {
|
||||
$phase = 'COMPLETED';
|
||||
$priority = 480;
|
||||
} elseif ($state === 'GRANTED') {
|
||||
$phase = 'GRANTED';
|
||||
$priority = 420;
|
||||
} elseif ($state === 'REVOKED') {
|
||||
$phase = 'REVOKED';
|
||||
$priority = 300;
|
||||
} elseif ($state === 'EXPIRED') {
|
||||
$phase = 'EXPIRED';
|
||||
$priority = 290;
|
||||
} elseif ($status === 'FAILED' && $operation === 'CONSENT_REQUEST') {
|
||||
$phase = 'FAILED';
|
||||
$priority = 260;
|
||||
} elseif ($status === 'FAILED') {
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 190;
|
||||
} elseif (in_array($state, ['REQUESTED', 'PENDING', 'STATUS_CHECKED'], true)) {
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 180;
|
||||
}
|
||||
|
||||
if ($best === null || $priority > $bestPriority) {
|
||||
$best = $row;
|
||||
$best['_phase'] = $phase;
|
||||
$bestPriority = $priority;
|
||||
$bestDecoded = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if ($best === null) {
|
||||
return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.'];
|
||||
}
|
||||
|
||||
$phase = (string) $best['_phase'];
|
||||
$consentId = trim((string) ($best['abdm_consent_artifact_id'] ?? $best['consent_id'] ?? $bestDecoded['consent_id'] ?? $bestDecoded['consentId'] ?? ''));
|
||||
$consentRequestId = trim((string) ($best['abdm_consent_request_id'] ?? $bestDecoded['abdm_consent_request_id'] ?? $bestDecoded['consent_request_id'] ?? $bestDecoded['consentRequestId'] ?? ''));
|
||||
$bestRequestId = trim((string) ($best['request_id'] ?? ''));
|
||||
|
||||
// Find the original consent_request row for this same chain, to get the
|
||||
// requested hi_types list + the original requested-on timestamp.
|
||||
$consentRequestRow = null;
|
||||
foreach ($rows as $row) {
|
||||
if (strtoupper((string) ($row['operation'] ?? '')) !== 'CONSENT_REQUEST') {
|
||||
continue;
|
||||
}
|
||||
$rowConsentReqId = trim((string) ($row['abdm_consent_request_id'] ?? ''));
|
||||
$rowRequestId = trim((string) ($row['request_id'] ?? ''));
|
||||
if (($consentRequestId !== '' && $rowConsentReqId === $consentRequestId)
|
||||
|| ($bestRequestId !== '' && $rowRequestId === $bestRequestId)
|
||||
) {
|
||||
$consentRequestRow = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($consentRequestRow === null) {
|
||||
foreach ($rows as $row) {
|
||||
if (strtoupper((string) ($row['operation'] ?? '')) === 'CONSENT_REQUEST') {
|
||||
$consentRequestRow = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$requestedHiTypes = [];
|
||||
$requestedOn = '';
|
||||
$purpose = '';
|
||||
$validFrom = '';
|
||||
$validTo = '';
|
||||
$eraseAt = '';
|
||||
|
||||
if (is_array($consentRequestRow)) {
|
||||
$reqPayload = json_decode((string) ($consentRequestRow['request_json'] ?? ''), true);
|
||||
if (! is_array($reqPayload)) {
|
||||
$reqPayload = [];
|
||||
}
|
||||
$consentBlock = (array) ($reqPayload['consent'] ?? []);
|
||||
$requestedHiTypes = $this->normalizeHiTypesList($consentBlock['hiTypes'] ?? $consentBlock['hi_types'] ?? []);
|
||||
$requestedOn = trim((string) ($consentRequestRow['created_at'] ?? ''));
|
||||
$purpose = trim((string) ($consentBlock['purpose']['text'] ?? $consentBlock['purpose']['code'] ?? ''));
|
||||
$validFrom = trim((string) ($consentBlock['permission']['dateRange']['from'] ?? ''));
|
||||
$validTo = trim((string) ($consentBlock['permission']['dateRange']['to'] ?? ''));
|
||||
$eraseAt = trim((string) ($consentBlock['permission']['dataEraseAt'] ?? ''));
|
||||
}
|
||||
|
||||
// Bridge flattens consent.hi_types -> top-level hi_types on consent_status /
|
||||
// consent_reconcile / consent_callback responses (see M3HiuGatewayClient
|
||||
// flatten logic). The "best" row for overall phase can be a data_fetch /
|
||||
// hi_data_push_callback row (once records are COMPLETED) whose response is
|
||||
// a FHIR data bundle with no hi_types field at all — so look separately for
|
||||
// the most recent row that actually carries the granted hi_types payload.
|
||||
$grantedHiTypes = [];
|
||||
$grantedOn = '';
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$rowHiTypes = $this->normalizeHiTypesList($decoded['hi_types'] ?? $decoded['consent']['hi_types'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
$grantedOn = trim((string) ($decoded['granted_at'] ?? $row['updated_at'] ?? ''));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$revokedOn = trim((string) ($best['revoked_at'] ?? ''));
|
||||
$expiredOn = trim((string) ($best['expired_at'] ?? ''));
|
||||
if (in_array($phase, ['GRANTED', 'COMPLETED'], true) && $grantedOn === '') {
|
||||
$grantedOn = trim((string) ($bestDecoded['granted_at'] ?? $best['updated_at'] ?? ''));
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$typesForItems = $requestedHiTypes !== [] ? $requestedHiTypes : $grantedHiTypes;
|
||||
foreach ($typesForItems as $hiType) {
|
||||
$itemStatus = 'REQUESTED';
|
||||
$itemTimestamp = $requestedOn;
|
||||
|
||||
if ($phase === 'REVOKED') {
|
||||
$itemStatus = 'REVOKED';
|
||||
$itemTimestamp = $revokedOn;
|
||||
} elseif ($phase === 'EXPIRED') {
|
||||
$itemStatus = 'EXPIRED';
|
||||
$itemTimestamp = $expiredOn;
|
||||
} elseif (in_array($phase, ['GRANTED', 'COMPLETED'], true)) {
|
||||
if (in_array($hiType, $grantedHiTypes, true)) {
|
||||
$itemStatus = 'GRANTED';
|
||||
$itemTimestamp = $grantedOn;
|
||||
} else {
|
||||
$itemStatus = 'DENIED';
|
||||
$itemTimestamp = $grantedOn;
|
||||
}
|
||||
} elseif ($phase === 'FAILED') {
|
||||
$itemStatus = 'FAILED';
|
||||
$itemTimestamp = trim((string) ($best['updated_at'] ?? ''));
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'document_name' => $hiType,
|
||||
'permission' => 'VIEW',
|
||||
'status' => $itemStatus,
|
||||
'timestamp' => $itemTimestamp,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'consent' => [
|
||||
'consent_id' => $consentId,
|
||||
'consent_request_id' => $consentRequestId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'status' => $phase,
|
||||
'purpose' => $purpose !== '' ? $purpose : 'Care Management',
|
||||
'requested_hi_types' => $requestedHiTypes,
|
||||
'granted_hi_types' => $grantedHiTypes,
|
||||
'valid_from' => $validFrom,
|
||||
'valid_to' => $validTo,
|
||||
'erase_at' => $eraseAt,
|
||||
'requested_on' => $requestedOn,
|
||||
'granted_on' => $grantedOn,
|
||||
'revoked_on' => $revokedOn,
|
||||
'expired_on' => $expiredOn,
|
||||
'items' => $items,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a hiTypes value (JSON string, array, or single string) into a clean,
|
||||
* de-duplicated string array.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function normalizeHiTypesList($value): array
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
$value = is_array($decoded) ? $decoded : [$value];
|
||||
}
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($value as $v) {
|
||||
$v = trim((string) $v);
|
||||
if ($v !== '' && ! in_array($v, $out, true)) {
|
||||
$out[] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@ if ($patientPhotoPath === '') {
|
|||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-secondary" id="abdmLastConsentBadge">Unknown</span>
|
||||
<span id="abdmStatusBox" class="small text-muted flex-grow-1">Click "Fetch ABDM Records" to request and load the patient's ABDM health records.</span>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 d-none" id="btnViewAbdmConsent" data-bs-toggle="modal" data-bs-target="#abdmConsentDetailModal">View Consent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -418,6 +419,20 @@ if ($patientPhotoPath === '') {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="abdmConsentDetailModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Consent Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="abdmConsentDetailBody">
|
||||
<div class="text-muted small">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
|
@ -426,6 +441,7 @@ $(function() {
|
|||
var abdmDocumentsUrl = '<?= base_url('billing/patient/abdm_documents/' . (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)) ?>';
|
||||
var abdmDocDetailBaseUrl = '<?= base_url('billing/patient/abdm_document_detail/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var currentAbdmRows = [];
|
||||
var autoFlowTimer = null;
|
||||
|
|
@ -481,6 +497,103 @@ $(function() {
|
|||
$('#btnFetchRecordsOnly').toggleClass('d-none', !show);
|
||||
}
|
||||
|
||||
// "View Consent" is shown once any consent activity has started (not just IDLE).
|
||||
function showViewConsentLink(show) {
|
||||
$('#btnViewAbdmConsent').toggleClass('d-none', !show);
|
||||
}
|
||||
|
||||
function consentItemBadgeClass(status) {
|
||||
switch ((status || '').toString().toUpperCase()) {
|
||||
case 'GRANTED': return 'bg-success-subtle text-success border border-success-subtle';
|
||||
case 'REVOKED': return 'bg-secondary-subtle text-secondary border border-secondary-subtle';
|
||||
case 'EXPIRED': return 'bg-warning-subtle text-warning border border-warning-subtle';
|
||||
case 'DENIED': return 'bg-danger-subtle text-danger border border-danger-subtle';
|
||||
case 'FAILED': return 'bg-danger-subtle text-danger border border-danger-subtle';
|
||||
default: return 'bg-info-subtle text-info border border-info-subtle';
|
||||
}
|
||||
}
|
||||
|
||||
function fmtConsentTs(value) {
|
||||
value = (value || '').toString().trim();
|
||||
if (value === '') {
|
||||
return '-';
|
||||
}
|
||||
// Accept both plain 'YYYY-MM-DD HH:MM:SS' and ISO forms.
|
||||
var d = new Date(value.indexOf('T') === -1 ? value.replace(' ', 'T') : value);
|
||||
if (isNaN(d.getTime())) {
|
||||
return escHtml(value);
|
||||
}
|
||||
return escHtml(d.toLocaleString());
|
||||
}
|
||||
|
||||
function renderAbdmConsentDetail(consent) {
|
||||
if (!consent) {
|
||||
return '<div class="text-danger small">No consent details available.</div>';
|
||||
}
|
||||
|
||||
var steps = ['REQUESTED', 'GRANTED'];
|
||||
var statusUpper = (consent.status || '').toString().toUpperCase();
|
||||
if (statusUpper === 'REVOKED') { steps.push('REVOKED'); }
|
||||
else if (statusUpper === 'EXPIRED') { steps.push('EXPIRED'); }
|
||||
|
||||
var stepperHtml = '<div class="d-flex align-items-center gap-2 mb-3 flex-wrap">';
|
||||
steps.forEach(function(step, idx) {
|
||||
var reached = steps.indexOf(statusUpper) >= idx || (statusUpper === 'COMPLETED' && step !== 'REVOKED' && step !== 'EXPIRED');
|
||||
stepperHtml += '<span class="badge ' + (reached ? 'bg-success' : 'bg-secondary') + '">' + (idx + 1) + '. ' + escHtml(step.charAt(0) + step.slice(1).toLowerCase()) + '</span>';
|
||||
if (idx < steps.length - 1) { stepperHtml += '<span class="text-muted">→</span>'; }
|
||||
});
|
||||
stepperHtml += '</div>';
|
||||
|
||||
var metaHtml = '<div class="row small text-muted mb-3">'
|
||||
+ '<div class="col-md-6">Consent ID: <strong>' + escHtml(consent.consent_id || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">ABHA: <strong>' + escHtml(consent.abha_address || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Purpose: <strong>' + escHtml(consent.purpose || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Status: <strong>' + escHtml(consent.status || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid From: <strong>' + fmtConsentTs(consent.valid_from) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid To: <strong>' + fmtConsentTs(consent.valid_to) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Requested On: <strong>' + fmtConsentTs(consent.requested_on) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Erase Date: <strong>' + fmtConsentTs(consent.erase_at) + '</strong></div>'
|
||||
+ '</div>';
|
||||
|
||||
var rowsHtml = '';
|
||||
(consent.items || []).forEach(function(item) {
|
||||
var ts = item.status === 'GRANTED' ? item.timestamp
|
||||
: item.status === 'REVOKED' ? item.timestamp
|
||||
: item.status === 'EXPIRED' ? item.timestamp
|
||||
: item.timestamp;
|
||||
rowsHtml += '<tr>'
|
||||
+ '<td>' + escHtml(item.document_name || '') + '</td>'
|
||||
+ '<td>' + escHtml(item.permission || 'VIEW') + '</td>'
|
||||
+ '<td><span class="badge ' + consentItemBadgeClass(item.status) + '">' + escHtml(item.status || '') + '</span></td>'
|
||||
+ '<td>' + fmtConsentTs(ts) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
if (rowsHtml === '') {
|
||||
rowsHtml = '<tr><td colspan="4" class="text-muted text-center">No health information types recorded for this request.</td></tr>';
|
||||
}
|
||||
|
||||
var tableHtml = '<div class="table-responsive"><table class="table table-sm table-bordered mb-0">'
|
||||
+ '<thead><tr><th>Document Type</th><th>Permission</th><th>Status</th><th>Timestamp</th></tr></thead>'
|
||||
+ '<tbody>' + rowsHtml + '</tbody></table></div>';
|
||||
|
||||
return stepperHtml + metaHtml + tableHtml;
|
||||
}
|
||||
|
||||
function loadAbdmConsentDetail() {
|
||||
$('#abdmConsentDetailBody').html('<div class="text-muted small">Loading...</div>');
|
||||
fetch(abdmConsentDetailUrl, { credentials: 'same-origin' })
|
||||
.then(function(resp) { return resp.json(); })
|
||||
.then(function(data) {
|
||||
if (!data || data.ok !== 1) {
|
||||
throw new Error((data && data.error) || 'Unable to load consent details.');
|
||||
}
|
||||
$('#abdmConsentDetailBody').html(renderAbdmConsentDetail(data.consent));
|
||||
})
|
||||
.catch(function(err) {
|
||||
$('#abdmConsentDetailBody').html('<div class="text-danger small">' + escHtml('Failed to load consent details: ' + (err.message || err)) + '</div>');
|
||||
});
|
||||
}
|
||||
|
||||
var zoom = 1;
|
||||
var minZoom = 0.5;
|
||||
var maxZoom = 3;
|
||||
|
|
@ -697,6 +810,7 @@ $(function() {
|
|||
}
|
||||
|
||||
showFetchRecordsButton(lastPhase === 'GRANTED' || lastPhase === 'COMPLETED');
|
||||
showViewConsentLink(lastPhase !== '' && lastPhase !== 'IDLE');
|
||||
updateConsentBadge(lastPhase, autoFlowNeedsFreshRequest);
|
||||
|
||||
var baseMsg = 'Loaded ' + currentAbdmRows.length + ' fetched ABDM record(s).';
|
||||
|
|
@ -784,6 +898,7 @@ $(function() {
|
|||
setFlowProgress(1);
|
||||
}
|
||||
showFetchRecordsButton(phase === 'GRANTED' || phase === 'COMPLETED');
|
||||
showViewConsentLink(phase !== '' && phase !== 'IDLE');
|
||||
updateConsentBadge(phase, autoFlowNeedsFreshRequest);
|
||||
|
||||
$('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text(info + (autoFlowRequestId ? (' | Request ID: ' + autoFlowRequestId) : ''));
|
||||
|
|
@ -858,6 +973,7 @@ $(function() {
|
|||
|
||||
setFlowProgress(3);
|
||||
showFetchRecordsButton(true);
|
||||
showViewConsentLink(true);
|
||||
updateConsentBadge('COMPLETED', false);
|
||||
|
||||
var info = (data.message || 'Records fetched successfully.').toString();
|
||||
|
|
@ -925,6 +1041,10 @@ $(function() {
|
|||
}
|
||||
});
|
||||
|
||||
$('#abdmConsentDetailModal').on('show.bs.modal', function() {
|
||||
loadAbdmConsentDetail();
|
||||
});
|
||||
|
||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||
|
||||
applyConsentButtonState();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user