finalize detail page except NDS and squencing

This commit is contained in:
2026-03-20 15:50:30 +07:00
parent aab4bd25dc
commit d4a8c9ded6
8 changed files with 212665 additions and 105432 deletions

View File

@@ -89,7 +89,6 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
const [yearB, weekB] = b.split('-').map(Number);
if (yearA !== yearB) return yearA - yearB;
return weekA - weekB;
});
@@ -109,11 +108,8 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
if (!allowedPrograms.includes(code)) return;
const values = labels.map(label => {
const found = data[code].find(row => `${row.year}-${row.period}` === label);
return found ? found.total : 0;
});
datasets.push({
@@ -135,14 +131,11 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
});
trendChart = new Chart(document.getElementById('trendChart'), {
type: 'line',
data: {
labels: displayLabels,
datasets: datasets
},
options: {
responsive: true,
plugins: {
@@ -160,11 +153,52 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
x: { grid: { display: false } }
}
}
});
});
}
/*
|--------------------------------------------------------------------------
| Province Map Helpers
|--------------------------------------------------------------------------
*/
function normalizeProvince(name, validSet) {
if (!name || !validSet) return null;
const clean = str =>
str.toLowerCase().replace(/\s+/g, '');
const raw = name.trim();
const map = {
"kepville": "Kep",
"sihanoukville": "Preah Sihanouk",
"sihanoukvillecity": "Preah Sihanouk",
"krongpailin": "Pailin",
"mondulkiri": "Mondulkiri",
"odormeanchey": "Oddar Meanchey",
"tbongkhmom": "Tboung Khmum",
"tboungkhmum": "Tboung Khmum",
"rattanakiri": "Ratanak Kiri"
};
const key = clean(raw);
if (map[key] && validSet.has(map[key])) {
return map[key];
}
const match = [...validSet].find(p => clean(p) === key);
return match || null;
}
function getRadius(total) {
if (!total) return 0;
const r = Math.sqrt(total);
return Math.max(4, Math.min(r * 2, 22));
}
@@ -184,72 +218,81 @@ function loadProvinceMap(startYear, startWeek, endYear, endWeek) {
attribution: '© OpenStreetMap'
}).addTo(map);
Promise.all([
fetch('/geo/cambodia_provinces.geojson').then(r => r.json()),
fetch(`/api/dashboard/province-circles?start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`).then(r => r.json())
])
.then(([geojson, data]) => {
.then(([geojson, data]) => {
L.geoJSON(geojson, {
style: {
fillOpacity: 0,
color: '#ccc',
weight: 1,
interactive: false
},
const validProvinces = new Set(
geojson.features.map(f => f.properties.ADM1_EN)
);
onEachFeature: function (feature, layer) {
L.geoJSON(geojson, {
style: {
fillOpacity: 0,
color: '#ccc',
weight: 1,
interactive: false
},
const province = feature.properties.ADM1_EN;
const center = layer.getBounds().getCenter();
onEachFeature: function (feature, layer) {
const rows = data.filter(d => d.site_province_name === province);
const province = feature.properties.ADM1_EN;
const center = layer.getBounds().getCenter();
const offsets = {
1: -0.15,
2: 0,
3: 0.15
};
const rows = data.filter(d => {
if (![1, 2, 3].includes(d.surveillance_id)) return false;
rows.forEach(row => {
const name = normalizeProvince(d.patient_province, validProvinces);
return name === province;
});
const lat = center.lat;
const lng = center.lng + offsets[row.surveillance_id];
const offsets = { 1: -0.15, 2: 0, 3: 0.15 };
const programName =
row.surveillance_id === 1 ? 'SARI' :
row.surveillance_id === 2 ? 'ILI' : 'LBM';
const colors = {
1: '#2563eb',
2: '#10b981',
3: '#9333ea'
};
const colors = {
1: '#2563eb',
2: '#10b981',
3: '#9333ea'
};
rows.forEach(row => {
L.circleMarker([lat, lng], {
const percent = row.total
? ((row.positive / row.total) * 100).toFixed(1)
: 0;
radius: 9,
fillColor: colors[row.surveillance_id],
color: '#fff',
weight: 1,
fillOpacity: 0.9
const offset = offsets[row.surveillance_id] ?? 0;
})
.bindTooltip(`
<strong>${province}</strong><br>
${programName}<br>
Total: ${row.total}
`)
.addTo(map);
const lat = center.lat;
const lng = center.lng + offset;
});
const programName =
row.surveillance_id === 1 ? 'SARI' :
row.surveillance_id === 2 ? 'ILI' : 'LBM';
}
L.circleMarker([lat, lng], {
radius: getRadius(row.total),
fillColor: colors[row.surveillance_id],
color: '#fff',
weight: 1,
fillOpacity: 0.9
})
.bindTooltip(`
<strong>${province}</strong><br>
${programName}<br>
Cases: ${row.total}<br>
Positivity: ${percent}%
`)
.addTo(map);
}).addTo(map);
});
});
}
}).addTo(map);
});
}
@@ -267,7 +310,6 @@ document.addEventListener("DOMContentLoaded", () => {
new DashboardFilter((startYear, startWeek, endYear, endWeek) => {
loadTrend('week', startYear, startWeek, endYear, endWeek);
loadProvinceMap(startYear, startWeek, endYear, endWeek);
});