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

551 lines
21 KiB
PHP

<script>
(function () {
const MIS_DATA = <?= json_encode($vm ?? [], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
const embedMode = <?= !empty($embed) ? 'true' : 'false' ?>;
const COLORS = {
blue: '#2E86DE',
navy: '#1B3F8C',
orange: '#E8862C',
purple: '#6A3D9A',
red: '#C8102E',
female: '#1A3399',
male: '#6A3D9A'
};
function fmt(n) {
if (n === '' || n === null || n === undefined) return '';
return Number(n).toLocaleString('en-IN');
}
function dash(v) { return (v === 0 || v === '0') ? '—' : v; }
function el(id) { return document.getElementById(id); }
function setHtml(id, html) { const node = el(id); if (node) node.innerHTML = html; }
function setText(id, text) { const node = el(id); if (node) node.textContent = text; }
const claimStatus = MIS_DATA.claimStatus || [];
setHtml('tblClaimStatus', claimStatus.map(r =>
`<tr><td>${r.status}</td><td class="num">${r.number}</td><td class="num">${fmt(r.reported)}</td><td class="num">${fmt(r.incurred)}</td></tr>`
).join(''));
const policyPremium = MIS_DATA.policyPremium || [];
setHtml('tblPolicyPremium', policyPremium.map(r =>
`<tr><td>${r.sr}</td><td>${r.label}</td><td class="num">${fmt(r.amount)}</td></tr>`
).join('') +
`<tr style="font-weight:700;background:#F0F0F3;border-top:2px solid var(--red)"><td colspan="2">Total</td><td class="num">${fmt(MIS_DATA.policyPremiumTotal || 0)}</td></tr>`);
const reimbTAT = MIS_DATA.reimbTAT || [];
setHtml('tblReimbTAT', reimbTAT.map(r =>
`<tr><td>${r.range}</td><td class="num">${r.count}</td><td class="num">${r.pct}</td></tr>`
).join('') +
`<tr style="font-weight:700;background:#F0F0F3;border-top:2px solid var(--red)"><td>Total</td><td class="num">${MIS_DATA.reimbTATTotal?.count || 0}</td><td class="num">${MIS_DATA.reimbTATTotal?.pct || 0}</td></tr>`);
const gender = MIS_DATA.gender || [];
const genderOrder = ['Male', 'Female'];
setHtml('tblGender', genderOrder.map(label => {
const row = gender.find(g => g.label === label) || { label, count: 0, amount: 0 };
return `<tr><td>${row.label}</td><td class="num">${row.count}</td><td class="num">${fmt(row.amount)}</td></tr>`;
}).join(''));
setText('tblGenderCountTotal', MIS_DATA.genderTotal?.count || 0);
setText('tblGenderAmtTotal', fmt(MIS_DATA.genderTotal?.incurred_amount || 0));
const claimType = MIS_DATA.claimType || [];
setHtml('tblClaimType', claimType.map(r =>
`<tr ${r.header ? 'class="subhead"' : ''}><td>${r.header ? '' : '&nbsp;&nbsp;&nbsp;'}${r.type}</td><td class="num">${r.count}</td><td class="num">${r.countPct}</td><td class="num">${fmt(r.amount)}</td><td class="num">${r.amtPct}</td><td class="num">${fmt(r.incurred)}</td><td class="num">${r.incPct}</td></tr>`
).join(''));
const ct = MIS_DATA.claimTypeTotal || {};
setText('tblClaimTypeCount', ct.count || 0);
setText('tblClaimTypeAmt', fmt(ct.amount || 0));
setText('tblClaimTypeInc', fmt(ct.incurred || 0));
const relation = MIS_DATA.relation || [];
setHtml('tblRelation', relation.map(r =>
`<tr><td>${r.label}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblRelationCount', MIS_DATA.relationTotal?.count || 0);
setText('tblRelationAmt', fmt(MIS_DATA.relationTotal?.incurred_amount || 0));
const memberType = MIS_DATA.memberType || [];
setHtml('tblMemberType', memberType.map(r =>
`<tr><td>${r.label}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblMemberCount', MIS_DATA.memberTypeTotal?.count || 0);
setText('tblMemberAmt', fmt(MIS_DATA.memberTypeTotal?.incurred_amount || 0));
const age = MIS_DATA.age || [];
setHtml('tblAge', age.map(r =>
`<tr><td>${r.band}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
setText('tblAgeCount', MIS_DATA.ageTotal?.count || 0);
setText('tblAgeAmt', fmt(MIS_DATA.ageTotal?.incurred_amount || 0));
const diagnosis = MIS_DATA.diagnosis || [];
setHtml('tblDiagnosis', diagnosis.map(r =>
`<tr><td>${r.name}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td><td class="num">${r.acs ? fmt(r.acs) : ''}</td><td class="num">${r.lr}</td></tr>`
).join(''));
const dt = MIS_DATA.diagnosisTotal || {};
setText('tblDiagCount', dt.count || 0);
setText('tblDiagAmt', fmt(dt.amt || 0));
setText('tblDiagAcs', fmt(dt.acs || 0));
setText('tblDiagLr', dt.lr || 0);
function renderUtil(id, data, prefix) {
const rows = data?.rows || [];
setHtml(id, rows.map(r =>
`<tr><td>${r.claims}</td><td class="num">${r.beneficiaries}</td><td class="num">${r.incurredCount}</td><td class="num">${r.countPct || dash(0)}</td><td class="num">${fmt(r.amount)}</td><td class="num">${r.amountPct || dash(0)}</td></tr>`
).join(''));
const t = data?.total || {};
setText(prefix + 'Ben', t.beneficiaries || 0);
setText(prefix + 'Cnt', t.incurredCount || 0);
setText(prefix + 'Amt', fmt(t.amount || 0));
}
renderUtil('tblUtilEmp', MIS_DATA.utilizationEmployees, 'tblUtilEmp');
renderUtil('tblUtilDep', MIS_DATA.utilizationDependents, 'tblUtilDep');
const hospitals = MIS_DATA.hospitals || [];
setHtml('tblHospital', hospitals.map(r =>
`<tr><td>${r.name}</td><td class="num">${r.count}</td><td class="num">${r.cpct}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.apct}</td></tr>`
).join(''));
const ht = MIS_DATA.hospitalTotal || {};
setText('tblHospitalCount', ht.count || 0);
setText('tblHospitalAmt', fmt(ht.incurred_amount || 0));
const cities = MIS_DATA.cities || [];
setHtml('tblCity', cities.map(r =>
`<tr><td>${r.city}</td><td class="num">${r.count}</td><td class="num">${fmt(r.amt)}</td><td class="num">${r.paidCount}</td><td class="num">${r.paidAmt !== '' && r.paidAmt !== null ? Number(r.paidAmt).toFixed(2) : ''}</td><td class="num">${r.outCount}</td><td class="num">${r.outAmt !== '' && r.outAmt !== null ? fmt(r.outAmt) : ''}</td><td class="num">${r.acs !== '' && r.acs !== null ? fmt(r.acs) : ''}</td></tr>`
).join(''));
const ct2 = MIS_DATA.cityTotals || {};
setText('tblCityCount', ct2.count || 0);
setText('tblCityAmt', fmt(ct2.amt || 0));
setText('tblCityPaidCnt', ct2.paidCount || 0);
setText('tblCityPaidAmt', Number(ct2.paidAmt || 0).toFixed(2));
setText('tblCityOutCnt', ct2.outCount || 0);
setText('tblCityOutAmt', fmt(ct2.outAmt || 0));
setText('tblCityAcs', fmt(ct2.acs || 0));
if (embedMode || typeof Chart === 'undefined') {
return;
}
Chart.defaults.font.family = 'Segoe UI, Arial, sans-serif';
Chart.defaults.font.size = 11;
Chart.defaults.color = '#333';
const statusColors = [COLORS.blue, COLORS.navy, COLORS.orange, COLORS.purple];
const statusLabels = ['Reported', 'Settled', 'Outstanding', 'Rejected'];
const amountData = claimStatus.map(r => r.reported || 0);
const countData = claimStatus.map(r => r.number || 0);
/* Nested donut: outer = Claim Amount, inner = Claim Count */
const donutLeaderPlugin = {
id: 'donutLeaderPlugin',
afterDatasetsDraw(chart) {
const { ctx } = chart;
const outerMeta = chart.getDatasetMeta(0);
const innerMeta = chart.getDatasetMeta(1);
if (!outerMeta || !innerMeta) return;
outerMeta.data.forEach((arc, i) => {
const val = amountData[i];
if (!val) return;
const pos = arc.tooltipPosition();
const angle = (arc.startAngle + arc.endAngle) / 2;
const r = arc.outerRadius + 18;
const x = arc.x + Math.cos(angle) * r;
const y = arc.y + Math.sin(angle) * r;
const lx = arc.x + Math.cos(angle) * (arc.outerRadius + 4);
const ly = arc.y + Math.sin(angle) * (arc.outerRadius + 4);
ctx.save();
ctx.strokeStyle = '#999';
ctx.lineWidth = 0.8;
ctx.beginPath();
ctx.moveTo(lx, ly);
ctx.lineTo(x, y);
ctx.stroke();
ctx.font = 'bold 11px Segoe UI, Arial';
ctx.fillStyle = '#222';
ctx.textAlign = x < arc.x ? 'right' : 'left';
ctx.fillText(fmt(val), x + (x < arc.x ? -4 : 4), y + 4);
ctx.restore();
});
innerMeta.data.forEach((arc, i) => {
const val = countData[i];
if (!val) return;
const pos = arc.tooltipPosition();
ctx.save();
ctx.font = 'bold 12px Segoe UI, Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(val), pos.x, pos.y);
ctx.restore();
});
}
};
new Chart(el('chartDonut1'), {
type: 'doughnut',
data: {
labels: statusLabels,
datasets: [
{
label: 'Claim Amount',
data: amountData,
backgroundColor: statusColors,
borderWidth: 2,
borderColor: '#fff',
weight: 1.1
},
{
label: 'Claim Count',
data: countData,
backgroundColor: statusColors,
borderWidth: 2,
borderColor: '#fff',
weight: 0.55
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '42%',
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (c) => c.dataset.label + ': ' + (c.datasetIndex === 1 ? c.raw : fmt(c.raw))
}
}
}
},
plugins: [donutLeaderPlugin]
});
/* Pre-Auth TAT — column chart with % labels above bars */
const preAuth = MIS_DATA.preAuthTAT || { within: 100, above: 0 };
const preAuthValues = [preAuth.within, preAuth.above];
function maxInDatasets(datasets) {
let max = 0;
(datasets || []).forEach(ds => {
(ds.data || []).forEach(v => { max = Math.max(max, Number(v) || 0); });
});
return max;
}
const barTopLabelsPlugin = {
id: 'barTopLabels',
afterDatasetsDraw(chart) {
const cfg = chart.options.plugins?.barTopLabels || {};
if (cfg.enabled === false || chart.options.indexAxis === 'y') return;
const formatType = cfg.format || 'number';
const formatters = {
percent: v => Number(v).toFixed(2) + '%',
count: v => String(Math.round(Number(v))),
amount: v => fmt(v),
number: v => fmt(v)
};
const labelFn = formatters[formatType] || formatters.number;
const hideZero = cfg.hideZero !== false;
const { ctx } = chart;
const dsCount = chart.data.datasets.length;
chart.data.datasets.forEach((ds, di) => {
chart.getDatasetMeta(di).data.forEach((bar, i) => {
const val = Number(ds.data[i]);
if (hideZero && (!val || val === 0)) return;
const pos = bar.tooltipPosition();
const offsetX = dsCount > 1 ? (di - (dsCount - 1) / 2) * 14 : 0;
ctx.save();
ctx.font = 'bold 10px Segoe UI, Arial';
ctx.fillStyle = '#222';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText(labelFn(val), pos.x + offsetX, pos.y - 5);
ctx.restore();
});
});
}
};
const chartLayout = { padding: { top: 28, left: 4, right: 4 } };
const hBarValuePlugin = {
id: 'hBarValuePlugin',
afterDatasetsDraw(chart) {
const cfg = chart.options.plugins?.hBarLabels || {};
if (cfg.enabled === false || chart.options.indexAxis !== 'y') return;
const formatType = cfg.format || 'amount';
const formatters = {
count: v => String(Math.round(Number(v))),
amount: v => fmt(v)
};
const labelFn = formatters[formatType] || formatters.amount;
const { ctx } = chart;
chart.getDatasetMeta(0).data.forEach((bar, i) => {
const val = chart.data.datasets[0].data[i];
if (!val && val !== 0) return;
const props = bar.getProps(['x', 'y', 'base', 'width'], true);
const barLeft = Math.min(props.x, props.base);
const barRight = Math.max(props.x, props.base);
const barW = Math.abs(barRight - barLeft);
const midX = barLeft + barW / 2;
const text = labelFn(val);
ctx.save();
ctx.font = 'bold 11px Segoe UI, Arial';
const tw = ctx.measureText(text).width + 14;
const th = 20;
const rx = midX - tw / 2;
const ry = props.y - th / 2;
ctx.fillStyle = 'rgba(255,255,255,0.92)';
ctx.strokeStyle = '#d1d5db';
ctx.lineWidth = 0.5;
ctx.fillRect(rx, ry, tw, th);
ctx.strokeRect(rx, ry, tw, th);
ctx.fillStyle = '#222';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, midX, props.y);
ctx.restore();
});
}
};
Chart.register(barTopLabelsPlugin, hBarValuePlugin);
new Chart(el('chartPreAuth'), {
type: 'bar',
data: {
labels: ['With in 2 Hours %', 'Above 2 Hours %'],
datasets: [{ data: preAuthValues, backgroundColor: [COLORS.blue, COLORS.navy], barPercentage: 0.45, categoryPercentage: 0.7 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'percent', hideZero: false } },
scales: {
x: { grid: { display: false }, border: { display: false } },
y: { display: false, suggestedMax: 115 }
}
}
});
/* Premium / Earn Premium / Claim Incurred — horizontal bars with value pills */
const premiumBars = MIS_DATA.premiumBars || [0, 0, 0];
const premiumLabels = ['Premium', 'Earn Premium', 'Claim Incurred Amount'];
new Chart(el('chartPremiumBars'), {
type: 'bar',
data: {
labels: premiumLabels,
datasets: [{ data: premiumBars, backgroundColor: [COLORS.navy, COLORS.blue, COLORS.orange], barThickness: 28 }]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 8, right: 16, bottom: 8, left: 8 } },
plugins: { legend: { display: false }, barTopLabels: { enabled: false }, hBarLabels: { format: 'amount' } },
scales: {
x: { display: false, grid: { display: false }, suggestedMax: Math.max(...premiumBars, 1) * 1.15 },
y: { grid: { display: false }, border: { display: false }, ticks: { font: { weight: 'bold', size: 12 } } }
}
}
});
/* Gender — horizontal bar charts (Male purple, Female navy) */
const genderSorted = genderOrder.map(label => {
const found = gender.find(g => g.label === label);
return found || { label, count: 0, amount: 0 };
});
function genderBar(canvasId, valueKey) {
const labels = genderSorted.map(g => g.label);
const values = genderSorted.map(g => g[valueKey] || 0);
const colors = labels.map(l => l.toLowerCase() === 'female' ? COLORS.female : COLORS.male);
const maxVal = Math.max(...values, 1);
new Chart(el(canvasId), {
type: 'bar',
data: { labels, datasets: [{ data: values, backgroundColor: colors, barThickness: 32 }] },
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 10, right: 20, bottom: 10, left: 4 } },
plugins: {
legend: { display: false },
barTopLabels: { enabled: false },
hBarLabels: { format: valueKey === 'count' ? 'count' : 'amount' }
},
scales: {
x: { display: false, grid: { display: false }, suggestedMax: maxVal * 1.2 },
y: { grid: { display: false }, border: { display: false }, ticks: { font: { weight: 'bold', size: 12 } } }
}
}
});
}
genderBar('chartGenderCount', 'count');
genderBar('chartGenderAmt', 'amount');
const pc = MIS_DATA.paidCharts || {};
const cbt = pc.countByType || {};
const paidCountData = pc.paidCount || [0, 0];
const paidAmtData = pc.paidAmt || [0, 0];
const acsData = pc.acs || [0, 0];
const amtStatusData = pc.amtByStatus || [0, 0, 0];
const countStatusCashless = [cbt.Settled?.Cashless || 0, cbt.Outstanding?.Cashless || 0, cbt.Rejected?.Cashless || 0];
const countStatusReimb = [cbt.Settled?.Reimbursement || 0, cbt.Outstanding?.Reimbursement || 0, cbt.Rejected?.Reimbursement || 0];
/* Claim Paid Count By Claim Type */
new Chart(el('chartPaidCount'), {
type: 'bar',
data: {
labels: ['Cashless', 'Reimbursement'],
datasets: [{ data: paidCountData, backgroundColor: [COLORS.blue, COLORS.navy], barPercentage: 0.5 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'count' } },
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1, precision: 0 },
suggestedMax: Math.max(1, maxInDatasets([{ data: paidCountData }]) * 1.25)
}
}
}
});
/* Claim Paid Amount and ACS */
const paidAmtDatasets = [
{ label: 'Claim Paid Amount', data: paidAmtData, backgroundColor: COLORS.blue },
{ label: 'ACS', data: acsData, backgroundColor: COLORS.orange }
];
new Chart(el('chartPaidAmtACS'), {
type: 'bar',
data: {
labels: ['Cashless', 'Reimbursement'],
datasets: paidAmtDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'amount' } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: v => fmt(v) },
suggestedMax: maxInDatasets(paidAmtDatasets) * 1.2 || undefined
}
}
}
});
/* Claim Count By Claim Status */
const countStatusDatasets = [
{ label: 'Cashless', data: countStatusCashless, backgroundColor: COLORS.blue },
{ label: 'Reimbursement', data: countStatusReimb, backgroundColor: COLORS.navy }
];
new Chart(el('chartCountStatus'), {
type: 'bar',
data: {
labels: ['Settled', 'Outstanding', 'Rejected'],
datasets: countStatusDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'count' } },
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1, precision: 0 },
suggestedMax: Math.max(1, maxInDatasets(countStatusDatasets) * 1.25)
}
}
}
});
/* Claim Amount By Claim Status */
new Chart(el('chartAmtStatus'), {
type: 'bar',
data: {
labels: ['Settled', 'Outstanding', 'Rejected'],
datasets: [{ data: amtStatusData, backgroundColor: [COLORS.blue, COLORS.orange, COLORS.purple], barPercentage: 0.55 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { display: false }, barTopLabels: { format: 'amount' } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: v => fmt(v) },
suggestedMax: maxInDatasets([{ data: amtStatusData }]) * 1.2 || undefined
}
}
}
});
/* Monthly registration charts */
const months = MIS_DATA.months || [];
const paidCountMonth = MIS_DATA.paidCountByMonth || [];
const outCountMonth = MIS_DATA.outCountByMonth || [];
const paidAmtMonth = MIS_DATA.paidAmtByMonth || [];
const outAmtMonth = MIS_DATA.outAmtByMonth || [];
const monthCountDatasets = [
{ label: 'Claim Paid Count', data: paidCountMonth, backgroundColor: COLORS.blue, order: 2, barPercentage: 0.7, categoryPercentage: 0.8 },
{ label: 'Claim Outstanding Count', data: outCountMonth, backgroundColor: 'rgba(46,134,222,0.12)', borderColor: COLORS.navy, borderWidth: 2, order: 1, barPercentage: 0.95, categoryPercentage: 0.8 }
];
const monthAmtDatasets = [
{ label: 'Claim Paid Amount', data: paidAmtMonth, backgroundColor: COLORS.blue, order: 2, barPercentage: 0.7, categoryPercentage: 0.8 },
{ label: 'Claim Outstanding Amount', data: outAmtMonth, backgroundColor: 'rgba(46,134,222,0.12)', borderColor: COLORS.navy, borderWidth: 2, order: 1, barPercentage: 0.95, categoryPercentage: 0.8 }
];
const monthCountMax = maxInDatasets(monthCountDatasets);
const monthAmtMax = maxInDatasets(monthAmtDatasets);
new Chart(el('chartMonthCount'), {
type: 'bar',
data: { labels: months, datasets: monthCountDatasets },
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'count' } },
scales: {
y: { position: 'right', beginAtZero: true, suggestedMax: monthCountMax * 1.2 || undefined, ticks: { precision: 0 } },
x: { grid: { display: false } }
}
}
});
new Chart(el('chartMonthAmt'), {
type: 'bar',
data: { labels: months, datasets: monthAmtDatasets },
options: {
responsive: true,
maintainAspectRatio: false,
layout: chartLayout,
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } }, barTopLabels: { format: 'amount' } },
scales: {
y: { position: 'right', beginAtZero: true, suggestedMax: monthAmtMax * 1.2 || undefined, ticks: { callback: v => fmt(v) } },
x: { grid: { display: false } }
}
}
});
})();
</script>