nhance/app/Views/tpa_mis_reports_dashboard.php
2026-08-07 10:19:31 +05:30

402 lines
15 KiB
PHP

<style>
.tpa-mis-dashboard .filter-bar {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
align-items: flex-end;
background: #eef5fa;
border-radius: 10px;
padding: 10px 14px;
margin-bottom: 10px;
}
.tpa-mis-dashboard .filter-field {
flex: 1 1 140px;
min-width: 120px;
max-width: 220px;
}
.tpa-mis-dashboard .filter-field label {
display: block;
font-size: 12px;
font-weight: 700;
margin-bottom: 4px;
color: #111;
}
.tpa-mis-dashboard .filter-field .select2-container {
width: 100% !important;
}
.tpa-mis-dashboard .filter-field .select2-selection--single {
height: 32px !important;
min-height: 32px;
border: 1px solid #d8e2ea !important;
border-radius: 8px !important;
font-size: 12px;
}
.tpa-mis-dashboard .filter-field .select2-selection__rendered {
line-height: 30px !important;
padding-left: 10px !important;
color: #333;
}
.tpa-mis-dashboard .filter-field .select2-selection__arrow {
height: 30px !important;
}
.tpa-mis-dashboard .filter-field .select2-container--disabled .select2-selection--single {
background-color: #f5f7f9 !important;
cursor: not-allowed;
opacity: 0.85;
}
.tpa-mis-dashboard .dashboard-status {
font-size: 12px;
color: #5c6b7a;
padding: 4px 0 10px;
min-height: 20px;
}
.tpa-mis-dashboard .dashboard-status.is-error { color: #c0392b; }
.tpa-mis-dashboard .dashboard-status.is-loading { color: #0a8794; }
.tpa-mis-dashboard .dashboard-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
color: #7a8a99;
font-size: 13px;
background: #fff;
border: 1px dashed #d0dce6;
border-radius: 8px;
}
.tpa-mis-dashboard .mis-toolbar {
display: none;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.tpa-mis-dashboard .mis-toolbar.is-visible {
display: flex;
}
.tpa-mis-dashboard .mis-toolbar h5 {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #152A54;
}
.tpa-mis-dashboard .mis-frame-wrap {
display: none;
background: #fff;
border: 1px solid #d0dce6;
border-radius: 8px;
overflow: visible;
}
.tpa-mis-dashboard .mis-frame-wrap.is-visible {
display: block;
}
.tpa-mis-dashboard #mis-report-frame {
display: block;
width: 100%;
height: 0;
min-height: 0;
border: 0;
background: #fff;
overflow: hidden;
}
</style>
<div class="row tpa-mis-dashboard">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<div class="filter-bar">
<div class="filter-field">
<label for="filter-client">Client</label>
<select id="filter-client" class="form-control form-control-sm tpa-mis-select2">
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-branch">Branch</label>
<select id="filter-branch" class="form-control form-control-sm tpa-mis-select2" disabled>
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-policy">Policy</label>
<select id="filter-policy" class="form-control form-control-sm tpa-mis-select2" disabled>
<option value="">Select</option>
</select>
</div>
</div>
<div id="dashboard-status" class="dashboard-status"></div>
<div id="mis-toolbar" class="mis-toolbar">
<h5 id="mis-title">TPA MIS Report</h5>
<a id="mis-pdf-link" class="btn btn-sm btn-outline-primary" href="#" target="_blank" rel="noopener" style="display:none;">Download PDF</a>
</div>
<div id="dashboard-container">
<div class="dashboard-placeholder" id="dashboard-placeholder">
Select client, branch, and policy to load the report.
</div>
<div id="mis-frame-wrap" class="mis-frame-wrap">
<iframe id="mis-report-frame" title="TPA MIS Report" scrolling="no"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function () {
const CLIENTS_API = <?= json_encode(base_url('/util/getClientAndBranchAndPolicy')) ?>;
const MIS_API = <?= json_encode($misApiUrl ?? base_url('/util/tpa-reports/mis')) ?>;
const select2Options = {
placeholder: 'Select',
allowClear: true,
width: '100%',
minimumResultsForSearch: 0,
dropdownParent: $(document.body)
};
let branchByClient = {};
let policyByClient = {};
const $elClient = $('#filter-client');
const $elBranch = $('#filter-branch');
const $elPolicy = $('#filter-policy');
const elStatus = document.getElementById('dashboard-status');
const elPlaceholder = document.getElementById('dashboard-placeholder');
const elMisToolbar = document.getElementById('mis-toolbar');
const elMisFrameWrap = document.getElementById('mis-frame-wrap');
const elMisFrame = document.getElementById('mis-report-frame');
const elMisTitle = document.getElementById('mis-title');
const elMisPdf = document.getElementById('mis-pdf-link');
function setStatus(message, type) {
elStatus.textContent = message || '';
elStatus.className = 'dashboard-status' + (type ? ' is-' + type : '');
}
function initSelect2($select, disabled) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.prop('disabled', false);
$select.select2(select2Options);
if (disabled) {
$select.prop('disabled', true);
}
}
function rebuildSelect($select, placeholder, disabled, items, getValue, getLabel) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.empty().append($('<option>', { value: '', text: placeholder }));
(items || []).forEach(function (item) {
$select.append($('<option>', {
value: String(getValue(item)),
text: getLabel(item)
}));
});
$select.val('');
initSelect2($select, disabled);
}
function filtersComplete() {
return !!($elClient.val() && $elBranch.val() && $elPolicy.val());
}
function clearMisReport() {
if (elMisToolbar) elMisToolbar.classList.remove('is-visible');
if (elMisFrameWrap) elMisFrameWrap.classList.remove('is-visible');
if (elMisFrame) {
elMisFrame.onload = null;
elMisFrame.removeAttribute('src');
elMisFrame.style.height = '0px';
}
if (elMisPdf) {
elMisPdf.style.display = 'none';
elMisPdf.setAttribute('href', '#');
}
if (elPlaceholder) elPlaceholder.style.display = '';
}
function prepareMisDocument(doc) {
if (!doc || doc.getElementById('tpa-mis-embed-style')) return;
const style = doc.createElement('style');
style.id = 'tpa-mis-embed-style';
style.textContent = [
'html, body { height: auto !important; min-height: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; background: #fff !important; }',
'.page, .wrap { margin: 0 auto !important; box-shadow: none !important; }',
'body { padding: 0 !important; }'
].join('\n');
(doc.head || doc.documentElement).appendChild(style);
}
/** Fit iframe to report content only — avoids leftover grey scroll after load. */
function resizeMisFrame() {
if (!elMisFrame || !elMisFrame.contentWindow) return;
try {
const doc = elMisFrame.contentDocument || elMisFrame.contentWindow.document;
if (!doc || !doc.body) return;
prepareMisDocument(doc);
const roots = doc.querySelectorAll('.page, .wrap');
let height = 0;
if (roots.length) {
roots.forEach(function (el) {
const bottom = el.offsetTop + el.offsetHeight;
if (bottom > height) height = bottom;
});
} else {
height = doc.body.scrollHeight || doc.body.offsetHeight || 0;
}
// Prefer content box over inflated html/body min-heights.
height = Math.ceil(height);
if (height < 200) {
height = Math.max(
doc.body.scrollHeight || 0,
(doc.documentElement && doc.documentElement.scrollHeight) || 0
);
}
if (height > 0) {
const next = height + 8;
const prev = parseInt(elMisFrame.style.height, 10) || 0;
// Only grow when content actually needs more room; shrink when excess grey space.
if (Math.abs(next - prev) > 4) {
elMisFrame.style.height = next + 'px';
}
}
} catch (e) {
// Same-origin expected; leave height alone on failure.
}
}
function bindMisFrameResize() {
if (!elMisFrame) return;
elMisFrame.onload = function () {
resizeMisFrame();
// Charts/images can settle after first paint (ABHI Chart.js).
[200, 600, 1200, 2000].forEach(function (ms) {
setTimeout(resizeMisFrame, ms);
});
};
}
async function loadFilterData() {
setStatus('Loading clients…', 'loading');
try {
const res = await fetch(CLIENTS_API, { credentials: 'same-origin' });
const json = await res.json();
if (!json.status) {
setStatus('Could not load client list.', 'error');
return;
}
branchByClient = json.branch_data || {};
policyByClient = json.policyListByClient || {};
rebuildSelect($elClient, 'Select', false, json.client_data || [], function (c) { return c.id; }, function (c) {
return c.client_name || c.client_short_name || ('Client ' + c.id);
});
rebuildSelect($elBranch, 'Select', true);
rebuildSelect($elPolicy, 'Select', true);
setStatus('');
} catch (e) {
setStatus('Failed to load filter options.', 'error');
}
}
async function fetchMisReport(policyId) {
clearMisReport();
if (!filtersComplete() || !policyId) {
setStatus('Select client, branch, and policy to load the report.');
return;
}
setStatus('Loading MIS report…', 'loading');
try {
const url = MIS_API + '?client_policy=' + encodeURIComponent(policyId);
const res = await fetch(url, { credentials: 'same-origin' });
const json = await res.json();
if (json.status !== 'success' || !json.data || !json.data.preview_url) {
setStatus(json.message || 'No MIS report for this TPA.', 'error');
return;
}
if (elMisTitle) elMisTitle.textContent = json.data.title || 'TPA MIS Report';
if (elMisFrame) {
bindMisFrameResize();
elMisFrame.src = json.data.preview_url;
}
if (elMisPdf && json.data.pdf_url) {
elMisPdf.href = json.data.pdf_url;
elMisPdf.style.display = '';
}
if (elPlaceholder) elPlaceholder.style.display = 'none';
if (elMisToolbar) elMisToolbar.classList.add('is-visible');
if (elMisFrameWrap) elMisFrameWrap.classList.add('is-visible');
setStatus('');
} catch (e) {
setStatus('Failed to load MIS report.', 'error');
}
}
$(window).on('resize', function () {
// Parent width change may reflow report; re-measure after layout.
setTimeout(resizeMisFrame, 100);
});
$elClient.on('change', function () {
const clientId = $(this).val();
rebuildSelect($elBranch, 'Select', !clientId);
rebuildSelect($elPolicy, 'Select', true);
clearMisReport();
setStatus(clientId ? '' : 'Select client, branch, and policy to load the report.');
if (!clientId) return;
const branches = branchByClient[clientId] || [];
rebuildSelect($elBranch, 'Select', branches.length === 0, branches, function (b) { return b.id; }, function (b) {
return b.branch_name || ('Branch ' + b.id);
});
});
$elBranch.on('change', function () {
const branchId = $(this).val();
const clientId = $elClient.val();
rebuildSelect($elPolicy, 'Select', !branchId);
clearMisReport();
setStatus(branchId ? '' : 'Select branch and policy to load the report.');
if (!branchId || !clientId) return;
const policiesForClient = policyByClient[clientId] || [];
const policies = policiesForClient.filter(function (p) {
return String(p.client_branch_id) === String(branchId);
});
rebuildSelect($elPolicy, 'Select', policies.length === 0, policies, function (p) { return p.id; }, function (p) {
const type = p.policy_type || '';
const no = p.policy_no || '';
return (type && no) ? (type + ' - ' + no) : (no || type || ('Policy ' + p.id));
});
if (policies.length === 0) {
setStatus('No policies found for this branch.', 'error');
}
});
$elPolicy.on('change', function () {
fetchMisReport($(this).val());
});
initSelect2($elClient, false);
initSelect2($elBranch, true);
initSelect2($elPolicy, true);
loadFilterData();
});
</script>