fix bugs
This commit is contained in:
@@ -234,27 +234,6 @@ class DashboardController extends Controller
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sentinel Map
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// public function sentinelMap(Request $request)
|
||||
|
||||
// {
|
||||
// $range = $this->getEpiRange($request);
|
||||
|
||||
// if (!$range) {
|
||||
// return response()->json(['error' => 'Missing epiweek range'], 400);
|
||||
// }
|
||||
|
||||
// return response()->json($data);
|
||||
// }
|
||||
|
||||
public function fetchSourceData()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -28,9 +28,6 @@ class DashboardService
|
||||
|
||||
public function summaryCards()
|
||||
{
|
||||
$programs = Surveillance::orderBy('id')->get();
|
||||
$results = [];
|
||||
|
||||
$today = date('Y-m-d');
|
||||
|
||||
$currentFrom = date('Y-m-d', strtotime('-6 days'));
|
||||
@@ -39,15 +36,23 @@ class DashboardService
|
||||
$prevFrom = date('Y-m-d', strtotime('-13 days'));
|
||||
$prevTo = date('Y-m-d', strtotime('-7 days'));
|
||||
|
||||
$results = [];
|
||||
|
||||
$programs = Surveillance::where('id', '!=', 6)
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
foreach ($programs as $program) {
|
||||
|
||||
$current = SurveillanceCase::where('surveillance_id', $program->id)
|
||||
->whereBetween('case_date', [$currentFrom, $currentTo])
|
||||
->count();
|
||||
->distinct('lab_code')
|
||||
->count('lab_code');
|
||||
|
||||
$previous = SurveillanceCase::where('surveillance_id', $program->id)
|
||||
->whereBetween('case_date', [$prevFrom, $prevTo])
|
||||
->count();
|
||||
->distinct('lab_code')
|
||||
->count('lab_code');
|
||||
|
||||
$percentChange = $previous > 0
|
||||
? round((($current - $previous) / $previous) * 100, 1)
|
||||
@@ -62,6 +67,44 @@ class DashboardService
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| A/H5N1 Summary Card
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
$h5n1Current = SurveillanceCase::join('case_lab_results', function ($join) {
|
||||
$join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code')
|
||||
->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id');
|
||||
})
|
||||
->whereBetween('surveillance_cases.case_date', [$currentFrom, $currentTo])
|
||||
->where('case_lab_results.subtype', 'A/H5N1')
|
||||
->where('surveillance_cases.surveillance_id', '!=', 6)
|
||||
->distinct('surveillance_cases.lab_code')
|
||||
->count('surveillance_cases.lab_code');
|
||||
|
||||
$h5n1Previous = SurveillanceCase::join('case_lab_results', function ($join) {
|
||||
$join->on('surveillance_cases.lab_code', '=', 'case_lab_results.lab_code')
|
||||
->on('surveillance_cases.surveillance_id', '=', 'case_lab_results.surveillance_id');
|
||||
})
|
||||
->whereBetween('surveillance_cases.case_date', [$prevFrom, $prevTo])
|
||||
->where('case_lab_results.subtype', 'A/H5N1')
|
||||
->where('surveillance_cases.surveillance_id', '!=', 6)
|
||||
->distinct('surveillance_cases.lab_code')
|
||||
->count('surveillance_cases.lab_code');
|
||||
|
||||
$h5n1Percent = $h5n1Previous > 0
|
||||
? round((($h5n1Current - $h5n1Previous) / $h5n1Previous) * 100, 1)
|
||||
: ($h5n1Current > 0 ? 100 : 0);
|
||||
|
||||
$results[] = [
|
||||
'surveillance_id' => null,
|
||||
'code' => 'H5N1',
|
||||
'current_total' => $h5n1Current,
|
||||
'previous_total' => $h5n1Previous,
|
||||
'percent_change' => $h5n1Percent
|
||||
];
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
'name' => env('APP_NAME', 'NRML Dashboard'),
|
||||
|
||||
'lookback_days' => [
|
||||
'SARI' => 30,
|
||||
@@ -61,7 +61,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
'url' => env('APP_URL', 'http://localhost:8000'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import * as Charts from "./globals.js";
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
document
|
||||
.getElementById('chartSelectorBtn')
|
||||
?.addEventListener('click', openChartSelector);
|
||||
const toggle = document.getElementById("exportToggle");
|
||||
const items = document.getElementById("exportItems");
|
||||
const close = document.getElementById("exportClose");
|
||||
|
||||
|
||||
if (!toggle || !items) return;
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
@@ -20,12 +27,31 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
toggle.innerText = "Export ▸";
|
||||
});
|
||||
}
|
||||
document
|
||||
.getElementById('btnCharts')
|
||||
?.addEventListener('click', openChartSelector);
|
||||
|
||||
document
|
||||
.getElementById('btnExport')
|
||||
?.addEventListener('click', exportFullDashboard);
|
||||
|
||||
|
||||
document
|
||||
.querySelector('#chartCancel')
|
||||
?.addEventListener('click', closeChartSelector);
|
||||
|
||||
document
|
||||
.querySelector('#chartDownload')
|
||||
?.addEventListener('click', exportSelectedCharts);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
function openChartSelector() {
|
||||
|
||||
if (typeof charts === "undefined" || Object.keys(charts).length === 0) {
|
||||
if (typeof Charts.charts === "undefined" || Object.keys(Charts.charts).length === 0) {
|
||||
alert("Charts are still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
@@ -35,7 +61,7 @@ function openChartSelector() {
|
||||
|
||||
list.innerHTML = "";
|
||||
|
||||
Object.keys(charts).forEach(id => {
|
||||
Object.keys(Charts.charts).forEach(id => {
|
||||
list.innerHTML += `
|
||||
<label style="display:block;margin-bottom:6px;">
|
||||
<input type="checkbox" value="${id}" checked>
|
||||
@@ -99,7 +125,7 @@ async function exportSelectedCharts() {
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
background: "rgb(255, 255, 255)",
|
||||
color: "#fff",
|
||||
color: "#000000",
|
||||
padding: "20px",
|
||||
borderRadius: "10px",
|
||||
zIndex: "10000"
|
||||
@@ -141,7 +167,7 @@ async function exportSelectedCharts() {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const chart = charts[cb.value];
|
||||
const chart = Charts.charts[cb.value];
|
||||
if (!chart) continue;
|
||||
|
||||
items.push({
|
||||
@@ -185,7 +211,7 @@ async function exportSelectedCharts() {
|
||||
if (item.type === "map") {
|
||||
img = item.img;
|
||||
|
||||
width = cardWidth - 50;
|
||||
width = cardWidth - 60;
|
||||
height = width * 0.65;
|
||||
|
||||
} else {
|
||||
@@ -352,12 +378,7 @@ async function getMapImage() {
|
||||
totals[province].positive += Number(r.positive);
|
||||
});
|
||||
|
||||
function getColor(value) {
|
||||
if (value > 50) return "#b91c1c";
|
||||
if (value >= 10) return "#ef4444";
|
||||
if (value > 0) return "#fecaca";
|
||||
return "#f3f4f600";
|
||||
}
|
||||
|
||||
|
||||
window.map.eachLayer(layer => {
|
||||
if (!layer.toGeoJSON) return;
|
||||
@@ -478,9 +499,7 @@ async function getMapImage() {
|
||||
ctx.fillStyle = item.fillColor;
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = item.strokeColor;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ Chart.register({
|
||||
});
|
||||
Chart.register(ChartDataLabels);
|
||||
Chart.defaults.devicePixelRatio = 2;
|
||||
const charts = {};
|
||||
export const charts = {};
|
||||
|
||||
|
||||
function buildStackedChart(canvasId, labels, data) {
|
||||
@@ -147,7 +147,7 @@ function buildStackedChart(canvasId, labels, data) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function buildChart(id, type, labels, data) {
|
||||
export function buildChart(id, type, labels, data) {
|
||||
|
||||
const ctx = document.getElementById(id);
|
||||
if (!ctx) return;
|
||||
@@ -160,7 +160,7 @@ function buildChart(id, type, labels, data) {
|
||||
labels = [];
|
||||
data = [];
|
||||
}
|
||||
const isHorizontal = id === 'sexChart';
|
||||
const isHorizontal = id === 'sexChart' || id === 'influenzaSubtypeDistribution';
|
||||
const isAgeChart = id === 'ageChart';
|
||||
const isSentinelChart = id === 'sentinelChart';
|
||||
const options = {
|
||||
@@ -292,7 +292,7 @@ function buildChart(id, type, labels, data) {
|
||||
});
|
||||
charts[id].$totalTested = 0;
|
||||
}
|
||||
function buildMixedTrendChart(canvasId, labels, samples, lines) {
|
||||
export function buildMixedTrendChart(canvasId, labels, samples, lines) {
|
||||
|
||||
const ctx = document.getElementById(canvasId);
|
||||
if (!ctx) return;
|
||||
@@ -346,3 +346,160 @@ function buildMixedTrendChart(canvasId, labels, samples, lines) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function buildDistributionChart(
|
||||
id,
|
||||
type,
|
||||
rows,
|
||||
labelKey,
|
||||
valueKey = 'total',
|
||||
colorResolver = null
|
||||
) {
|
||||
|
||||
const labels = rows.map(r => r[labelKey]);
|
||||
buildChart(
|
||||
id,
|
||||
type,
|
||||
labels,
|
||||
rows.map(r => r[valueKey])
|
||||
);
|
||||
|
||||
if (!charts[id]) return;
|
||||
|
||||
charts[id].data.datasets[0].backgroundColor = labels.map(
|
||||
(label, index) =>
|
||||
colorResolver
|
||||
? colorResolver(label, index)
|
||||
: COLORS[index % COLORS.length]
|
||||
);
|
||||
|
||||
charts[id].update();
|
||||
}
|
||||
export function getSubtypeColor(label, index = 0) {
|
||||
|
||||
const specialColors = {
|
||||
'A/H5N1': '#dc2626',
|
||||
'Influenza': '#b90c00'
|
||||
};
|
||||
|
||||
return specialColors[label]
|
||||
|| COLORS[index % COLORS.length];
|
||||
}
|
||||
|
||||
export const COLORS = [
|
||||
|
||||
'#ef4444', // blue
|
||||
'#10b981', // emerald
|
||||
'#f59e0b', // amber
|
||||
'#ef4444', // red
|
||||
'#8b5cf6', // violet
|
||||
'#14b8a6', // teal
|
||||
'#f97316', // orange
|
||||
'#84cc16', // lime
|
||||
'#e11dba', // fuchsia
|
||||
'#f6f63b', // yellow
|
||||
|
||||
'#0ea5e9', // sky
|
||||
'#22c55e', // green
|
||||
'#a855f7', // purple
|
||||
'#ec4899', // pink
|
||||
'#06b6d4', // cyan
|
||||
'#65a30d', // olive
|
||||
'#dc2626', // dark red
|
||||
'#1d4ed8', // strong blue
|
||||
'#7c3aed', // deep violet
|
||||
'#059669', // dark emerald
|
||||
'#c2410c', // burnt orange
|
||||
'#be123c', // rose
|
||||
'#4338ca', // indigo
|
||||
'#0f766e', // dark teal
|
||||
'#9333ea', // bright purple
|
||||
'#15803d', // forest green
|
||||
'#ea580c', // deep orange
|
||||
'#0284c7', // ocean blue
|
||||
'#ca8a04', // mustard
|
||||
'#db2777' // magenta
|
||||
];
|
||||
export const SUBTYPE_COLORS = {
|
||||
'A/H1N1pdm': '#f0d401',
|
||||
'A/H3N2': '#00ffff',
|
||||
'A/H9N2': '#2563eb',
|
||||
'A/H5N1': '#dc2626',
|
||||
'A/Unsubtypable': '#f455d7',
|
||||
'B/Yam': '#9333ea',
|
||||
'B/Vic': '#086037',
|
||||
'B/Unsubtypable': '#66ff00',
|
||||
'B/Victoria': '#9333ea',
|
||||
'H1N1pdm': '#f0d401',
|
||||
'H3N2': '#00ffff',
|
||||
'H9N2': '#2563eb',
|
||||
'J.2.4': '#8c6060',
|
||||
'K': '#55f49a',
|
||||
};
|
||||
|
||||
export const SURVEILLANCE_COLORS = {
|
||||
'LBM': '#f0d401',
|
||||
'ILI': '#2563eb',
|
||||
'SARI': '#dc2626',
|
||||
'NDS': '#9333ea',
|
||||
'AFI': '#086037',
|
||||
'SEQ': '#66ff00'
|
||||
};
|
||||
export function buildPeriodLabels(data, startYear, endYear) {
|
||||
|
||||
const yearlyView = (endYear - startYear) >= 5;
|
||||
|
||||
if (yearlyView) {
|
||||
|
||||
const labels = [...new Set(
|
||||
data.flatMap(program =>
|
||||
program.map(row => row.year)
|
||||
)
|
||||
)].sort((a, b) => a - b);
|
||||
|
||||
return {
|
||||
yearlyView,
|
||||
labels
|
||||
};
|
||||
}
|
||||
|
||||
const labelsSet = new Set();
|
||||
|
||||
data.forEach(program => {
|
||||
program.forEach(row => {
|
||||
labelsSet.add(`${row.year}-${row.period}`);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
yearlyView,
|
||||
labels: [...labelsSet].sort((a, b) => {
|
||||
|
||||
const [yearA, weekA] = a.split('-').map(Number);
|
||||
const [yearB, weekB] = b.split('-').map(Number);
|
||||
|
||||
if (yearA !== yearB) {
|
||||
return yearA - yearB;
|
||||
}
|
||||
|
||||
return weekA - weekB;
|
||||
})
|
||||
};
|
||||
}
|
||||
export function buildPeriods(data, field, startYear, endYear) {
|
||||
|
||||
const yearlyView = (endYear - startYear) >= 5;
|
||||
|
||||
const periods = yearlyView
|
||||
? [...new Set(
|
||||
data.map(row => row[field].split('-')[0])
|
||||
)].sort((a, b) => a - b)
|
||||
: [...new Set(
|
||||
data.map(row => row[field])
|
||||
)].sort();
|
||||
|
||||
return {
|
||||
yearlyView,
|
||||
periods
|
||||
};
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
export const COLORS = [
|
||||
|
||||
'#ef4444', // blue
|
||||
'#10b981', // emerald
|
||||
'#f59e0b', // amber
|
||||
'#ef4444', // red
|
||||
'#8b5cf6', // violet
|
||||
'#14b8a6', // teal
|
||||
'#f97316', // orange
|
||||
'#84cc16', // lime
|
||||
'#e11dba', // fuchsia
|
||||
'#f6f63b', // yellow
|
||||
|
||||
'#0ea5e9', // sky
|
||||
'#22c55e', // green
|
||||
'#a855f7', // purple
|
||||
'#ec4899', // pink
|
||||
'#06b6d4', // cyan
|
||||
'#65a30d', // olive
|
||||
'#dc2626', // dark red
|
||||
'#1d4ed8', // strong blue
|
||||
'#7c3aed', // deep violet
|
||||
'#059669', // dark emerald
|
||||
'#c2410c', // burnt orange
|
||||
'#be123c', // rose
|
||||
'#4338ca', // indigo
|
||||
'#0f766e', // dark teal
|
||||
'#9333ea', // bright purple
|
||||
'#15803d', // forest green
|
||||
'#ea580c', // deep orange
|
||||
'#0284c7', // ocean blue
|
||||
'#ca8a04', // mustard
|
||||
'#db2777' // magenta
|
||||
];
|
||||
export const SUBTYPE_COLORS = {
|
||||
'A/H1N1pdm': '#f0d401',
|
||||
'A/H3N2': '#00ffff',
|
||||
'A/H9N2': '#2563eb',
|
||||
'A/H5N1': '#dc2626',
|
||||
'A/Unsubtypable': '#f455d7',
|
||||
'B/Yam': '#9333ea',
|
||||
'B/Vic': '#086037',
|
||||
'B/Unsubtypable': '#66ff00',
|
||||
'B/Victoria': '#9333ea',
|
||||
'H1N1pdm': '#f0d401',
|
||||
'H3N2': '#00ffff',
|
||||
'H9N2': '#2563eb',
|
||||
'J.2.4': '#8c6060',
|
||||
'K': '#55f49a',
|
||||
};
|
||||
|
||||
export const SURVEILLANCE_COLORS = {
|
||||
'LBM': '#f0d401',
|
||||
'ILI': '#2563eb',
|
||||
'SARI': '#dc2626',
|
||||
'NDS': '#9333ea',
|
||||
'AFI': '#086037',
|
||||
'SEQ': '#66ff00'
|
||||
};
|
||||
@@ -1,7 +1,5 @@
|
||||
import { COLORS, SUBTYPE_COLORS, SURVEILLANCE_COLORS } from "./globals.js";
|
||||
|
||||
import * as Charts from "./dashboard/globals.js";
|
||||
let trendChart;
|
||||
let influenzaSubtypeChart;
|
||||
let covidDistributedByAgeChart;
|
||||
let covidLineageFrequencyChart;
|
||||
let influenzaSubtypeFrequencyChart;
|
||||
@@ -31,6 +29,8 @@ function loadSummary() {
|
||||
if (item.percent_change > 0) trendColor = 'text-danger';
|
||||
if (item.percent_change < 0) trendColor = 'text-success';
|
||||
|
||||
|
||||
|
||||
html += `
|
||||
<div class="col-md-2 mb-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
@@ -39,7 +39,11 @@ function loadSummary() {
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
<div>
|
||||
<h6 class="fw-bold">${item.code} Cases</h6>
|
||||
<h6 class="fw-bold">
|
||||
${item.code === 'H5N1'
|
||||
? 'A/H5N1 Cases'
|
||||
: `${item.code} Cases`}
|
||||
</h6>
|
||||
<h3 class="mb-1">${item.current_total}</h3>
|
||||
<small class="text-muted">Last 7 days</small>
|
||||
</div>
|
||||
@@ -61,9 +65,11 @@ function loadSummary() {
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
window._summaryData = data;
|
||||
updateAlerts();
|
||||
|
||||
@@ -89,58 +95,15 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
|
||||
.then(data => {
|
||||
|
||||
if (trendChart) trendChart.destroy();
|
||||
const totalYears = endYear - startYear;
|
||||
const useYearlyView = totalYears >= 5;
|
||||
|
||||
// const labelsSet = new Set();
|
||||
|
||||
// Object.values(data).forEach(program => {
|
||||
// program.forEach(row => {
|
||||
// labelsSet.add(`${row.year}-${row.period}`);
|
||||
// });
|
||||
// });
|
||||
|
||||
// const labels = Array.from(labelsSet).sort((a, b) => {
|
||||
|
||||
// const [yearA, weekA] = a.split('-').map(Number);
|
||||
// const [yearB, weekB] = b.split('-').map(Number);
|
||||
|
||||
// if (yearA !== yearB) return yearA - yearB;
|
||||
// return weekA - weekB;
|
||||
|
||||
// });
|
||||
let labels = [];
|
||||
|
||||
if (useYearlyView) {
|
||||
|
||||
labels = [...new Set(
|
||||
Object.values(data)
|
||||
.flat()
|
||||
.map(row => row.year)
|
||||
)].sort((a, b) => a - b);
|
||||
|
||||
} else {
|
||||
|
||||
const labelsSet = new Set();
|
||||
|
||||
Object.values(data).forEach(program => {
|
||||
program.forEach(row => {
|
||||
labelsSet.add(`${row.year}-${row.period}`);
|
||||
});
|
||||
});
|
||||
|
||||
labels = Array.from(labelsSet).sort((a, b) => {
|
||||
|
||||
const [yearA, weekA] = a.split('-').map(Number);
|
||||
const [yearB, weekB] = b.split('-').map(Number);
|
||||
|
||||
if (yearA !== yearB) return yearA - yearB;
|
||||
|
||||
return weekA - weekB;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
const {
|
||||
yearlyView: useYearlyView,
|
||||
labels
|
||||
} = Charts.buildPeriodLabels(
|
||||
Object.values(data),
|
||||
startYear,
|
||||
endYear
|
||||
);
|
||||
|
||||
|
||||
const datasets = [];
|
||||
@@ -151,17 +114,14 @@ 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;
|
||||
// });
|
||||
|
||||
const values = labels.map(label => {
|
||||
|
||||
if (useYearlyView) {
|
||||
|
||||
return data[code]
|
||||
.filter(row => row.year == label)
|
||||
.reduce((sum, row) => sum + row.total, 0);
|
||||
.filter(row => String(row.year) === String(label))
|
||||
.reduce((sum, row) => sum + Number(row.total || 0), 0);
|
||||
|
||||
}
|
||||
|
||||
@@ -176,8 +136,8 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
|
||||
datasets.push({
|
||||
label: code,
|
||||
data: values,
|
||||
borderColor: SURVEILLANCE_COLORS[code],
|
||||
backgroundColor: SURVEILLANCE_COLORS[code],
|
||||
borderColor: Charts.SURVEILLANCE_COLORS[code],
|
||||
backgroundColor: Charts.SURVEILLANCE_COLORS[code],
|
||||
borderWidth: 3,
|
||||
pointRadius: 4,
|
||||
maxBarThickness: 50,
|
||||
@@ -201,6 +161,7 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
|
||||
return `${year}-W${String(week).padStart(2, '0')}`;
|
||||
});
|
||||
|
||||
|
||||
trendChart = new Chart(document.getElementById('trendChart'), {
|
||||
|
||||
type: 'line',
|
||||
@@ -244,7 +205,7 @@ function loadTrend(periodType, startYear, startWeek, endYear, endWeek) {
|
||||
}
|
||||
}
|
||||
});
|
||||
charts['trendChart'] = trendChart;
|
||||
Charts.charts['trendChart'] = trendChart;
|
||||
|
||||
});
|
||||
}
|
||||
@@ -254,88 +215,19 @@ function loadInfluenzaSubtypeDistribution(periodType, startYear, startWeek, endY
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
let displayLabels = data.map(item => item.subtype);
|
||||
let dataset = data.map(item => item.total);
|
||||
const displayData = data.map(item => ({
|
||||
...item,
|
||||
subtype: item.subtype === "B/VIC" ? "B/Vic" : item.subtype
|
||||
}));
|
||||
|
||||
// const colors = displayLabels.map(
|
||||
// label => SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
// );
|
||||
|
||||
if (influenzaSubtypeChart) influenzaSubtypeChart.destroy();
|
||||
influenzaSubtypeChart = new Chart(document.getElementById('influenzaSubtypeDistribution'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: displayLabels,
|
||||
datasets: [{
|
||||
data: dataset,
|
||||
backgroundColor: displayLabels.map(
|
||||
label => SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
),
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 20,
|
||||
right: 30,
|
||||
bottom: 20
|
||||
}
|
||||
},
|
||||
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right'
|
||||
},
|
||||
datalabels: {
|
||||
color: '#000000',
|
||||
borderRadius: 6,
|
||||
z: 1000,
|
||||
padding: {
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
},
|
||||
|
||||
font: {
|
||||
weight: 'bold',
|
||||
size: 12
|
||||
},
|
||||
formatter: (value) => value,
|
||||
anchor: 'end',
|
||||
align: 'end',
|
||||
offset: 4,
|
||||
clamp: true,
|
||||
clip: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Number of Positive Influenza Subtypes'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Influenza Subtypes'
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [ChartDataLabels]
|
||||
});
|
||||
charts['influenzaSubtypeDistribution'] = influenzaSubtypeChart;
|
||||
Charts.buildDistributionChart(
|
||||
'influenzaSubtypeDistribution',
|
||||
'bar',
|
||||
displayData,
|
||||
'subtype',
|
||||
'total',
|
||||
label => Charts.SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
@@ -344,83 +236,7 @@ function loadCovidDistributedByAgeGroup(periodType, startYear, startWeek, endYea
|
||||
fetch(`/api/dashboard/covid-distributed-by-age-group?period_type=${periodType}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
let displayLabels = data.map(item => item.age_group);
|
||||
let dataset = data.map(item => item.total);
|
||||
|
||||
|
||||
if (covidDistributedByAgeChart) covidDistributedByAgeChart.destroy();
|
||||
covidDistributedByAgeChart = new Chart(document.getElementById('covidDistributedByAgeGroup'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: displayLabels,
|
||||
datasets: [{
|
||||
label: 'Total Covid-19 Detected',
|
||||
data: dataset,
|
||||
backgroundColor: COLORS,
|
||||
maxBarThickness: 50
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 50,
|
||||
bottom: 10,
|
||||
}
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'bottom'
|
||||
},
|
||||
datalabels: {
|
||||
color: '#000',
|
||||
borderRadius: 6,
|
||||
z: 1000,
|
||||
padding: {
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
left: 10,
|
||||
right: 10
|
||||
},
|
||||
font: {
|
||||
weight: 'bold',
|
||||
size: 12
|
||||
},
|
||||
formatter: (value) => value,
|
||||
anchor: 'end',
|
||||
align: 'end',
|
||||
offset: 4,
|
||||
clamp: true,
|
||||
clip: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Patient Age Group'
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Number of Positive SARS-CoV-2'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
charts['covidDistributedByAgeGroup'] = covidDistributedByAgeChart;
|
||||
Charts.buildDistributionChart('covidDistributedByAgeGroup', 'bar', data, 'age_group');
|
||||
|
||||
});
|
||||
}
|
||||
@@ -430,36 +246,23 @@ function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, en
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
// const weeks = [...new Set(data.map(item => item.week))].sort();
|
||||
const totalYears = endYear - startYear;
|
||||
const useYearlyView = totalYears >= 5;
|
||||
const {
|
||||
yearlyView: useYearlyView,
|
||||
periods
|
||||
} = Charts.buildPeriods(
|
||||
data,
|
||||
'week',
|
||||
startYear,
|
||||
endYear
|
||||
);
|
||||
|
||||
let periods;
|
||||
|
||||
if (useYearlyView) {
|
||||
|
||||
periods = [...new Set(
|
||||
data.map(item => item.week.split('-')[0])
|
||||
)].sort((a, b) => a - b);
|
||||
|
||||
} else {
|
||||
|
||||
periods = [...new Set(
|
||||
data.map(item => item.week)
|
||||
)].sort();
|
||||
|
||||
}
|
||||
const lineages = [...new Set(data.map(item => item.lineage))];
|
||||
|
||||
|
||||
|
||||
const datasets = lineages.map((lineage, index) => {
|
||||
// const lineageData = weeks.map(week => {
|
||||
// const found = data.find(
|
||||
// item => item.week === week && item.lineage === lineage
|
||||
// );
|
||||
// return found ? found.total : 0;
|
||||
// });
|
||||
|
||||
const lineageData = periods.map(period => {
|
||||
|
||||
@@ -492,7 +295,7 @@ function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, en
|
||||
// borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
pointRadius: 0,
|
||||
backgroundColor: hexToRGBA(colors[index % colors.length], 0.3),
|
||||
backgroundColor: hexToRGBA(Charts.COLORS[index % Charts.COLORS.length], 0.3),
|
||||
stack: 'total'
|
||||
};
|
||||
});
|
||||
@@ -545,11 +348,8 @@ function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, en
|
||||
}
|
||||
}
|
||||
});
|
||||
charts['covidLineageFrequency'] = covidLineageFrequencyChart;
|
||||
Charts.charts['covidLineageFrequency'] = covidLineageFrequencyChart;
|
||||
|
||||
// -------------------------
|
||||
// Custom right-side scrollable legend
|
||||
// -------------------------
|
||||
const legendContainer = document.getElementById('legendContainer');
|
||||
legendContainer.innerHTML = '';
|
||||
datasets.forEach((dataset, index) => {
|
||||
@@ -602,31 +402,22 @@ function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
const totalYears = endYear - startYear;
|
||||
const useYearlyView = totalYears >= 5;
|
||||
|
||||
let periods;
|
||||
|
||||
if (useYearlyView) {
|
||||
|
||||
periods = [...new Set(
|
||||
data.map(item => item.week.split('-')[0])
|
||||
)].sort((a, b) => a - b);
|
||||
|
||||
} else {
|
||||
|
||||
periods = [...new Set(
|
||||
data.map(item => item.week)
|
||||
)].sort();
|
||||
|
||||
}
|
||||
const {
|
||||
yearlyView: useYearlyView,
|
||||
periods
|
||||
} = Charts.buildPeriods(
|
||||
data,
|
||||
'week',
|
||||
startYear,
|
||||
endYear
|
||||
);
|
||||
|
||||
const lineages = [...new Set(
|
||||
data.map(item => item.lineage)
|
||||
data.map(item => item.lineage === 'B/VIC' ? 'B/Vic' : item.lineage)
|
||||
)];
|
||||
|
||||
const lineageColors = lineages.map(
|
||||
label => SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
label => Charts.SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
);
|
||||
|
||||
const datasets = lineages.map((lineage, index) => {
|
||||
@@ -747,7 +538,7 @@ function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear
|
||||
|
||||
});
|
||||
|
||||
charts['influenzaSubtypeFrequency'] =
|
||||
Charts.charts['influenzaSubtypeFrequency'] =
|
||||
influenzaSubtypeFrequencyChart;
|
||||
|
||||
/*
|
||||
@@ -857,9 +648,9 @@ function hexToRGBA(hex, alpha) {
|
||||
}
|
||||
function updateAlerts() {
|
||||
|
||||
if (!window._summaryData || !window._provinceData) return;
|
||||
if (!window._summaryData || !window.latestProvinceData) return;
|
||||
|
||||
const raw = buildAlerts(window._summaryData, window._provinceData);
|
||||
const raw = buildAlerts(window._summaryData, window.latestProvinceData);
|
||||
const finalAlerts = processAlerts(raw);
|
||||
|
||||
renderAlerts(finalAlerts);
|
||||
@@ -1088,6 +879,7 @@ function normalizeProvince(name, validSet) {
|
||||
|
||||
return match || null;
|
||||
}
|
||||
window.normalizeProvince = normalizeProvince;
|
||||
function getRadius(total) {
|
||||
if (!total) return 0;
|
||||
const r = Math.sqrt(total);
|
||||
@@ -1095,10 +887,10 @@ function getRadius(total) {
|
||||
}
|
||||
|
||||
function getPositivityColor(subtype) {
|
||||
return SUBTYPE_COLORS[subtype] || "#9ca3af";
|
||||
return Charts.SUBTYPE_COLORS[subtype] || "#9ca3af";
|
||||
}
|
||||
const getColorByPathogen = name =>
|
||||
SUBTYPE_COLORS[name] || '#9ca3af';
|
||||
Charts.SUBTYPE_COLORS[name] || '#9ca3af';
|
||||
|
||||
function addPositivityLegend() {
|
||||
|
||||
@@ -1157,7 +949,7 @@ function addPositivityLegend() {
|
||||
>
|
||||
|
||||
<span style="
|
||||
background:${SUBTYPE_COLORS[item]};
|
||||
background:${Charts.SUBTYPE_COLORS[item]};
|
||||
width:10px;
|
||||
height:10px;
|
||||
display:inline-block;
|
||||
@@ -1237,7 +1029,7 @@ function loadProvinceMap(
|
||||
map = L.map('provinceMap')
|
||||
.setView([12.7, 104.9], 7);
|
||||
|
||||
|
||||
window.map = map;
|
||||
|
||||
L.tileLayer(
|
||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
@@ -1258,7 +1050,7 @@ function loadProvinceMap(
|
||||
])
|
||||
.then(([geojson, data]) => {
|
||||
|
||||
window._provinceData = data;
|
||||
window.latestProvinceData = data;
|
||||
|
||||
updateAlerts();
|
||||
|
||||
@@ -1268,7 +1060,7 @@ function loadProvinceMap(
|
||||
)
|
||||
);
|
||||
|
||||
Object.keys(SUBTYPE_COLORS)
|
||||
Object.keys(Charts.SUBTYPE_COLORS)
|
||||
.forEach(subtype => {
|
||||
|
||||
subtypeLayers[subtype] =
|
||||
@@ -1331,7 +1123,7 @@ function loadProvinceMap(
|
||||
">
|
||||
|
||||
<span>
|
||||
${r.pathogen_name}
|
||||
${r.pathogen_name === 'B/VIC' ? 'B/Vic' : r.pathogen_name}
|
||||
</span>
|
||||
|
||||
<span style="font-weight:600">
|
||||
@@ -1411,17 +1203,17 @@ function loadProvinceMap(
|
||||
{
|
||||
radius: getRadius(row.total),
|
||||
fillColor: getColorByPathogen(
|
||||
row.pathogen_name
|
||||
row.pathogen_name === 'B/VIC' ? 'B/Vic' : row.pathogen_name
|
||||
),
|
||||
fillOpacity: 0.85,
|
||||
stroke: false
|
||||
}
|
||||
);
|
||||
|
||||
if (subtypeLayers[row.pathogen_name]) {
|
||||
if (subtypeLayers[row.pathogen_name === 'B/VIC' ? 'B/Vic' : row.pathogen_name]) {
|
||||
marker.addTo(
|
||||
subtypeLayers[
|
||||
row.pathogen_name
|
||||
row.pathogen_name === 'B/VIC' ? 'B/Vic' : row.pathogen_name
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { COLORS, SUBTYPE_COLORS } from "./globals.js";
|
||||
import * as Charts from "./dashboard/globals.js";
|
||||
const standardPrograms = ['SARI', 'ILI', 'LBM', 'AFI', 'NDS'];
|
||||
const programCode = (window.PROGRAM_CODE || '').trim().toUpperCase();
|
||||
|
||||
@@ -31,34 +31,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
});
|
||||
function buildDistributionChart(
|
||||
id,
|
||||
type,
|
||||
rows,
|
||||
labelKey,
|
||||
valueKey = 'total',
|
||||
colorResolver = null
|
||||
) {
|
||||
|
||||
const labels = rows.map(r => r[labelKey]);
|
||||
buildChart(
|
||||
id,
|
||||
type,
|
||||
labels,
|
||||
rows.map(r => r[valueKey])
|
||||
);
|
||||
|
||||
if (!charts[id]) return;
|
||||
|
||||
charts[id].data.datasets[0].backgroundColor = labels.map(
|
||||
(label, index) =>
|
||||
colorResolver
|
||||
? colorResolver(label, index)
|
||||
: COLORS[index % COLORS.length]
|
||||
);
|
||||
|
||||
charts[id].update();
|
||||
}
|
||||
function renderTrend(valueId, changeId, current, previous, suffix = '') {
|
||||
|
||||
const valueEl = document.getElementById(valueId);
|
||||
@@ -90,16 +63,7 @@ function renderTrend(valueId, changeId, current, previous, suffix = '') {
|
||||
changeEl.className = "text-muted";
|
||||
}
|
||||
}
|
||||
function getSubtypeColor(label, index = 0) {
|
||||
|
||||
const specialColors = {
|
||||
'A/H5N1': '#dc2626',
|
||||
'Influenza': '#b90c00'
|
||||
};
|
||||
|
||||
return specialColors[label]
|
||||
|| COLORS[index % COLORS.length];
|
||||
}
|
||||
function renderSummary(summary = {}) {
|
||||
|
||||
const mappings = [
|
||||
@@ -127,41 +91,41 @@ function renderDashboard(data = {}) {
|
||||
renderSummary(data.summary);
|
||||
renderProvinceHeatmap(data.province_distribution || []);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'pathogenChart',
|
||||
'doughnut',
|
||||
(data.pathogen_distribution || [])
|
||||
.sort((a, b) => b.total - a.total),
|
||||
'pathogen',
|
||||
'total',
|
||||
getSubtypeColor
|
||||
Charts.getSubtypeColor
|
||||
|
||||
);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'ageChart',
|
||||
'doughnut',
|
||||
data.age_distribution || [],
|
||||
'age_group'
|
||||
);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'sexChart',
|
||||
'bar',
|
||||
data.sex_distribution || [],
|
||||
'patient_sex'
|
||||
);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'subtypeChart',
|
||||
'bar',
|
||||
data.subtype_distribution || [],
|
||||
'subtype',
|
||||
'total',
|
||||
getSubtypeColor
|
||||
Charts.getSubtypeColor
|
||||
);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'sentinelChart',
|
||||
'pie',
|
||||
data.sentinel_sites || [],
|
||||
@@ -178,21 +142,21 @@ function renderAFIDashboard(data = {}) {
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_1,
|
||||
'afiSection1Trend',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'trend'
|
||||
);
|
||||
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_2,
|
||||
'afiSection2Trend',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'trend'
|
||||
);
|
||||
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_3,
|
||||
'afiSection3Trend',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'trend'
|
||||
);
|
||||
|
||||
@@ -200,21 +164,21 @@ function renderAFIDashboard(data = {}) {
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_1,
|
||||
'afiPcrChart',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'donut'
|
||||
);
|
||||
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_2,
|
||||
'afiMultiplexChart',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'donut'
|
||||
);
|
||||
|
||||
renderAFITrend(
|
||||
data.afi_case_trend.section_3,
|
||||
'afiElisaChart',
|
||||
COLORS,
|
||||
Chart.COLORS,
|
||||
'donut'
|
||||
);
|
||||
|
||||
@@ -466,7 +430,7 @@ function renderProgramTrend(rows = []) {
|
||||
|
||||
if (!rows.length) {
|
||||
|
||||
buildMixedTrendChart(
|
||||
Charts.buildMixedTrendChart(
|
||||
'trendChart',
|
||||
[],
|
||||
[],
|
||||
@@ -720,7 +684,7 @@ function renderProgramTrend(rows = []) {
|
||||
|
||||
}
|
||||
|
||||
buildMixedTrendChart(
|
||||
Charts.buildMixedTrendChart(
|
||||
'trendChart',
|
||||
labels,
|
||||
samples,
|
||||
@@ -758,7 +722,7 @@ function renderAFITrend(
|
||||
|
||||
if (type === 'donut') {
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
canvasId,
|
||||
'doughnut',
|
||||
[],
|
||||
@@ -768,7 +732,7 @@ function renderAFITrend(
|
||||
|
||||
} else {
|
||||
|
||||
buildMixedTrendChart(
|
||||
Charts.buildMixedTrendChart(
|
||||
canvasId,
|
||||
[],
|
||||
[],
|
||||
@@ -862,7 +826,7 @@ function renderAFITrend(
|
||||
total
|
||||
}));
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
canvasId,
|
||||
'doughnut',
|
||||
donutRows,
|
||||
@@ -886,12 +850,12 @@ function renderAFITrend(
|
||||
0
|
||||
);
|
||||
|
||||
if (charts[canvasId]) {
|
||||
if (Charts.charts[canvasId]) {
|
||||
|
||||
charts[canvasId].$afiTotalCases =
|
||||
Charts.charts[canvasId].$afiTotalCases =
|
||||
donutTotal;
|
||||
|
||||
charts[canvasId].update();
|
||||
Charts.charts[canvasId].update();
|
||||
|
||||
}
|
||||
|
||||
@@ -941,8 +905,8 @@ function renderAFITrend(
|
||||
}),
|
||||
|
||||
color:
|
||||
COLORS[
|
||||
i % COLORS.length
|
||||
Charts.COLORS[
|
||||
i % Charts.COLORS.length
|
||||
]
|
||||
|
||||
})
|
||||
@@ -954,7 +918,7 @@ function renderAFITrend(
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
buildMixedTrendChart(
|
||||
Charts.buildMixedTrendChart(
|
||||
canvasId,
|
||||
labels,
|
||||
totalCases,
|
||||
@@ -965,7 +929,7 @@ function renderAFITrend(
|
||||
|
||||
|
||||
function renderPathogenChart(rows = []) {
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'pathogenChart',
|
||||
'doughnut',
|
||||
rows,
|
||||
@@ -973,7 +937,7 @@ function renderPathogenChart(rows = []) {
|
||||
);
|
||||
}
|
||||
function renderSentinel(rows = []) {
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'sentinelChart',
|
||||
'pie',
|
||||
rows,
|
||||
@@ -982,27 +946,26 @@ function renderSentinel(rows = []) {
|
||||
}
|
||||
function renderSubtypeChart(rows = []) {
|
||||
|
||||
console.log('renderSubtypeChart');
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'subtypeChart',
|
||||
'bar',
|
||||
rows,
|
||||
'subtype',
|
||||
'total',
|
||||
getSubtypeColor
|
||||
Charts.getSubtypeColor
|
||||
);
|
||||
}
|
||||
function renderDemographics(data = {}) {
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'ageChart',
|
||||
'doughnut',
|
||||
data.age_distribution || [],
|
||||
'age_group'
|
||||
);
|
||||
|
||||
buildDistributionChart(
|
||||
Charts.buildDistributionChart(
|
||||
'sexChart',
|
||||
'bar',
|
||||
data.sex_distribution || [],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { COLORS, SUBTYPE_COLORS } from "./globals.js";
|
||||
import * as Charts from "./dashboard/globals.js";
|
||||
|
||||
let sequencingTotalChart;
|
||||
let covidLineageFrequencyChart;
|
||||
@@ -70,7 +70,7 @@ function renderSequencingTotalChart(rows) {
|
||||
|
||||
}
|
||||
});
|
||||
charts['sequencingTotalChart'] = sequencingTotalChart;
|
||||
Charts.charts['sequencingTotalChart'] = sequencingTotalChart;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, en
|
||||
// borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
pointRadius: 0,
|
||||
backgroundColor: hexToRGBA(COLORS[index % COLORS.length], 0.3),
|
||||
backgroundColor: hexToRGBA(Charts.COLORS[index % Charts.COLORS.length], 0.3),
|
||||
stack: 'total'
|
||||
};
|
||||
});
|
||||
@@ -203,7 +203,7 @@ function loadCovidLineageFrequency(periodType, startYear, startWeek, endYear, en
|
||||
}
|
||||
}
|
||||
});
|
||||
charts['covidLineageFrequency'] = covidLineageFrequencyChart;
|
||||
Charts.charts['covidLineageFrequency'] = covidLineageFrequencyChart;
|
||||
|
||||
// -------------------------
|
||||
// Custom right-side scrollable legend
|
||||
@@ -284,7 +284,7 @@ function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear
|
||||
)];
|
||||
|
||||
const lineageColors = lineages.map(
|
||||
label => SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
label => Charts.SUBTYPE_COLORS[label] || '#9ca3af'
|
||||
);
|
||||
|
||||
const datasets = lineages.map((lineage, index) => {
|
||||
@@ -405,7 +405,7 @@ function loadInfluenzaSubtypeFrequency(periodType, startYear, startWeek, endYear
|
||||
|
||||
});
|
||||
|
||||
charts['influenzaSubtypeFrequency'] =
|
||||
Charts.charts['influenzaSubtypeFrequency'] =
|
||||
influenzaSubtypeFrequencyChart;
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400 p-4">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -60,14 +60,14 @@
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@if (Route::has('password.request'))
|
||||
<!-- @if (Route::has('password.request'))
|
||||
<a
|
||||
href="{{ route('password.request') }}"
|
||||
class="text-sm text-green-600 hover:text-green-700"
|
||||
>
|
||||
Forgot password?
|
||||
</a>
|
||||
@endif
|
||||
@endif -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||
|
||||
<script src="/js/dashboard/filter.js"></script>
|
||||
<script src="/js/dashboard/charts.js"></script>
|
||||
<script src="/js/dashboard/export.js"></script>
|
||||
<script src="/js/dashboard/globals.js" type="module"></script>
|
||||
<script src="/js/dashboard/export.js" type="module"></script>
|
||||
|
||||
|
||||
<style>
|
||||
@@ -278,8 +278,8 @@
|
||||
</button>
|
||||
|
||||
<div id="exportItems" class="align-items-center gap-2">
|
||||
<button class="btn btn-sm btn-light" onclick="openChartSelector()">Charts</button>
|
||||
<button class="btn btn-sm btn-light" onclick="exportFullDashboard()">Screen</button>
|
||||
<button class="btn btn-sm btn-light" id="btnCharts">Charts</button>
|
||||
<button class="btn btn-sm btn-light" id="btnExport">Screen</button>
|
||||
<button class="btn btn-sm btn-light" onclick="window.print()">Print</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" id="exportClose">✕</button>
|
||||
</div>
|
||||
@@ -290,8 +290,8 @@
|
||||
<div id="chartList"></div>
|
||||
|
||||
<div class="mt-3 d-flex justify-content-end gap-2">
|
||||
<button onclick="closeChartSelector()">Cancel</button>
|
||||
<button onclick="exportSelectedCharts()">Download PDF</button>
|
||||
<button id="chartCancel" class="btn btn-sm btn-secondary">Cancel</button>
|
||||
<button id="chartDownload" class="btn btn-sm btn-primary">Download PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -328,7 +328,14 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
updateLastUpdated();
|
||||
|
||||
const lastUpdated = localStorage.getItem('dashboardLastUpdated');
|
||||
|
||||
if (lastUpdated) {
|
||||
document.getElementById('lastUpdated').innerHTML =
|
||||
`Last update: ${lastUpdated}`;
|
||||
}
|
||||
|
||||
});
|
||||
window.addEventListener("click", (e) => {
|
||||
const modal = document.getElementById("chartModal");
|
||||
@@ -360,7 +367,21 @@
|
||||
.then(res => res.json())
|
||||
.then(() => {
|
||||
|
||||
updateLastUpdated();
|
||||
const now = new Date();
|
||||
|
||||
const time = now.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
const date = now.toISOString().split('T')[0];
|
||||
|
||||
const timestamp = `${time} | ${date}`;
|
||||
|
||||
localStorage.setItem(
|
||||
'dashboardLastUpdated',
|
||||
timestamp
|
||||
);
|
||||
|
||||
location.reload();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user