devsofttech_hms_ci4/app/Views/abdm/task_board.php
DevSoftTech Server 1b6f2c2db7 feat: Add Submit to ABDM button on FHIR bundle preview
- POST Opd_prescription/fhir_bundle_submit endpoint:
  loads stored FHIR bundle, fetches patient/OPD/doctor context,
  calls EAtriaBridgeConnector::pushRecord to push to bridge
- Standalone preview page (/Opd_prescription/fhir_bundle_preview/N):
  added Submit to ABDM Bridge button with CSRF-safe AJAX + inline
  success/error alert
- FHIR modal in task_board.php: added Submit to ABDM button next
  to Copy JSON; JS reads _fhirOpdId/_fhirSessionId and posts to
  fhir_bundle_submit with CSRF token refresh
2026-05-22 15:54:13 +05:30

1669 lines
88 KiB
PHP

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ABDM Work Task Board</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #f5f7fb; }
.container-wrap { width: 100%; margin: 0; padding: 20px 24px; box-sizing: border-box; }
.table td, .table th { vertical-align: middle; }
.status-pill { text-transform: uppercase; font-size: 11px; letter-spacing: .4px; }
</style>
</head>
<body>
<div class="container-wrap">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h4 class="mb-0">ABDM Work Task Board</h4>
<div class="small text-muted">Unupdated ABHA Patient List + Separate ABDM Task Queues</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary" id="btnRefresh">Refresh</button>
</div>
<div class="d-flex flex-wrap gap-2 mb-3" id="taskFilters">
<button class="btn btn-sm btn-primary filter-btn" data-filter="patient_abha_create">Unupdated ABHA</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="patient_abha_update">ABHA Verify Pending</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="opd_book">OPD Book</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="opd_consult_publish">OPD Consult Publish</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="lab_report_publish">Lab Reports</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="radiology_report_publish">Radiology Reports</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="ipd_admission_publish">IPD Admission</button>
<button class="btn btn-sm btn-outline-primary filter-btn" data-filter="ipd_discharge_publish">IPD Discharge</button>
</div>
<div class="card shadow-sm mb-3 d-none" id="fhirDebugCard">
<div class="card-header py-2 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-2 flex-wrap">
<strong id="fhirDebugTitle">FHIR JSON Debug</strong>
<span class="badge bg-secondary" id="fhirDataBadge">UNKNOWN</span>
<span class="badge bg-secondary" id="fhirHttpBadge">HTTP -</span>
</div>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-sm btn-outline-primary" id="btnCopyFhirDebug">Copy JSON</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="btnHideFhirDebug">Hide</button>
</div>
</div>
<div class="card-body">
<div class="small text-muted mb-2" id="fhirDebugMeta">Ready</div>
<pre id="fhirDebugJson" class="mb-0" style="max-height:420px; overflow:auto; background:#0b1020; color:#d9e2ff; padding:12px; border-radius:8px;">{}</pre>
</div>
</div>
<div class="card shadow-sm" id="taskTableCard">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-striped mb-0" id="taskTable">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Task</th>
<th>Patient</th>
<th>ABHA</th>
<th>Entity</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach (($tasks ?? []) as $t): ?>
<?php
$type = strtolower((string) ($t['task_type'] ?? ''));
$abhaId = trim((string) ($t['abha_id'] ?? ''));
$hasValidAbha = preg_match('/^\d{14}$/', $abhaId) === 1;
$payload = json_decode((string) ($t['payload_json'] ?? ''), true);
if (! is_array($payload)) {
$payload = [];
}
$meta = (isset($payload['meta']) && is_array($payload['meta'])) ? $payload['meta'] : [];
$opdId = (int) ($meta['opd_id'] ?? 0);
$opdSessionId = (int) ($meta['opd_session_id'] ?? 0);
$showSandbox = ($type === 'opd_prescription_publish' && $opdId > 0);
$showPreview = ($type === 'opd_prescription_publish' && $opdId > 0);
?>
<tr
data-task-id="<?= (int) ($t['id'] ?? 0) ?>"
data-task-type="<?= esc((string) ($t['task_type'] ?? '')) ?>"
data-entity-id="<?= esc((string) ($t['entity_id'] ?? '')) ?>"
data-patient-id="<?= (int) ($t['patient_id'] ?? 0) ?>"
data-abha-id="<?= esc($abhaId) ?>"
data-opd-id="<?= $opdId ?>"
data-opd-session-id="<?= $opdSessionId ?>"
data-sandbox-eligible="<?= $showSandbox ? '1' : '0' ?>"
>
<td><?= (int) ($t['id'] ?? 0) ?></td>
<td>
<div><strong><?= esc((string) ($t['task_code'] ?? '')) ?></strong></div>
<div class="text-muted small"><?= esc((string) ($t['task_type'] ?? '')) ?></div>
</td>
<td>
<div><?= esc((string) ($t['patient_name'] ?? '')) ?></div>
<div class="text-muted small">#<?= (int) ($t['patient_id'] ?? 0) ?></div>
</td>
<td><input type="text" class="form-control form-control-sm abha-input" value="<?= esc((string) ($t['abha_id'] ?? '')) ?>" placeholder="14-digit ABHA"></td>
<td>
<div><?= esc((string) ($t['entity_type'] ?? '')) ?></div>
<div class="text-muted small"><?= esc((string) ($t['entity_id'] ?? '')) ?></div>
</td>
<td><span class="badge bg-secondary status-pill"><?= esc((string) ($t['status'] ?? 'pending')) ?></span></td>
<td>
<div class="d-flex gap-1 flex-wrap">
<?php if ($type === 'patient_abha_create' || $type === 'patient_abha_link'): ?>
<button class="btn btn-sm btn-outline-success action-btn" data-action="create_abha">Create ABHA</button>
<?php elseif ($type === 'patient_abha_update'): ?>
<button class="btn btn-sm btn-outline-primary action-btn" data-action="update_abha">Update ABHA</button>
<?php else: ?>
<button class="btn btn-sm btn-outline-warning action-btn" data-action="submit">Submit</button>
<?php endif; ?>
<?php if ($showSandbox): ?>
<button class="btn btn-sm btn-outline-info sandbox-btn">Sandbox</button>
<?php endif; ?>
<?php if ($showPreview): ?>
<button class="btn btn-sm btn-outline-primary preview-fhir-btn">Preview FHIR</button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-dark close-btn">Close</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="card shadow-sm mt-3 d-none" id="abdmSandboxCard">
<div class="card-header py-2 d-flex justify-content-between align-items-center">
<strong>ABDM Sandbox Actions</strong>
<button type="button" class="btn btn-sm btn-outline-secondary" id="btnSandboxHide">Hide</button>
</div>
<div class="card-body p-3">
<div class="small text-muted mb-2" id="abdmSandboxContext">Select eligible OPD Publish task to use sandbox actions.</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label mb-1 small">ABHA Number</label>
<input type="text" class="form-control form-control-sm" id="tb_abha_id" maxlength="14" placeholder="14-digit ABHA">
</div>
<div class="col-md-6">
<label class="form-label mb-1 small">QR Payload (Scan &amp; Share)</label>
<input type="text" class="form-control form-control-sm" id="tb_abdm_qr_payload" placeholder="Paste scanned QR payload">
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-2">
<button type="button" class="btn btn-outline-primary btn-sm" id="tb_btn_abdm_scan_lookup">Scan Lookup</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="tb_btn_abdm_validate_abha">Validate ABHA</button>
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<input type="text" class="form-control form-control-sm" id="tb_abdm_purpose_code" placeholder="Purpose (ex: CAREMGT)">
</div>
<div class="col-md-6">
<input type="datetime-local" class="form-control form-control-sm" id="tb_abdm_consent_expires_at">
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-2">
<button type="button" class="btn btn-outline-success btn-sm" id="tb_btn_abdm_consent_request">Request Consent</button>
<button type="button" class="btn btn-outline-primary btn-sm" id="tb_btn_abdm_preview_fhir">Preview FHIR JSON</button>
<button type="button" class="btn btn-outline-warning btn-sm" id="tb_btn_abdm_share_fhir">Share FHIR</button>
</div>
<div class="mb-2">
<label class="form-label mb-1 small">Consent Handle</label>
<input type="text" class="form-control form-control-sm" id="tb_abdm_consent_handle" placeholder="Auto-filled after consent request">
</div>
<div class="row g-2 mb-2">
<div class="col-md-7">
<input type="number" min="1" class="form-control form-control-sm" id="tb_abdm_claim_document_id" placeholder="Claim Document ID">
</div>
<div class="col-md-5">
<button type="button" class="btn btn-outline-dark btn-sm w-100" id="tb_btn_abdm_claim_status">Claim Status</button>
</div>
</div>
<div class="small text-muted" id="abdmSandboxStatus">Ready</div>
</div>
</div>
<!-- OPD Book Card -->
<div class="card shadow-sm mt-3 d-none" id="opdBookCard">
<div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<strong>OPD Book — ABDM Patients</strong>
<span class="badge bg-primary ms-2" id="hmsOpdBadge"><?= count($opd_book_rows ?? []) ?></span>
</div>
<div class="d-flex gap-2 align-items-center">
<input type="date" class="form-control form-control-sm" id="opdBookDate" value="<?= date('Y-m-d') ?>" style="width:145px">
<button type="button" class="btn btn-sm btn-outline-info" id="btnFetchAbdmQueue">Fetch ABDM Queue</button>
</div>
</div>
<div class="card-body p-0">
<div class="px-3 pt-2 pb-1 bg-light border-bottom d-flex align-items-center gap-2">
<strong class="small">ABDM Gateway Queue</strong>
<span class="badge bg-secondary" id="abdmQueueBadge">-</span>
<span class="text-muted small" id="abdmQueueStatus">Click "Fetch ABDM Queue" to load</span>
</div>
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr><th>Token</th><th>ABHA</th><th>Name</th><th>Status</th><th>Time</th><th>HMS Patient</th></tr>
</thead>
<tbody id="abdmQueueTbody"><tr><td colspan="6" class="text-muted px-2 py-2">Not loaded yet.</td></tr></tbody>
</table>
</div>
<div class="px-3 pt-2 pb-1 bg-light border-bottom border-top d-flex align-items-center gap-2 mt-1">
<strong class="small">HMS Direct OPD with ABHA</strong>
<span class="badge bg-success"><?= count($opd_book_rows ?? []) ?></span>
<span class="text-muted small">Last 7 days · patient has 14-digit ABHA</span>
</div>
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr><th>OPD ID</th><th>Patient</th><th>ABHA</th><th>Date</th><th>Doctor</th><th>Status</th></tr>
</thead>
<tbody>
<?php if (! empty($opd_book_rows ?? [])): ?>
<?php foreach (($opd_book_rows ?? []) as $r): ?>
<tr>
<td>#<?= (int) ($r['opd_id'] ?? 0) ?></td>
<td><?= esc((string) ($r['P_name'] ?? '')) ?></td>
<td><span class="text-primary small"><?= esc((string) ($r['abha_id'] ?? '')) ?></span></td>
<td><?= esc(substr((string) ($r['apointment_date'] ?? ''), 0, 16)) ?></td>
<td><?= esc((string) ($r['doc_name'] ?? '')) ?></td>
<td><span class="badge bg-<?= (int) ($r['opd_status'] ?? 0) >= 2 ? 'success' : 'warning text-dark' ?>"><?= (int) ($r['opd_status'] ?? 0) >= 2 ? 'Done' : 'Pending' ?></span></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="6" class="text-muted px-2 py-2">No OPD with ABHA found in last 7 days.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- OPD Consult Publish Card -->
<div class="card shadow-sm mt-3 d-none" id="opdConsultCard">
<div class="card-header py-2 d-flex align-items-center gap-2">
<strong>OPD Consult Publish — Done Appointments with ABHA</strong>
<span class="badge bg-success"><?= count($opd_consult_rows ?? []) ?></span>
<small class="text-muted ms-1">Last 30 days · opd_status=Done · ABHA linked</small>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr><th>OPD ID</th><th>Patient</th><th>ABHA</th><th>Consult Date</th><th>Doctor</th><th>Action</th></tr>
</thead>
<tbody>
<?php if (! empty($opd_consult_rows ?? [])): ?>
<?php foreach (($opd_consult_rows ?? []) as $r): ?>
<tr>
<td>#<?= (int) ($r['opd_id'] ?? 0) ?></td>
<td><?= esc((string) ($r['P_name'] ?? '')) ?></td>
<td><span class="text-primary small"><?= esc((string) ($r['abha_id'] ?? '')) ?></span></td>
<td><?= esc(substr((string) ($r['apointment_date'] ?? ''), 0, 16)) ?></td>
<td><?= esc((string) ($r['doc_name'] ?? '')) ?></td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary btn-opd-consult-fhir" data-opd-id="<?= (int) ($r['opd_id'] ?? 0) ?>">Preview FHIR</button>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="6" class="text-muted px-2 py-2">No done OPD with ABHA found in last 30 days.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="small text-muted mt-2" id="statusBox">Ready</div>
</div>
<!-- FHIR Preview Modal -->
<div class="modal fade" id="fhirPreviewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header py-2">
<div class="d-flex align-items-center gap-2 flex-wrap">
<h6 class="modal-title mb-0" id="fhirModalTitle">FHIR Preview</h6>
<span class="badge bg-secondary" id="fhirModalDataBadge">CHECKING</span>
<span class="badge bg-secondary" id="fhirModalHttpBadge">HTTP -</span>
</div>
<div class="d-flex gap-2 ms-auto flex-wrap">
<div class="dropdown">
<button type="button" class="btn btn-sm btn-outline-success dropdown-toggle" id="btnFhirUpdateCodes" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-pencil-square"></i> Update Codes
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Assign SNOMED / LOINC</h6></li>
<li>
<a class="dropdown-item" href="#" id="btnGoInvestMaster">
<i class="bi bi-clipboard2-pulse me-1"></i> Investigation Master <small class="text-muted">(LOINC codes)</small>
</a>
</li>
<li>
<a class="dropdown-item" href="#" id="btnGoClinicalMaster">
<i class="bi bi-heart-pulse me-1"></i> Clinical Master <small class="text-muted">(Complaints &amp; Diagnosis SNOMED)</small>
</a>
</li>
</ul>
</div>
<button type="button" class="btn btn-sm btn-outline-success" id="btnSubmitFhirToAbdm" title="Push FHIR bundle to ABDM bridge"><i class="bi bi-cloud-upload"></i> Submit to ABDM</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="btnCopyFhirModal">Copy JSON</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body p-0">
<div class="d-flex align-items-center gap-3 px-3 pt-2 pb-0 border-bottom">
<div class="flex-grow-1 small" id="fhirModalMeta">Loading...</div>
<ul class="nav nav-tabs border-0" id="fhirViewTabs" style="margin-bottom:-1px;">
<li class="nav-item">
<button class="nav-link active py-1 px-3" id="fhirTabForm" type="button">Form View</button>
</li>
<li class="nav-item">
<button class="nav-link py-1 px-3" id="fhirTabJson" type="button">JSON</button>
</li>
</ul>
</div>
<div class="px-3 pb-2 pt-1" id="fhirModalQuality" style="display:none;"></div>
<div id="fhirFormView" style="overflow-y:auto;max-height:62vh;padding:16px;"></div>
<pre id="fhirModalJson" style="display:none;margin:0;max-height:62vh;overflow:auto;background:#0b1020;color:#d9e2ff;padding:16px;border-radius:0;">{}</pre>
</div>
</div>
</div>
</div>
<div class="modal fade" id="taskActionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">ABDM Guided Action</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-2 small text-muted" id="modalTaskSummary"></div>
<label class="form-label">ABHA Number</label>
<input type="text" class="form-control" id="modalAbhaInput" maxlength="14" placeholder="Enter 14-digit ABHA">
<div class="form-text">ABHA validation is required before queueing the ABDM API action.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="modalConfirmBtn">Queue Action</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
(function () {
var csrfName = '<?= csrf_token() ?>';
var csrfHash = '<?= csrf_hash() ?>';
var refreshUrl = '<?= base_url('AbdmTaskBoard') ?>';
var statusBox = document.getElementById('statusBox');
var modalEl = document.getElementById('taskActionModal');
var actionModal = new bootstrap.Modal(modalEl);
var modalAbhaInput = document.getElementById('modalAbhaInput');
var modalTaskSummary = document.getElementById('modalTaskSummary');
var modalConfirmBtn = document.getElementById('modalConfirmBtn');
var sandboxCard = document.getElementById('abdmSandboxCard');
var sandboxContext = document.getElementById('abdmSandboxContext');
var sandboxStatus = document.getElementById('abdmSandboxStatus');
var sandboxAbhaInput = document.getElementById('tb_abha_id');
var sandboxQrPayloadInput = document.getElementById('tb_abdm_qr_payload');
var sandboxPurposeCodeInput = document.getElementById('tb_abdm_purpose_code');
var sandboxConsentExpiresInput = document.getElementById('tb_abdm_consent_expires_at');
var sandboxConsentHandleInput = document.getElementById('tb_abdm_consent_handle');
var sandboxClaimDocumentInput = document.getElementById('tb_abdm_claim_document_id');
var quickOpdInput = null;
var quickSessionInput = null;
var fhirDebugCard = document.getElementById('fhirDebugCard');
var fhirDebugTitle = document.getElementById('fhirDebugTitle');
var fhirDebugMeta = document.getElementById('fhirDebugMeta');
var fhirDebugJson = document.getElementById('fhirDebugJson');
var fhirDataBadge = document.getElementById('fhirDataBadge');
var fhirHttpBadge = document.getElementById('fhirHttpBadge');
var selectedAction = '';
var selectedRow = null;
var sandboxRow = null;
function setBadge(el, text, tone) {
if (!el) {
return;
}
el.classList.remove('bg-secondary', 'bg-success', 'bg-danger', 'bg-warning', 'text-dark');
if (tone === 'success') {
el.classList.add('bg-success');
} else if (tone === 'danger') {
el.classList.add('bg-danger');
} else if (tone === 'warning') {
el.classList.add('bg-warning', 'text-dark');
} else {
el.classList.add('bg-secondary');
}
el.textContent = text;
}
var opdBookCard = document.getElementById('opdBookCard');
var opdConsultCard = document.getElementById('opdConsultCard');
var taskTableCard = document.getElementById('taskTableCard');
function applyFilter(filter) {
// Hide dedicated section cards
if (opdBookCard) opdBookCard.classList.add('d-none');
if (opdConsultCard) opdConsultCard.classList.add('d-none');
if (filter === 'opd_book') {
if (taskTableCard) taskTableCard.classList.add('d-none');
if (opdBookCard) opdBookCard.classList.remove('d-none');
} else if (filter === 'opd_consult_publish') {
if (taskTableCard) taskTableCard.classList.add('d-none');
if (opdConsultCard) opdConsultCard.classList.remove('d-none');
} else {
if (taskTableCard) taskTableCard.classList.remove('d-none');
var rows = document.querySelectorAll('#taskTable tbody tr');
rows.forEach(function (row) {
var type = (row.getAttribute('data-task-type') || '').toLowerCase();
row.style.display = (type === filter.toLowerCase()) ? '' : 'none';
});
}
document.querySelectorAll('.filter-btn').forEach(function (btn) {
btn.classList.remove('btn-primary');
btn.classList.add('btn-outline-primary');
});
var active = document.querySelector('.filter-btn[data-filter="' + filter + '"]');
if (active) {
active.classList.remove('btn-outline-primary');
active.classList.add('btn-primary');
}
}
function showInlineFhirPreview(previewUrl, titleText) {
if (!previewUrl) {
setStatus('FHIR preview URL is missing.', true);
return;
}
if (fhirDebugTitle) {
fhirDebugTitle.textContent = titleText || 'FHIR JSON Debug';
}
if (fhirDebugMeta) {
fhirDebugMeta.textContent = 'Loading...';
fhirDebugMeta.className = 'small text-muted mb-2';
}
setBadge(fhirDataBadge, 'CHECKING', 'warning');
setBadge(fhirHttpBadge, 'HTTP ...', 'warning');
if (fhirDebugJson) {
fhirDebugJson.textContent = '{\n "loading": true\n}';
}
if (fhirDebugCard) {
fhirDebugCard.classList.remove('d-none');
fhirDebugCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
fetch(previewUrl, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
}).then(function (r) {
return r.text().then(function (body) {
return {
ok: r.ok,
status: r.status,
body: body
};
});
}).then(function (res) {
var parsed = null;
try {
parsed = JSON.parse(res.body || '{}');
} catch (e) {
parsed = { raw: res.body || '' };
}
var hasFhir = false;
if (parsed && typeof parsed === 'object') {
if (parsed.status === 'ok' && parsed.bundle && typeof parsed.bundle === 'object') {
hasFhir = true;
}
if (parsed.resourceType === 'Bundle') {
hasFhir = true;
}
}
if (fhirDebugMeta) {
if (res.ok) {
fhirDebugMeta.textContent = 'Loaded successfully (' + res.status + ')';
fhirDebugMeta.className = 'small text-success mb-2';
} else {
fhirDebugMeta.textContent = 'Loaded with error status (' + res.status + ')';
fhirDebugMeta.className = 'small text-danger mb-2';
}
}
setBadge(fhirHttpBadge, 'HTTP ' + res.status, res.ok ? 'success' : 'danger');
setBadge(fhirDataBadge, hasFhir ? 'HAS FHIR' : 'NOT GENERATED', hasFhir ? 'success' : 'danger');
if (fhirDebugJson) {
fhirDebugJson.textContent = JSON.stringify(parsed, null, 2);
}
}).catch(function (e) {
if (fhirDebugMeta) {
fhirDebugMeta.textContent = 'Request failed: ' + e.message;
fhirDebugMeta.className = 'small text-danger mb-2';
}
setBadge(fhirHttpBadge, 'HTTP ERR', 'danger');
setBadge(fhirDataBadge, 'NOT GENERATED', 'danger');
if (fhirDebugJson) {
fhirDebugJson.textContent = JSON.stringify({
error: e.message
}, null, 2);
}
});
}
function setStatus(msg, isError) {
statusBox.textContent = msg;
statusBox.style.color = isError ? '#dc3545' : '#6c757d';
}
function post(url, payload, cb) {
payload[csrfName] = csrfHash;
var body = Object.keys(payload).map(function (key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(payload[key]);
}).join('&');
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body
}).then(function (r) {
return r.json();
}).then(function (json) {
if (json && json.csrfHash) {
csrfHash = json.csrfHash;
}
cb(json || {});
}).catch(function (e) {
setStatus('Request failed: ' + e.message, true);
});
}
function setSandboxStatus(msg, level) {
if (!sandboxStatus) {
return;
}
sandboxStatus.classList.remove('text-muted', 'text-success', 'text-danger', 'text-warning');
if (level === 'success') {
sandboxStatus.classList.add('text-success');
} else if (level === 'danger') {
sandboxStatus.classList.add('text-danger');
} else if (level === 'warning') {
sandboxStatus.classList.add('text-warning');
} else {
sandboxStatus.classList.add('text-muted');
}
sandboxStatus.textContent = msg || 'Ready';
}
function currentSandboxContext() {
if (!sandboxRow) {
return null;
}
return {
taskId: parseInt(sandboxRow.getAttribute('data-task-id') || '0', 10) || 0,
patientId: parseInt(sandboxRow.getAttribute('data-patient-id') || '0', 10) || 0,
opdId: parseInt(sandboxRow.getAttribute('data-opd-id') || '0', 10) || 0,
opdSessionId: parseInt(sandboxRow.getAttribute('data-opd-session-id') || '0', 10) || 0,
abhaId: (sandboxAbhaInput.value || '').trim()
};
}
function openSandboxForRow(row) {
if (!row || !sandboxCard) {
return;
}
if ((row.getAttribute('data-sandbox-eligible') || '0') !== '1') {
setSandboxStatus('Sandbox actions are available only for OPD Publish rows with printed consult.', 'warning');
return;
}
sandboxRow = row;
var taskCode = '';
var patientName = '';
var taskCodeEl = row.querySelector('td:nth-child(2) strong');
var patientNameEl = row.querySelector('td:nth-child(3) div');
if (taskCodeEl) {
taskCode = taskCodeEl.textContent.trim();
}
if (patientNameEl) {
patientName = patientNameEl.textContent.trim();
}
var rowAbha = (row.getAttribute('data-abha-id') || '').trim();
sandboxAbhaInput.value = rowAbha;
sandboxQrPayloadInput.value = '';
sandboxPurposeCodeInput.value = sandboxPurposeCodeInput.value || 'CAREMGT';
sandboxConsentHandleInput.value = '';
sandboxClaimDocumentInput.value = '';
var opdId = row.getAttribute('data-opd-id') || '0';
var opdSessionId = row.getAttribute('data-opd-session-id') || '0';
sandboxContext.textContent = 'Task: ' + taskCode + ' | Patient: ' + patientName + ' | OPD: ' + opdId + ' | Session: ' + opdSessionId;
sandboxCard.classList.remove('d-none');
setSandboxStatus('Ready');
sandboxCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
function runAction(row, action) {
var taskId = row.getAttribute('data-task-id');
var abha = modalAbhaInput.value.trim();
setStatus('Submitting action...');
post('<?= base_url('AbdmTaskBoard/perform_action') ?>', {
task_id: taskId,
action: action,
abha_id: abha,
opd_session_id: ''
}, function (res) {
if (res.ok !== 1) {
setStatus(res.error_text || 'Action failed', true);
return;
}
var badge = row.querySelector('.status-pill');
if (badge) {
badge.textContent = 'IN_PROGRESS';
badge.className = 'badge bg-info status-pill';
}
setStatus('Action queued: ' + (res.event_type || '-'));
actionModal.hide();
});
}
document.querySelectorAll('.action-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
selectedRow = btn.closest('tr');
if (!selectedRow) {
return;
}
selectedAction = btn.getAttribute('data-action') || 'submit';
var abhaInput = selectedRow.querySelector('.abha-input');
var taskType = selectedRow.getAttribute('data-task-type') || '';
modalAbhaInput.value = abhaInput ? abhaInput.value.trim() : '';
modalTaskSummary.textContent = 'Task: ' + taskType + ' | Action: ' + selectedAction.toUpperCase();
actionModal.show();
});
});
document.querySelectorAll('.filter-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
applyFilter(btn.getAttribute('data-filter') || 'all');
});
});
document.querySelectorAll('.sandbox-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
openSandboxForRow(btn.closest('tr'));
});
});
document.querySelectorAll('.preview-fhir-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var row = btn.closest('tr');
if (!row) {
return;
}
var opdId = parseInt(row.getAttribute('data-opd-id') || '0', 10) || 0;
var opdSessionId = parseInt(row.getAttribute('data-opd-session-id') || '0', 10) || 0;
if (opdId <= 0) {
setStatus('FHIR preview unavailable for this row.', true);
return;
}
var previewUrl = opdSessionId > 0
? '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId + '/' + opdSessionId
: '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId;
showInlineFhirPreview(previewUrl, 'Task Row OPD ' + opdId + (opdSessionId > 0 ? (' / Session ' + opdSessionId) : ''));
});
});
document.querySelectorAll('.inline-json-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var previewUrl = (btn.getAttribute('data-preview-url') || '').trim();
var titleText = (btn.getAttribute('data-title') || 'FHIR JSON Debug').trim();
showInlineFhirPreview(previewUrl, titleText);
});
});
applyFilter('patient_abha_create');
modalConfirmBtn.addEventListener('click', function () {
if (!selectedRow || !selectedAction) {
return;
}
var abha = modalAbhaInput.value.trim();
if (!/^\d{14}$/.test(abha)) {
setStatus('ABHA must be a 14-digit number.', true);
return;
}
var rowInput = selectedRow.querySelector('.abha-input');
if (rowInput) {
rowInput.value = abha;
}
runAction(selectedRow, selectedAction);
});
document.querySelectorAll('.close-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var row = btn.closest('tr');
if (!row) {
return;
}
var taskId = row.getAttribute('data-task-id');
post('<?= base_url('AbdmTaskBoard/mark_status') ?>', {
task_id: taskId,
status: 'completed',
note: 'Closed from task board'
}, function (res) {
if (res.ok !== 1) {
setStatus(res.error_text || 'Close failed', true);
return;
}
if (sandboxRow === row && sandboxCard) {
sandboxCard.classList.add('d-none');
sandboxRow = null;
}
row.remove();
setStatus('Task closed');
});
});
});
document.getElementById('btnSandboxHide').addEventListener('click', function () {
if (sandboxCard) {
sandboxCard.classList.add('d-none');
}
sandboxRow = null;
});
document.getElementById('tb_btn_abdm_scan_lookup').addEventListener('click', function () {
var qrPayload = (sandboxQrPayloadInput.value || '').trim();
if (!qrPayload) {
setSandboxStatus('Paste QR payload first.', 'warning');
return;
}
setSandboxStatus('Queuing Scan & Share lookup...');
post('<?= base_url('AbdmGateway/scan_share_lookup') ?>', {
qr_payload: qrPayload
}, function (res) {
if (res.ok !== 1) {
setSandboxStatus(res.error_text || 'Scan lookup failed.', 'danger');
return;
}
if (res.abha_id_hint) {
sandboxAbhaInput.value = (res.abha_id_hint || '').toString().trim();
}
setSandboxStatus('Scan lookup queued. Queue ID: ' + (res.queue_id || '-'), 'success');
});
});
document.getElementById('tb_btn_abdm_validate_abha').addEventListener('click', function () {
var abha = (sandboxAbhaInput.value || '').trim();
if (!/^\d{14}$/.test(abha)) {
setSandboxStatus('ABHA must be a 14-digit number.', 'warning');
return;
}
setSandboxStatus('Queuing ABHA validation...');
post('<?= base_url('AbdmGateway/abha_validate') ?>', {
abha_id: abha
}, function (res) {
if (res.ok !== 1) {
setSandboxStatus(res.error_text || 'ABHA validation queue failed.', 'danger');
return;
}
setSandboxStatus('ABHA validation queued. Queue ID: ' + (res.queue_id || '-'), 'success');
});
});
document.getElementById('tb_btn_abdm_consent_request').addEventListener('click', function () {
var ctx = currentSandboxContext();
if (!ctx || ctx.patientId <= 0) {
setSandboxStatus('Select an eligible OPD Publish row first.', 'warning');
return;
}
if (!/^\d{14}$/.test(ctx.abhaId)) {
setSandboxStatus('ABHA must be a 14-digit number.', 'warning');
return;
}
setSandboxStatus('Requesting consent...');
post('<?= base_url('AbdmGateway/consent_request') ?>', {
patient_id: ctx.patientId,
abha_id: ctx.abhaId,
purpose_code: (sandboxPurposeCodeInput.value || '').trim(),
expires_at: (sandboxConsentExpiresInput.value || '').trim().replace('T', ' ')
}, function (res) {
if (res.ok !== 1) {
setSandboxStatus(res.error_text || 'Consent request failed.', 'danger');
return;
}
if (res.consent_handle) {
sandboxConsentHandleInput.value = (res.consent_handle || '').toString();
}
setSandboxStatus('Consent requested. Handle: ' + (res.consent_handle || '-'), 'success');
});
});
document.getElementById('tb_btn_abdm_share_fhir').addEventListener('click', function () {
var ctx = currentSandboxContext();
if (!ctx || ctx.patientId <= 0 || ctx.opdId <= 0) {
setSandboxStatus('Select an eligible OPD Publish row first.', 'warning');
return;
}
if (!/^\d{14}$/.test(ctx.abhaId)) {
setSandboxStatus('ABHA must be a 14-digit number.', 'warning');
return;
}
setSandboxStatus('Checking consent and queuing FHIR share...');
post('<?= base_url('AbdmGateway/share_prescription_bundle') ?>', {
opd_id: ctx.opdId,
opd_session_id: ctx.opdSessionId,
patient_id: ctx.patientId,
abha_id: ctx.abhaId,
consent_handle: (sandboxConsentHandleInput.value || '').trim()
}, function (res) {
if (res.ok !== 1) {
setSandboxStatus(res.error_text || 'FHIR share queue failed.', 'danger');
return;
}
if (res.consent_handle) {
sandboxConsentHandleInput.value = (res.consent_handle || '').toString();
}
setSandboxStatus('FHIR share queued. Queue ID: ' + (res.queue_id || '-'), 'success');
});
});
document.getElementById('tb_btn_abdm_preview_fhir').addEventListener('click', function () {
var ctx = currentSandboxContext();
if (!ctx || ctx.opdId <= 0) {
setSandboxStatus('Select an eligible OPD Publish row first.', 'warning');
return;
}
var previewUrl = ctx.opdSessionId > 0
? '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + ctx.opdId + '/' + ctx.opdSessionId
: '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + ctx.opdId;
window.open(previewUrl, '_blank');
setSandboxStatus('Opened FHIR preview for OPD ' + ctx.opdId + (ctx.opdSessionId > 0 ? (' / Session ' + ctx.opdSessionId) : ''), 'success');
});
document.getElementById('tb_btn_abdm_claim_status').addEventListener('click', function () {
var docId = parseInt((sandboxClaimDocumentInput.value || '0').toString(), 10) || 0;
if (docId <= 0) {
setSandboxStatus('Enter Claim Document ID first.', 'warning');
return;
}
setSandboxStatus('Queuing claim status request...');
post('<?= base_url('AbdmGateway/nhcx_claim_status_request') ?>', {
document_id: docId
}, function (res) {
if (res.ok !== 1) {
setSandboxStatus(res.error_text || 'Claim status queue failed.', 'danger');
return;
}
setSandboxStatus('Claim status queued. Queue ID: ' + (res.queue_id || '-'), 'success');
});
});
document.getElementById('btnHideFhirDebug').addEventListener('click', function () {
if (fhirDebugCard) {
fhirDebugCard.classList.add('d-none');
}
});
document.getElementById('btnCopyFhirDebug').addEventListener('click', function () {
var btn = this;
var originalText = btn.textContent;
var originalClass = btn.className;
var jsonText = fhirDebugJson ? (fhirDebugJson.textContent || '{}') : '{}';
if (!navigator.clipboard || !navigator.clipboard.writeText) {
setStatus('Clipboard API unavailable in this browser context.', true);
return;
}
navigator.clipboard.writeText(jsonText).then(function () {
setStatus('FHIR JSON copied to clipboard.');
btn.textContent = 'Copied ✓';
btn.className = 'btn btn-sm btn-success';
window.setTimeout(function () {
btn.textContent = originalText;
btn.className = originalClass;
}, 1500);
}).catch(function (e) {
setStatus('Copy failed: ' + e.message, true);
});
});
// ABDM Gateway Queue fetch for OPD Book
document.getElementById('btnFetchAbdmQueue').addEventListener('click', function () {
var date = (document.getElementById('opdBookDate').value || '').trim() || '<?= date('Y-m-d') ?>';
var statusEl = document.getElementById('abdmQueueStatus');
var badge = document.getElementById('abdmQueueBadge');
var tbody = document.getElementById('abdmQueueTbody');
if (statusEl) statusEl.textContent = 'Fetching...';
fetch('<?= base_url('AbdmOpdQueue/fetch') ?>?date=' + encodeURIComponent(date) + '&status=PENDING', {
method: 'GET',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
}).then(function (r) { return r.json(); }).then(function (data) {
var tokens = data.data || data.tokens || [];
if (badge) badge.textContent = tokens.length;
if (statusEl) statusEl.textContent = 'Loaded ' + tokens.length + ' token(s)';
if (! tbody) return;
if (! tokens.length) {
tbody.innerHTML = '<tr><td colspan="6" class="text-muted px-2 py-2">No PENDING tokens for ' + date + '.</td></tr>';
return;
}
tbody.innerHTML = tokens.map(function (t) {
var abha = (t.abha_id || t.abha_number || t.abhaId || '').toString().trim();
var name = (t.patient_name || t.name || t.patientName || '').toString();
var stat = (t.status || t.queue_status || '').toString();
var token = (t.token_number || t.tokenNumber || t.gateway_token_id || t.id || '').toString();
var time = (t.created_at || t.token_time || t.arrival_time || '').toString();
var hms = t.patient_id ? ('#' + t.patient_id + (t.p_fname ? ' ' + t.p_fname : '')) : '<span class="text-muted">—</span>';
return '<tr>' +
'<td>' + (token || '—') + '</td>' +
'<td><span class="text-primary small">' + (abha || '<span class="text-muted">—</span>') + '</span></td>' +
'<td>' + (name || '—') + '</td>' +
'<td><span class="badge bg-' + (stat === 'PENDING' ? 'warning text-dark' : 'secondary') + '">' + (stat || '—') + '</span></td>' +
'<td class="small">' + (time ? time.substring(0, 16) : '—') + '</td>' +
'<td>' + hms + '</td>' +
'</tr>';
}).join('');
}).catch(function (e) {
if (statusEl) statusEl.textContent = 'Fetch failed: ' + e.message;
});
});
// FHIR Preview Modal
var fhirPreviewModalEl = document.getElementById('fhirPreviewModal');
var fhirPreviewModal = new bootstrap.Modal(fhirPreviewModalEl);
var fhirModalTitle = document.getElementById('fhirModalTitle');
var fhirModalMeta = document.getElementById('fhirModalMeta');
var fhirModalQuality = document.getElementById('fhirModalQuality');
var fhirModalJson = document.getElementById('fhirModalJson');
var fhirFormView = document.getElementById('fhirFormView');
var fhirTabForm = document.getElementById('fhirTabForm');
var fhirTabJson = document.getElementById('fhirTabJson');
var fhirModalDataBadge = document.getElementById('fhirModalDataBadge');
var fhirModalHttpBadge = document.getElementById('fhirModalHttpBadge');
// ── Tab switching ──────────────────────────────────────────────────
fhirTabForm.addEventListener('click', function () {
fhirTabForm.classList.add('active');
fhirTabJson.classList.remove('active');
fhirFormView.style.display = '';
fhirModalJson.style.display = 'none';
});
fhirTabJson.addEventListener('click', function () {
fhirTabJson.classList.add('active');
fhirTabForm.classList.remove('active');
fhirModalJson.style.display = '';
fhirFormView.style.display = 'none';
});
// ── Track current bundle context for edits ──────────────────────
var _fhirOpdId = 0, _fhirSessionId = 0;
var _fhirLiveComplaints = [], _fhirLiveDiagnoses = [];
// ── Inline complaint SNOMED edit (event delegation on fhirFormView) ─
var _editTimer = null;
fhirFormView.addEventListener('click', function(e) {
var editBtn = e.target.closest('.fhir-complaint-edit');
var cancelBtn = e.target.closest('.fhir-complaint-cancel');
var saveBtn = e.target.closest('.fhir-complaint-save');
var ddItem = e.target.closest('.fhir-snomed-dd-item');
var diagEditBtn = e.target.closest('.fhir-diag-edit');
var diagCancelBtn = e.target.closest('.fhir-diag-cancel');
var diagSaveBtn = e.target.closest('.fhir-diag-save');
var diagDdItem = e.target.closest('.fhir-diag-dd-item');
if (editBtn) {
var panel = fhirFormView.querySelector('.fhir-edit-panel[data-idx="' + editBtn.dataset.idx + '"]');
if (!panel) return;
// Close others
fhirFormView.querySelectorAll('.fhir-edit-panel,.fhir-diag-panel').forEach(function(p) { if (p !== panel) p.style.display = 'none'; });
panel.style.display = panel.style.display === 'none' ? '' : 'none';
}
if (diagEditBtn) {
var dPanel = fhirFormView.querySelector('.fhir-diag-panel[data-didx="' + diagEditBtn.dataset.didx + '"]');
if (!dPanel) return;
fhirFormView.querySelectorAll('.fhir-edit-panel,.fhir-diag-panel').forEach(function(p) { if (p !== dPanel) p.style.display = 'none'; });
dPanel.style.display = dPanel.style.display === 'none' ? '' : 'none';
}
if (cancelBtn) {
cancelBtn.closest('.fhir-edit-panel').style.display = 'none';
}
if (diagCancelBtn) {
diagCancelBtn.closest('.fhir-diag-panel').style.display = 'none';
}
if (diagDdItem) {
var dPanel2 = diagDdItem.closest('.fhir-diag-panel');
dPanel2.querySelector('.fhir-diag-id').value = diagDdItem.dataset.id || '';
dPanel2.querySelector('.fhir-diag-term').value = diagDdItem.dataset.term || '';
dPanel2.querySelector('.fhir-diag-search').value = diagDdItem.dataset.term || '';
dPanel2.querySelector('.fhir-diag-dd').style.display = 'none';
var dSel = dPanel2.querySelector('.fhir-diag-selected');
dSel.textContent = '\u2713 ' + (diagDdItem.dataset.id || '') + ' \u2014 ' + (diagDdItem.dataset.term || '');
dSel.style.display = '';
}
if (ddItem) {
var panel2 = ddItem.closest('.fhir-edit-panel');
panel2.querySelector('.fhir-snomed-id').value = ddItem.dataset.id || '';
panel2.querySelector('.fhir-snomed-term').value = ddItem.dataset.term || '';
panel2.querySelector('.fhir-snomed-search').value = ddItem.dataset.term || '';
panel2.querySelector('.fhir-snomed-dd').style.display = 'none';
var sel = panel2.querySelector('.fhir-snomed-selected');
sel.textContent = '✓ ' + (ddItem.dataset.id || '') + ' — ' + (ddItem.dataset.term || '');
sel.style.display = '';
}
if (saveBtn) {
var panel3 = saveBtn.closest('.fhir-edit-panel');
var sId = panel3.querySelector('.fhir-snomed-id').value.trim();
var sTerm = panel3.querySelector('.fhir-snomed-term').value.trim();
var cText = panel3.dataset.complaintText || '';
if (!sId && !sTerm) {
// Try the search field text as the term if nothing selected
sTerm = panel3.querySelector('.fhir-snomed-search').value.trim();
}
var csrfN = document.querySelector('meta[name="csrf-token-name"]');
var csrfH = document.querySelector('meta[name="csrf-token-hash"]');
var body = new URLSearchParams({
opd_id: _fhirOpdId,
opd_session_id: _fhirSessionId,
complaint_text: cText,
snomed_concept_id: sId,
snomed_term: sTerm
});
body.append(csrfName, csrfHash);
saveBtn.disabled = true;
saveBtn.textContent = 'Saving…';
fetch('<?= base_url('Opd_prescription/fhir_complaint_recode') ?>', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
})
.then(function(r){ return r.json(); })
.then(function(res){
if (res.update == 1) {
// Update the CSRF token for next request
if (res.csrfName && res.csrfHash) { csrfName = res.csrfName; csrfHash = res.csrfHash; }
// Update the displayed complaint badge inline
var li = panel3.closest('li');
if (li && sId) {
var existing = li.querySelector('.fhir-snomed-badge');
if (existing) { existing.textContent = 'SNOMED\u00a0' + sId; }
else {
var badge = document.createElement('span');
badge.className = 'badge bg-info text-dark ms-1 fhir-snomed-badge';
badge.style.fontSize = '.7rem';
badge.textContent = 'SNOMED\u00a0' + sId;
var strong = li.querySelector('strong');
if (strong) strong.insertAdjacentElement('afterend', badge);
}
}
panel3.style.display = 'none';
} else {
alert(res.error_text || 'Save failed');
}
})
.catch(function(e){ alert('Error: ' + e.message); })
.finally(function(){ saveBtn.disabled = false; saveBtn.textContent = 'Save'; });
}
if (diagSaveBtn) {
var dPanel3 = diagSaveBtn.closest('.fhir-diag-panel');
var dId = dPanel3.querySelector('.fhir-diag-id').value.trim();
var dTerm = dPanel3.querySelector('.fhir-diag-term').value.trim();
var dText = dPanel3.dataset.diagText || '';
var dType = dPanel3.dataset.diagType || 'provisional';
if (!dId && !dTerm) { dTerm = dPanel3.querySelector('.fhir-diag-search').value.trim(); }
var dBody = new URLSearchParams({
opd_id: _fhirOpdId,
opd_session_id: _fhirSessionId,
diagnosis_type: dType,
diagnosis_text: dText,
snomed_concept_id: dId,
snomed_term: dTerm
});
dBody.append(csrfName, csrfHash);
diagSaveBtn.disabled = true;
diagSaveBtn.textContent = 'Saving\u2026';
fetch('<?= base_url('Opd_prescription/fhir_diagnosis_recode') ?>', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
body: dBody.toString()
})
.then(function(r){ return r.json(); })
.then(function(res){
if (res.update == 1) {
if (res.csrfName && res.csrfHash) { csrfName = res.csrfName; csrfHash = res.csrfHash; }
var dLi = dPanel3.closest('li');
if (dLi && dId) {
var dExisting = dLi.querySelector('.fhir-diag-snomed-badge');
if (dExisting) { dExisting.textContent = 'SNOMED\u00a0' + dId; }
else {
var dBadge = document.createElement('span');
dBadge.className = 'badge bg-success ms-1 fhir-diag-snomed-badge';
dBadge.style.fontSize = '.7rem';
dBadge.textContent = 'SNOMED\u00a0' + dId;
var dStrong = dLi.querySelector('strong');
if (dStrong) dStrong.insertAdjacentElement('afterend', dBadge);
}
}
dPanel3.style.display = 'none';
} else {
alert(res.error_text || 'Save failed');
}
})
.catch(function(e){ alert('Error: ' + e.message); })
.finally(function(){ diagSaveBtn.disabled = false; diagSaveBtn.textContent = 'Save'; });
}
});
fhirFormView.addEventListener('input', function(e) {
var searchInput = e.target.closest('.fhir-snomed-search');
var diagSearch = e.target.closest('.fhir-diag-search');
if (!searchInput && !diagSearch) return;
if (diagSearch) {
var dPanel4 = diagSearch.closest('.fhir-diag-panel');
var dDd = dPanel4.querySelector('.fhir-diag-dd');
var dQ = diagSearch.value.trim();
if (dQ.length < 2) { dDd.style.display = 'none'; return; }
clearTimeout(_editTimer);
_editTimer = setTimeout(function() {
fetch('<?= base_url('Opd_prescription/complaints_search') ?>?q=' + encodeURIComponent(dQ), {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r){ return r.json(); })
.then(function(res){
dDd.innerHTML = '';
var rows = (res.rows || res.results || []);
if (!rows.length) { dDd.style.display = 'none'; return; }
rows.slice(0, 10).forEach(function(row) {
var id = row.concept_id || row.snomed_concept_id || '';
var name = row.name || row.term || row.snomed_term || '';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'list-group-item list-group-item-action py-1 px-2 fhir-diag-dd-item';
btn.dataset.id = id;
btn.dataset.term = name;
btn.innerHTML = '<small>' + hesc(name) + (id ? ' <span class="text-muted">' + hesc(id) + '</span>' : '') + '</small>';
dDd.appendChild(btn);
});
dDd.style.display = '';
})
.catch(function(){ dDd.style.display = 'none'; });
}, 280);
return;
}
if (!searchInput) return;
var panel = searchInput.closest('.fhir-edit-panel');
var dd = panel.querySelector('.fhir-snomed-dd');
var q = searchInput.value.trim();
if (q.length < 2) { dd.style.display = 'none'; return; }
clearTimeout(_editTimer);
_editTimer = setTimeout(function() {
fetch('<?= base_url('Opd_prescription/complaints_search') ?>?q=' + encodeURIComponent(q), {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r){ return r.json(); })
.then(function(res){
dd.innerHTML = '';
var rows = (res.rows || res.results || []);
if (!rows.length) { dd.style.display = 'none'; return; }
rows.slice(0, 10).forEach(function(row) {
var id = row.concept_id || row.snomed_concept_id || '';
var name = row.name || row.term || row.snomed_term || '';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'list-group-item list-group-item-action py-1 px-2 fhir-snomed-dd-item';
btn.dataset.id = id;
btn.dataset.term = name;
btn.innerHTML = '<small>' + hesc(name) + (id ? ' <span class="text-muted">' + hesc(id) + '</span>' : '') + '</small>';
dd.appendChild(btn);
});
dd.style.display = '';
})
.catch(function(){ dd.style.display = 'none'; });
}, 280);
});
fhirFormView.addEventListener('focusout', function(e) {
var inPanel = e.target.closest('.fhir-edit-panel');
var inDPanel = e.target.closest('.fhir-diag-panel');
if (!inPanel && !inDPanel) return;
setTimeout(function() {
if (inPanel) { var d = inPanel.querySelector('.fhir-snomed-dd'); if (d) d.style.display = 'none'; }
if (inDPanel) { var dd2 = inDPanel.querySelector('.fhir-diag-dd'); if (dd2) dd2.style.display = 'none'; }
}, 200);
});
// ── Arrow-key / Enter navigation for SNOMED dropdowns ─────────────
fhirFormView.addEventListener('keydown', function(e) {
var key = e.key;
if (key !== 'ArrowDown' && key !== 'ArrowUp' && key !== 'Enter' && key !== 'Escape') return;
var searchInput = e.target.closest('.fhir-snomed-search, .fhir-diag-search');
var ddItem = e.target.closest('.fhir-snomed-dd-item, .fhir-diag-dd-item');
if (!searchInput && !ddItem) return;
// Determine which dropdown container we're in
var panel = e.target.closest('.fhir-edit-panel, .fhir-diag-panel');
if (!panel) return;
var dd = panel.querySelector('.fhir-snomed-dd, .fhir-diag-dd');
if (!dd) return;
var input = panel.querySelector('.fhir-snomed-search, .fhir-diag-search');
var items = Array.prototype.slice.call(dd.querySelectorAll('.list-group-item'));
if (!items.length && key !== 'Escape') return;
if (key === 'Escape') {
dd.style.display = 'none';
if (input) input.focus();
e.preventDefault();
return;
}
if (key === 'Enter' && ddItem) {
e.preventDefault();
ddItem.click();
return;
}
if (key === 'Enter' && searchInput) {
// Select first visible item on Enter if dropdown open
if (dd.style.display !== 'none' && items.length) {
e.preventDefault();
items[0].click();
}
return;
}
if (key === 'ArrowDown') {
e.preventDefault();
if (searchInput) {
// From input: focus first item
if (dd.style.display !== 'none' && items.length) { items[0].focus(); }
} else if (ddItem) {
var idx = items.indexOf(ddItem);
if (idx < items.length - 1) { items[idx + 1].focus(); } else { items[0].focus(); }
}
}
if (key === 'ArrowUp') {
e.preventDefault();
if (ddItem) {
var idx2 = items.indexOf(ddItem);
if (idx2 > 0) { items[idx2 - 1].focus(); } else { if (input) input.focus(); }
}
}
});
// ── Render FHIR bundle as human-readable form ──────────────────────
function hesc(s) {
return String(s || '')
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function fmtDate(s) {
if (!s) return '';
var d = s.toString().substring(0, 10).split('-');
return d.length === 3 ? d[2]+'/'+d[1]+'/'+d[0] : s.toString().substring(0,16);
}
function renderFhirBundleForm(bundle, liveComplaints, liveDiagnoses) {
liveComplaints = liveComplaints || [];
liveDiagnoses = liveDiagnoses || [];
var resources = (bundle.entry || []).map(function(e) { return e.resource; }).filter(Boolean);
function byType(t) { return resources.filter(function(r){ return r.resourceType === t; }); }
var patient = byType('Patient')[0];
var encounter = byType('Encounter')[0];
var practitioner = byType('Practitioner')[0];
var organization = byType('Organization')[0];
var allConditions = byType('Condition');
var observations = byType('Observation');
var medications = byType('MedicationRequest');
var serviceReqs = byType('ServiceRequest');
var complaints = allConditions.filter(function(c) {
return (c.category || []).some(function(cat) { return (cat.text || '').toLowerCase().includes('complaint'); });
});
var diagnoses = allConditions.filter(function(c) {
return !(c.category || []).some(function(cat) { return (cat.text || '').toLowerCase().includes('complaint'); });
});
var html = '';
// ── Patient header
if (patient) {
var pname = ((patient.name || [{}])[0].text || '').trim();
var gender = (patient.gender || '').toUpperCase().charAt(0);
var dob = fmtDate(patient.birthDate);
var abha = (patient.identifier || []).find(function(i){ return (i.system||'').includes('abha'); });
var docName = practitioner ? ((practitioner.name || [{}])[0].text || '') : '';
var orgName = organization ? (organization.name || '') : '';
var encStart = encounter ? fmtDate((encounter.period || {}).start || '') : '';
html += '<div class="card border-primary mb-3">';
html += '<div class="card-header bg-primary text-white py-2 d-flex flex-wrap gap-2 align-items-center">';
html += '<strong class="fs-6">' + hesc(pname || 'Unknown Patient') + '</strong>';
if (gender) html += '<span class="badge bg-white text-primary">' + hesc(gender) + '</span>';
if (dob) html += '<small class="opacity-75">DOB: ' + hesc(dob) + '</small>';
if (abha) html += '<span class="badge bg-warning text-dark ms-auto">ABHA: ' + hesc(abha.value) + '</span>';
html += '</div>';
html += '<div class="card-body py-2 d-flex flex-wrap gap-3 small">';
if (docName) html += '<span><i class="bi bi-person-badge me-1"></i><strong>' + hesc(docName) + '</strong></span>';
if (encStart) html += '<span><i class="bi bi-calendar2 me-1"></i>' + hesc(encStart) + '</span>';
if (orgName) html += '<span><i class="bi bi-hospital me-1"></i>' + hesc(orgName) + '</span>';
html += '</div></div>';
}
// ── Complaints
if (complaints.length) {
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Chief Complaints</small></div><div class="card-body py-2">';
html += '<ul class="mb-0 ps-3">';
complaints.forEach(function(c, idx) {
var codeText = (c.code || {}).text || '';
var coding = (((c.code || {}).coding) || [])[0] || {};
var snomed = coding.code || '';
var display = coding.display || codeText;
var sev = ((c.severity || {}).coding || [{}])[0];
var sevText = (sev && sev.display) ? sev.display : ((c.severity || {}).text || '');
var note = ((c.note || [{}])[0] || {}).text || '';
// Resolve doctor's original text from live complaint_snomed_json
var doctorText = codeText;
var liveItem = null;
if (snomed && liveComplaints.length) {
for (var li = 0; li < liveComplaints.length; li++) {
if ((liveComplaints[li].concept_id || '') === snomed) { liveItem = liveComplaints[li]; break; }
}
}
if (!liveItem && liveComplaints.length > idx) { liveItem = liveComplaints[idx]; }
if (liveItem) { doctorText = liveItem.original_term || liveItem.term || codeText; }
html += '<li class="mb-2">';
html += '<div class="d-flex align-items-start gap-2">';
html += '<div class="flex-grow-1">';
html += '<strong>' + hesc(doctorText) + '</strong>';
if (snomed) html += ' <span class="badge bg-info text-dark fhir-snomed-badge" style="font-size:.7rem;">SNOMED&nbsp;' + hesc(snomed) + '</span>';
if (sevText) html += ' <span class="badge bg-secondary" style="font-size:.7rem;">' + hesc(sevText) + '</span>';
if (note) html += ' <small class="text-muted">(' + hesc(note) + ')</small>';
if (display && display !== doctorText) html += ' <small class="text-secondary d-block">SNOMED: ' + hesc(display) + '</small>';
html += '</div>';
html += '<button type="button" class="btn btn-outline-secondary fhir-complaint-edit flex-shrink-0" data-idx="' + idx + '" style="font-size:.7rem;padding:1px 6px;">Edit</button>';
html += '</div>';
// inline edit panel
html += '<div class="fhir-edit-panel border rounded p-2 mt-1 bg-light" data-idx="' + idx + '" data-complaint-text="' + hesc(doctorText) + '" style="display:none;">';
html += '<div class="mb-1 small text-muted">Doctor wrote: <em>' + hesc(doctorText) + '</em> &mdash; assign SNOMED code:</div>';
html += '<div class="position-relative mb-1">';
html += '<input type="text" class="form-control form-control-sm fhir-snomed-search" placeholder="Type to search SNOMED…" value="' + hesc(doctorText) + '">';
html += '<div class="list-group shadow position-absolute w-100 fhir-snomed-dd" style="z-index:2000;display:none;max-height:180px;overflow-y:auto;"></div>';
html += '</div>';
html += '<div class="fhir-snomed-selected small text-success mb-1" style="display:none;"></div>';
html += '<input type="hidden" class="fhir-snomed-id" value="' + hesc(snomed) + '">';
html += '<input type="hidden" class="fhir-snomed-term" value="' + hesc(doctorText) + '">';
html += '<div class="d-flex gap-2"><button type="button" class="btn btn-sm btn-primary fhir-complaint-save">Save</button>';
html += '<button type="button" class="btn btn-sm btn-outline-secondary fhir-complaint-cancel">Cancel</button></div>';
html += '</div>';
html += '</li>';
});
html += '</ul></div></div>';
}
// ── Diagnoses
if (diagnoses.length) {
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Diagnosis</small></div><div class="card-body py-2">';
html += '<ul class="mb-0 ps-3">';
diagnoses.forEach(function(c, didx) {
var text = (c.code || {}).text || '';
var coding = (((c.code || {}).coding) || [])[0] || {};
var snomed = coding.code || '';
var display = coding.display || text;
var verif = (((c.verificationStatus || {}).coding) || [])[0] || {};
var isProvisional = (verif.code || 'provisional') !== 'confirmed';
var diagType = isProvisional ? 'provisional' : 'confirmed';
// Resolve doctor's original text from live diagnosis data
var doctorDiagText = text;
var liveDiag = null;
if (liveDiagnoses.length) {
for (var ldi = 0; ldi < liveDiagnoses.length; ldi++) {
if ((liveDiagnoses[ldi].type || '') === diagType) { liveDiag = liveDiagnoses[ldi]; break; }
}
if (!liveDiag && liveDiagnoses.length > didx) { liveDiag = liveDiagnoses[didx]; }
}
if (liveDiag) { doctorDiagText = liveDiag.term || text; }
html += '<li class="mb-2">';
html += '<div class="d-flex align-items-start gap-2">';
html += '<div class="flex-grow-1">';
html += '<strong>' + hesc(doctorDiagText) + '</strong>';
if (snomed) html += ' <span class="badge bg-success fhir-diag-snomed-badge" style="font-size:.7rem;">SNOMED&nbsp;' + hesc(snomed) + '</span>';
html += ' <span class="badge ' + (isProvisional ? 'bg-warning text-dark' : 'bg-success-subtle text-success-emphasis border border-success') + '" style="font-size:.7rem;">' + (isProvisional ? 'Provisional' : 'Confirmed') + '</span>';
if (display && display !== doctorDiagText) html += ' <small class="text-secondary d-block">SNOMED: ' + hesc(display) + '</small>';
html += '</div>';
html += '<button type="button" class="btn btn-outline-secondary fhir-diag-edit flex-shrink-0" data-didx="' + didx + '" style="font-size:.7rem;padding:1px 6px;">Edit</button>';
html += '</div>';
html += '<div class="fhir-diag-panel border rounded p-2 mt-1 bg-light" data-didx="' + didx + '" data-diag-text="' + hesc(doctorDiagText) + '" data-diag-type="' + hesc(diagType) + '" style="display:none;">';
html += '<div class="mb-1 small text-muted">Doctor wrote: <em>' + hesc(doctorDiagText) + '</em> &mdash; assign SNOMED code:</div>';
html += '<div class="position-relative mb-1">';
html += '<input type="text" class="form-control form-control-sm fhir-diag-search" placeholder="Type to search SNOMED\u2026" value="' + hesc(doctorDiagText) + '">';
html += '<div class="list-group shadow position-absolute w-100 fhir-diag-dd" style="z-index:2000;display:none;max-height:180px;overflow-y:auto;"></div>';
html += '</div>';
html += '<div class="fhir-diag-selected small text-success mb-1" style="display:none;"></div>';
html += '<input type="hidden" class="fhir-diag-id" value="' + hesc(snomed) + '">';
html += '<input type="hidden" class="fhir-diag-term" value="' + hesc(display || doctorDiagText) + '">';
html += '<div class="d-flex gap-2"><button type="button" class="btn btn-sm btn-primary fhir-diag-save">Save</button>';
html += '<button type="button" class="btn btn-sm btn-outline-secondary fhir-diag-cancel">Cancel</button></div>';
html += '</div>';
html += '</li>';
});
html += '</ul></div></div>';
}
// ── Vitals
if (observations.length) {
var loincLabel = { '8867-4':'HR','59408-5':'SpO₂','8480-6':'BP Sys','8462-4':'BP Dia','8310-5':'Temp','9279-1':'RR','8302-2':'Height','29463-7':'Weight' };
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Vitals</small></div><div class="card-body py-2"><div class="d-flex flex-wrap gap-2">';
observations.forEach(function(o) {
var loinc = (((o.code || {}).coding) || [])[0] || {};
var label = loincLabel[loinc.code] || (o.code || {}).text || loinc.display || '';
var val = (o.valueQuantity || {}).value;
var unit = (o.valueQuantity || {}).unit || '';
if (val === undefined || val === null) return;
html += '<div class="border rounded px-2 py-1 text-center" style="min-width:80px;">';
html += '<div class="small text-muted" style="font-size:.7rem;">' + hesc(label) + '</div>';
html += '<div class="fw-bold">' + hesc(val) + '</div>';
html += '<div class="text-muted" style="font-size:.7rem;">' + hesc(unit);
if (loinc.code) html += ' <span class="text-secondary" style="font-size:.65rem;">'+hesc(loinc.code)+'</span>';
html += '</div></div>';
});
html += '</div></div></div>';
}
// ── Medications
if (medications.length) {
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Medications</small></div><div class="card-body py-2">';
html += '<ol class="mb-0 ps-3">';
medications.forEach(function(m) {
var drug = (m.medicationCodeableConcept || {}).text || '';
var di = (m.dosageInstruction || [{}])[0] || {};
var dosage = di.text || '';
var method = (di.method || {}).text || '';
html += '<li class="mb-1"><strong>' + hesc(drug) + '</strong>';
if (dosage) html += ' <span class="text-muted small">— ' + hesc(dosage) + (method ? ' (' + hesc(method) + ')' : '') + '</span>';
html += '</li>';
});
html += '</ol></div></div>';
}
// ── Investigations
if (serviceReqs.length) {
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Investigations</small></div><div class="card-body py-2">';
html += '<ul class="mb-0 ps-3">';
serviceReqs.forEach(function(s) {
var text = (s.code || {}).text || '';
var loinc = (((s.code || {}).coding) || [])[0] || {};
html += '<li class="mb-1"><strong>' + hesc(text) + '</strong>';
if (loinc.code) html += ' <span class="badge bg-info text-dark" style="font-size:.7rem;">LOINC&nbsp;' + hesc(loinc.code) + '</span>';
html += '</li>';
});
html += '</ul></div></div>';
}
return html || '<p class="text-muted p-3">No structured clinical data found in bundle.</p>';
}
function fhirCodeQuality(bundle) {
var entries = (bundle && bundle.entry) ? bundle.entry : [];
var complaints = [], diagnoses = [], services = [], obs = [], meds = [];
entries.forEach(function(e) {
var r = e.resource;
if (!r) return;
if (r.resourceType === 'Observation') { obs.push(r); return; }
if (r.resourceType === 'MedicationRequest') { meds.push(r); return; }
if (r.resourceType === 'ServiceRequest') { services.push(r); return; }
if (r.resourceType === 'Condition') {
var cats = r.category || [];
var isComplaint = cats.some(function(c) { return (c.text || '').toLowerCase().includes('complaint'); });
if (isComplaint) complaints.push(r); else diagnoses.push(r);
}
});
function hasSnomedCoding(r) {
return (((r.code || {}).coding) || []).some(function(c) { return c.code && c.code !== ''; });
}
function hasLoincCoding(r) {
return (((r.code || {}).coding) || []).some(function(c) { return (c.system || '').includes('loinc') && c.code; });
}
var items = [];
if (complaints.length) {
var n = complaints.filter(hasSnomedCoding).length;
items.push('<span class="badge ' + (n === complaints.length ? 'bg-success' : 'bg-warning text-dark') + ' me-1">Complaints ' + n + '/' + complaints.length + ' SNOMED</span>');
}
if (diagnoses.length) {
var n = diagnoses.filter(hasSnomedCoding).length;
items.push('<span class="badge ' + (n === diagnoses.length ? 'bg-success' : 'bg-warning text-dark') + ' me-1">Diagnoses ' + n + '/' + diagnoses.length + ' SNOMED</span>');
}
if (obs.length) {
items.push('<span class="badge bg-success me-1">Vitals ' + obs.length + ' LOINC</span>');
}
if (services.length) {
var n = services.filter(hasLoincCoding).length;
items.push('<span class="badge ' + (n === services.length ? 'bg-success' : 'bg-warning text-dark') + ' me-1">Investigations ' + n + '/' + services.length + ' LOINC</span>');
}
if (meds.length) {
items.push('<span class="badge bg-secondary me-1">Medications ' + meds.length + '</span>');
}
return items.length ? '<small class="text-muted me-1">Code quality:</small>' + items.join('') : '';
}
function openFhirModal(url, title) {
_fhirOpdId = 0; _fhirSessionId = 0;
if (fhirModalTitle) fhirModalTitle.textContent = title || 'FHIR Preview';
if (fhirModalMeta) { fhirModalMeta.textContent = 'Loading...'; fhirModalMeta.className = 'flex-grow-1 small text-muted'; }
if (fhirModalQuality) { fhirModalQuality.innerHTML = ''; fhirModalQuality.style.display = 'none'; }
if (fhirFormView) fhirFormView.innerHTML = '<p class="text-muted p-3">Loading...</p>';
if (fhirModalJson) fhirModalJson.textContent = '{\n "loading": true\n}';
// Reset to Form View tab
fhirTabForm.classList.add('active'); fhirTabJson.classList.remove('active');
fhirFormView.style.display = ''; fhirModalJson.style.display = 'none';
if (fhirModalDataBadge) { fhirModalDataBadge.className = 'badge bg-warning text-dark'; fhirModalDataBadge.textContent = 'CHECKING'; }
if (fhirModalHttpBadge) { fhirModalHttpBadge.className = 'badge bg-warning text-dark'; fhirModalHttpBadge.textContent = 'HTTP ...'; }
fhirPreviewModal.show();
fetch(url, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } })
.then(function (r) { return r.text().then(function (b) { return { ok: r.ok, status: r.status, body: b }; }); })
.then(function (res) {
var parsed = null;
try { parsed = JSON.parse(res.body || '{}'); } catch (e) { parsed = { raw: res.body || '' }; }
var bundle = (parsed && parsed.resourceType === 'Bundle') ? parsed : ((parsed && parsed.bundle) ? parsed.bundle : null);
var hasFhir = bundle !== null;
_fhirOpdId = parseInt(parsed.opd_id || '0', 10) || 0;
_fhirSessionId = parseInt(parsed.opd_session_id || '0', 10) || 0;
_fhirLiveComplaints = Array.isArray(parsed.live_complaints) ? parsed.live_complaints : [];
_fhirLiveDiagnoses = Array.isArray(parsed.live_diagnoses) ? parsed.live_diagnoses : [];
if (fhirModalJson) fhirModalJson.textContent = JSON.stringify(parsed, null, 2);
if (fhirFormView) fhirFormView.innerHTML = hasFhir ? renderFhirBundleForm(bundle, _fhirLiveComplaints, _fhirLiveDiagnoses) : '<p class="text-muted p-3">No FHIR bundle generated yet.</p>';
if (fhirModalMeta) { fhirModalMeta.textContent = res.ok ? 'Loaded (' + res.status + ')' : 'Error (' + res.status + ')'; fhirModalMeta.className = 'flex-grow-1 small ' + (res.ok ? 'text-success' : 'text-danger'); }
if (fhirModalDataBadge) { fhirModalDataBadge.className = 'badge ' + (hasFhir ? 'bg-success' : 'bg-danger'); fhirModalDataBadge.textContent = hasFhir ? 'HAS FHIR' : 'NOT GENERATED'; }
if (fhirModalHttpBadge) { fhirModalHttpBadge.className = 'badge ' + (res.ok ? 'bg-success' : 'bg-danger'); fhirModalHttpBadge.textContent = 'HTTP ' + res.status; }
if (fhirModalQuality && hasFhir) {
var qs = fhirCodeQuality(bundle);
if (qs) { fhirModalQuality.innerHTML = qs; fhirModalQuality.style.display = 'block'; }
}
})
.catch(function (e) {
if (fhirFormView) fhirFormView.innerHTML = '<p class="text-danger p-3">Request failed: ' + hesc(e.message) + '</p>';
if (fhirModalMeta) { fhirModalMeta.textContent = 'Request failed: ' + e.message; fhirModalMeta.className = 'flex-grow-1 small text-danger'; }
if (fhirModalDataBadge) { fhirModalDataBadge.className = 'badge bg-danger'; fhirModalDataBadge.textContent = 'ERROR'; }
if (fhirModalHttpBadge) { fhirModalHttpBadge.className = 'badge bg-danger'; fhirModalHttpBadge.textContent = 'HTTP ERR'; }
});
}
// Update Codes dropdown — navigate to master workspaces
(function () {
function navTo(url, label) {
fhirPreviewModal.hide();
if (typeof window.load_form === 'function') {
window.load_form(url, label);
} else {
window.location.href = url;
}
}
var btnInvest = document.getElementById('btnGoInvestMaster');
var btnClinical = document.getElementById('btnGoClinicalMaster');
if (btnInvest) btnInvest.addEventListener('click', function(e) {
e.preventDefault();
navTo('<?= base_url('Opd_prescription/opd_invest_master') ?>', 'Investigation Master');
});
if (btnClinical) btnClinical.addEventListener('click', function(e) {
e.preventDefault();
navTo('<?= base_url('Opd_prescription/clinical_master_workspace') ?>', 'Clinical Master');
});
})();
document.getElementById('btnSubmitFhirToAbdm').addEventListener('click', function () {
var btn = this;
if (_fhirOpdId <= 0) { alert('No FHIR bundle loaded.'); return; }
if (!confirm('Submit FHIR bundle for OPD #' + _fhirOpdId + ' to ABDM bridge?')) return;
var origHtml = btn.innerHTML;
btn.disabled = true;
btn.textContent = 'Submitting…';
var body = new URLSearchParams({ opd_id: _fhirOpdId, opd_session_id: _fhirSessionId });
body.append(csrfName, csrfHash);
fetch('<?= base_url('Opd_prescription/fhir_bundle_submit') ?>', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
})
.then(function (r) { return r.json(); })
.then(function (res) {
if (res.csrfName && res.csrfHash) { csrfName = res.csrfName; csrfHash = res.csrfHash; }
if (res.ok == 1) {
btn.className = 'btn btn-sm btn-success';
btn.innerHTML = '<i class="bi bi-check-circle"></i> Submitted ✓';
var qInfo = res.queue_id ? ' Queue ID: ' + res.queue_id : '';
alert('✅ Bundle submitted to ABDM bridge.\n' + (res.message || '') + qInfo);
} else {
btn.disabled = false;
btn.innerHTML = origHtml;
alert('❌ Submission failed:\n' + (res.message || res.error_text || 'Check bridge configuration.') + (res.http_code ? ' (HTTP ' + res.http_code + ')' : ''));
}
})
.catch(function (e) {
btn.disabled = false;
btn.innerHTML = origHtml;
alert('❌ Error: ' + e.message);
});
});
document.getElementById('btnCopyFhirModal').addEventListener('click', function () {
var btn = this;
var jsonText = fhirModalJson ? (fhirModalJson.textContent || '{}') : '{}';
if (! navigator.clipboard || ! navigator.clipboard.writeText) { setStatus('Clipboard API unavailable.', true); return; }
navigator.clipboard.writeText(jsonText).then(function () {
var orig = btn.textContent; btn.textContent = 'Copied ✓'; btn.className = 'btn btn-sm btn-success';
window.setTimeout(function () { btn.textContent = orig; btn.className = 'btn btn-sm btn-outline-secondary'; }, 1500);
}).catch(function (e) { setStatus('Copy failed: ' + e.message, true); });
});
// OPD Consult Publish — FHIR preview buttons → open in modal
document.querySelectorAll('.btn-opd-consult-fhir').forEach(function (btn) {
btn.addEventListener('click', function () {
var opdId = parseInt(btn.getAttribute('data-opd-id') || '0', 10);
if (opdId <= 0) return;
openFhirModal('<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId, 'FHIR Preview — OPD #' + opdId);
});
});
document.getElementById('btnRefresh').addEventListener('click', function () {
if (typeof window.load_form === 'function') {
window.load_form(refreshUrl, 'ABDM Task Board');
return;
}
window.location.href = refreshUrl;
});
})();
</script>
</body>
</html>