const standardPrograms = ['SARI', 'ILI', 'LBM']; const programCode = (window.PROGRAM_CODE || '').trim().toUpperCase(); let map; let provinceLayer; document.addEventListener("DOMContentLoaded", () => { if (!standardPrograms.includes(programCode)) return; new DashboardFilter((startYear, startWeek, endYear, endWeek) => { fetch(`/api/dashboard/program?surveillance_id=${window.SURVEILLANCE_ID}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`) .then(res => res.json()) .then(renderDashboard) .catch(err => console.error("Dashboard API error:", err)); }); }); function renderProvinceHeatmap(rows) { const totals = {}; rows.forEach(r => { totals[r.site_province_name] = { total: Number(r.total), positive: Number(r.positive) }; }); if (map) { map.remove(); } map = L.map('provinceMap').setView([12.7, 104.9], 7); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); addProvinceLegend(); fetch('/geo/cambodia_provinces.geojson') .then(r => r.json()) .then(geo => { function getColor(value) { if (value > 50) return "#b91c1c"; if (value >= 10) return "#ef4444"; if (value > 0) return "#fecaca"; return "#f3f4f600"; } provinceLayer = L.geoJSON(geo, { style: feature => { const province = feature.properties.ADM1_EN; const value = totals[province]?.total || 0; return { color: "#444", weight: 1, fillColor: getColor(value), fillOpacity: 0.7 }; }, onEachFeature: (feature, layer) => { const province = feature.properties.ADM1_EN; const total = totals[province]?.total || 0; const positive = totals[province]?.positive || 0; const percent = total ? ((positive / total) * 100).toFixed(1) : 0; console.log(province, total, positive, percent); layer.bindTooltip(` ${province}
Total: ${total}
Positivity: ${percent}% `); } }).addTo(map); }); } function addProvinceLegend() { const legend = L.control({ position: "bottomright" }); legend.onAdd = function () { const div = L.DomUtil.create("div", "map-legend"); div.innerHTML = `
Cases
> 50
10 – 50
1 – 9
0
`; return div; }; legend.addTo(map); } function renderTrend(valueId, changeId, current, previous, suffix = '') { const valueEl = document.getElementById(valueId); const changeEl = document.getElementById(changeId); if (!valueEl || !changeEl) return; valueEl.textContent = current + suffix; if (!previous) { changeEl.innerHTML = "— No previous data"; changeEl.className = "text-muted"; return; } const diff = current - previous; const percent = ((diff / previous) * 100).toFixed(1); if (diff > 0) { changeEl.innerHTML = `↑ +${percent}% from previous week`; changeEl.className = "text-success"; } else if (diff < 0) { changeEl.innerHTML = `↓ ${percent}% from previous week`; changeEl.className = "text-danger"; } else { changeEl.innerHTML = "— No significant change"; changeEl.className = "text-muted"; } } function renderProgramTrend(rows) { rows = rows || []; const labels = rows.map(r => `W${r.period}`); const samples = rows.map(r => r.total_samples || 0); const fluRate = rows.map(r => r.influenza_rate || 0); const covidRate = rows.map(r => r.covid_rate || 0); buildMixedTrendChart( 'trendChart', labels, samples, fluRate, covidRate ); } function renderSummary(summary) { summary = summary || {}; const cases = summary.cases || {}; const hospital = summary.hospital_rate || {}; const icu = summary.icu_rate || {}; const positivity = summary.positivity_rate || {}; renderTrend( "totalCases", "casesChange", cases.current || 0, cases.previous || 0 ); renderTrend( "influenzaRate", "influenzaChange", summary.influenza_rate.current, summary.influenza_rate.previous, "%" ); renderTrend( "covidRate", "covidChange", summary.covid_rate.current, summary.covid_rate.previous, "%" ); renderTrend( "hospitalRate", "hospitalChange", hospital.current || 0, hospital.previous || 0, "%" ); renderTrend( "icuRate", "icuChange", icu.current || 0, icu.previous || 0, "%" ); renderTrend( "positivityRate", "positivityChange", positivity.current || 0, positivity.previous || 0, "%" ); } function renderDashboard(data) { data = data || {}; renderProgramTrend(data.trend || []); renderSummary(data.summary || {}); renderProvinceHeatmap(data.province_distribution || []); // buildStackedChart( // "pathogenChart", // labels, // [ // { // label: "Influenza", // data: influenza, // backgroundColor: "#2E7D32" // }, // { // label: "SARS-CoV-2", // data: covid, // backgroundColor: "#A5D6A7" // } // ] // ); const pathogenRows = (data.pathogen_distribution || []) .sort((a, b) => b.total - a.total); const colors = [ '#2563eb', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#f97316', '#84cc16' ]; buildChart( 'pathogenChart', 'doughnut', pathogenRows.map(r => r.pathogen), pathogenRows.map(r => r.total) ); charts['pathogenChart'].data.datasets[0].backgroundColor = colors; charts['pathogenChart'].update(); buildChart( 'ageChart', 'doughnut', (data.age_distribution || []).map(r => r.age_group), (data.age_distribution || []).map(r => r.total) ); charts['ageChart'].data.datasets[0].backgroundColor = colors; charts['ageChart'].update(); buildChart( 'sexChart', 'bar', (data.sex_distribution || []).map(r => r.patient_sex), (data.sex_distribution || []).map(r => r.total) ); charts['sexChart'].data.datasets[0].backgroundColor = colors; charts['sexChart'].update(); buildChart( 'subtypeChart', 'bar', (data.subtype_distribution || []).map(r => r.subtype), (data.subtype_distribution || []).map(r => r.total) ); charts['subtypeChart'].data.datasets[0].backgroundColor = colors; charts['subtypeChart'].update(); buildChart( 'sentinelChart', 'pie', (data.sentinel_sites || []).map(r => r.name), (data.sentinel_sites || []).map(r => r.total) ); charts['sentinelChart'].data.datasets[0].backgroundColor = colors; charts['sentinelChart'].update(); }