working on detail page for sari, lil, amd lbm

This commit is contained in:
2026-03-13 15:49:01 +07:00
parent 519d0924c8
commit c2b820fc6d
14 changed files with 1627 additions and 956 deletions

View File

@@ -0,0 +1,108 @@
const charts = {};
function buildChart(id, type, labels, data, label = 'Cases') {
const ctx = document.getElementById(id);
if (!ctx) return;
if (charts[id]) charts[id].destroy();
charts[id] = new Chart(ctx, {
type: type,
data: {
labels: labels,
datasets: [{
label: label,
data: data,
borderWidth: 2,
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
}
function buildMixedTrendChart(canvasId, labels, samples, positivity) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
if (charts[canvasId]) charts[canvasId].destroy();
charts[canvasId] = new Chart(ctx, {
data: {
labels: labels,
datasets: [
{
type: 'line',
label: '% Positive',
data: positivity,
borderColor: '#1e6ef2',
borderWidth: 2,
tension: 0.4,
fill: false,
pointRadius: 4,
pointStyle: 'line',
yAxisID: 'y1'
},
{
type: 'bar',
label: 'Total sample',
data: samples,
backgroundColor: '#2ecc71',
borderRadius: 6,
barPercentage: 0.6,
pointStyle: 'rect',
categoryPercentage: 0.7,
yAxisID: 'y',
},
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
align: 'center',
labels: {
usePointStyle: true,
padding: 20,
boxWidth: 30,
font: {
size: 12
}
}
}
},
layout: {
padding: {
bottom: 50
}
},
scales: {
y: {
position: 'left',
title: {
display: true,
text: 'Total sample'
}
},
y1: {
position: 'right',
grid: {
drawOnChartArea: false
},
title: {
display: true,
text: '% Positive'
},
ticks: {
callback: value => value + '%'
}
}
}
}
});
}

View File

@@ -0,0 +1,132 @@
class DashboardFilter {
constructor(onChange) {
this.onChange = onChange;
this.rangeSelect = document.getElementById("trend_range");
this.startYear = document.getElementById("start_year");
this.startWeek = document.getElementById("start_week");
this.endYear = document.getElementById("end_year");
this.endWeek = document.getElementById("end_week");
this.customContainer = document.getElementById("custom_range_container");
if (!this.rangeSelect) return;
this.init();
}
init() {
this.populateFilters();
this.rangeSelect.addEventListener("change", () => {
const val = this.rangeSelect.value;
if (val === "custom") {
this.customContainer.style.display = "flex";
return;
}
this.customContainer.style.display = "none";
const range = this.lastWeeks(parseInt(val));
this.apply(range);
this.trigger();
});
["start_year","start_week","end_year","end_week"].forEach(id => {
const el = document.getElementById(id);
if (el) el.addEventListener("change", ()=>this.trigger());
});
const defaultRange = this.lastWeeks(8);
this.apply(defaultRange);
this.trigger();
}
trigger() {
this.onChange(
this.startYear.value,
this.startWeek.value,
this.endYear.value,
this.endWeek.value
);
}
apply(range) {
this.startYear.value = range.startYear;
this.startWeek.value = range.startWeek;
this.endYear.value = range.endYear;
this.endWeek.value = range.endWeek;
}
populateFilters() {
const year = new Date().getFullYear();
for (let y = year-10; y <= year; y++) {
this.startYear.innerHTML += `<option value="${y}">${y}</option>`;
this.endYear.innerHTML += `<option value="${y}">${y}</option>`;
}
for (let w = 1; w <= 53; w++) {
this.startWeek.innerHTML += `<option value="${w}">W${w}</option>`;
this.endWeek.innerHTML += `<option value="${w}">W${w}</option>`;
}
}
getISOWeek(date) {
const d = new Date(Date.UTC(date.getFullYear(),date.getMonth(),date.getDate()));
d.setUTCDate(d.getUTCDate()+4-(d.getUTCDay()||7));
const yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
const week = Math.ceil((((d-yearStart)/86400000)+1)/7);
return {year:d.getUTCFullYear(),week};
}
lastWeeks(n) {
const end = this.getISOWeek(new Date());
let startWeek = end.week-n+1;
let startYear = end.year;
while(startWeek<=0){
startWeek += 52;
startYear--;
}
return {
startYear,
startWeek,
endYear:end.year,
endWeek:end.week
};
}
}