From f518d7d184821fe378a3c57836c05c91fb6386e4 Mon Sep 17 00:00:00 2001 From: Khantey Long Date: Mon, 22 Jun 2026 08:47:13 +0700 Subject: [PATCH] fix bugs --- dashboard/app/Services/DashboardService.php | 366 +++++++++++------- dashboard/public/js/dashboard/charts.js | 2 +- dashboard/public/js/dashboard/export.js | 13 +- dashboard/public/js/globals.js | 2 +- dashboard/public/js/program.js | 218 +++++------ .../components/application-logo.blade.php | 7 +- .../resources/views/layouts/app.blade.php | 1 + 7 files changed, 346 insertions(+), 263 deletions(-) diff --git a/dashboard/app/Services/DashboardService.php b/dashboard/app/Services/DashboardService.php index b77fa0c..ce8fa74 100644 --- a/dashboard/app/Services/DashboardService.php +++ b/dashboard/app/Services/DashboardService.php @@ -96,21 +96,21 @@ class DashboardService $q->whereRaw( "(year_data > ? - OR ( - year_data = ? - AND week_data >= ? - ) - )", - [$startYear, $startYear, $startWeek] - ) + OR ( + year_data = ? + AND week_data >= ? + ) + )", + [$startYear, $startYear, $startWeek] + ) ->whereRaw( "(year_data < ? - OR ( - year_data = ? - AND week_data <= ? - ) - )", + OR ( + year_data = ? + AND week_data <= ? + ) + )", [$endYear, $endYear, $endWeek] ); }) @@ -121,38 +121,38 @@ class DashboardService ->selectRaw(" - surveillance_cases.year_data as year, - surveillance_cases.week_data as period, + surveillance_cases.year_data as year, + surveillance_cases.week_data as period, - case_lab_results.pathogen_name as pathogen, - case_lab_results.subtype, + case_lab_results.pathogen_name as pathogen, + case_lab_results.subtype, - CASE + CASE - WHEN LOWER(case_lab_results.pathogen_name) - LIKE '%influenza%' + WHEN LOWER(case_lab_results.pathogen_name) + LIKE '%influenza%' - OR LOWER(case_lab_results.pathogen_name) - LIKE '%covid%' + OR LOWER(case_lab_results.pathogen_name) + LIKE '%covid%' - OR LOWER(case_lab_results.pathogen_name) - LIKE '%sars-cov%' + OR LOWER(case_lab_results.pathogen_name) + LIKE '%sars-cov%' - THEN 'section_1' + THEN 'section_1' - WHEN LOWER(case_lab_results.indicator) - LIKE '%serum%' + WHEN LOWER(case_lab_results.indicator) + LIKE '%serum%' - THEN 'section_3' + THEN 'section_3' - ELSE 'section_2' + ELSE 'section_2' - END as afi_section, + END as afi_section, - COUNT(DISTINCT surveillance_cases.lab_code) - as total_positive + COUNT(DISTINCT surveillance_cases.lab_code) + as total_positive - ") + ") ->groupBy( 'surveillance_cases.year_data', @@ -184,6 +184,7 @@ class DashboardService ]; } + public function afiCaseTrend( $surveillanceId, $startYear, @@ -194,14 +195,42 @@ class DashboardService /* |-------------------------------------------------------------------------- - | TOTAL CASES BY SECTION + | AFI SECTION SQL + |-------------------------------------------------------------------------- + */ + + $afiSectionSql = " + + CASE + + WHEN LOWER(case_lab_results.indicator) + LIKE '%influenza%' + + OR LOWER(case_lab_results.indicator) + LIKE '%covid%' + + THEN 'section_1' + + WHEN LOWER(case_lab_results.indicator) + LIKE '%serum%' + + THEN 'section_3' + + ELSE 'section_2' + + END + + "; + + /* + |-------------------------------------------------------------------------- + | ALL TESTED CASES |-------------------------------------------------------------------------- | - | Count DISTINCT lab_code PER SECTION - | - | section_1 = Influenza / Covid - | section_2 = PCR - | section_3 = Serum + | THIS MUST INCLUDE: + | - positive + | - negative + | - weeks with no positives | */ @@ -226,7 +255,11 @@ class DashboardService AND week_data >= ? ) )", - [$startYear, $startYear, $startWeek] + [ + $startYear, + $startYear, + $startWeek + ] ) ->whereRaw( @@ -236,38 +269,27 @@ class DashboardService AND week_data <= ? ) )", - [$endYear, $endYear, $endWeek] + [ + $endYear, + $endYear, + $endWeek + ] ); + }) ->selectRaw(" - surveillance_cases.year_data as year, - surveillance_cases.week_data as period, + surveillance_cases.year_data as year, + surveillance_cases.week_data as period, - CASE + {$afiSectionSql} + as afi_section, - WHEN LOWER(case_lab_results.indicator) - LIKE '%influenza%' + COUNT(DISTINCT case_lab_results.lab_code) + as total_cases - OR LOWER(case_lab_results.indicator) - LIKE '%covid%' - - THEN 'section_1' - - WHEN LOWER(case_lab_results.indicator) - LIKE '%serum%' - - THEN 'section_3' - - ELSE 'section_2' - - END as afi_section, - - COUNT(DISTINCT case_lab_results.lab_code) - as total_cases - - ") + ") ->groupBy( 'surveillance_cases.year_data', @@ -275,16 +297,29 @@ class DashboardService 'afi_section' ) - ->get() + ->orderBy('surveillance_cases.year_data') - ->keyBy( - fn($r) => - $r->year . - '-' . - $r->period . - '-' . - $r->afi_section - ); + ->orderBy('surveillance_cases.week_data') + + ->get(); + + /* + |-------------------------------------------------------------------------- + | KEYED TOTALS + |-------------------------------------------------------------------------- + */ + + $totalCasesKeyed = $totalCases->keyBy( + + fn($r) => + + $r->year . + '-' . + $r->period . + '-' . + $r->afi_section + + ); /* |-------------------------------------------------------------------------- @@ -313,7 +348,11 @@ class DashboardService AND week_data >= ? ) )", - [$startYear, $startYear, $startWeek] + [ + $startYear, + $startYear, + $startWeek + ] ) ->whereRaw( @@ -323,54 +362,52 @@ class DashboardService AND week_data <= ? ) )", - [$endYear, $endYear, $endWeek] + [ + $endYear, + $endYear, + $endWeek + ] ); + }) ->where('case_lab_results.is_positive', 1) - ->whereNotNull('case_lab_results.pathogen_name') + ->whereNotNull( + 'case_lab_results.pathogen_name' + ) + + ->where( + 'case_lab_results.pathogen_name', + '!=', + '' + ) ->selectRaw(" - surveillance_cases.year_data as year, - surveillance_cases.week_data as period, + surveillance_cases.year_data as year, + surveillance_cases.week_data as period, - CASE + CASE - WHEN LOWER(case_lab_results.pathogen_name) - LIKE '%influenza%' - THEN 'Influenza' + WHEN LOWER(case_lab_results.pathogen_name) + LIKE '%influenza%' - ELSE case_lab_results.pathogen_name + THEN 'Influenza' - END as pathogen, + ELSE case_lab_results.pathogen_name - case_lab_results.subtype, + END as pathogen, - CASE + case_lab_results.subtype, - WHEN LOWER(case_lab_results.indicator) - LIKE '%influenza%' + {$afiSectionSql} + as afi_section, - OR LOWER(case_lab_results.indicator) - LIKE '%covid%' + COUNT(DISTINCT case_lab_results.lab_code) + as total_positive - THEN 'section_1' - - WHEN LOWER(case_lab_results.indicator) - LIKE '%serum%' - - THEN 'section_3' - - ELSE 'section_2' - - END as afi_section, - - COUNT(DISTINCT case_lab_results.lab_code) - as total_positive - - ") + ") ->groupBy( 'surveillance_cases.year_data', @@ -386,9 +423,10 @@ class DashboardService ->get() - ->map(function ($r) use ($totalCases) { + ->map(function ($r) use ($totalCasesKeyed) { $key = + $r->year . '-' . $r->period . @@ -396,36 +434,92 @@ class DashboardService $r->afi_section; $r->total_cases = - $totalCases[$key]->total_cases ?? 0; + + $totalCasesKeyed[$key] + ->total_cases ?? 0; $r->positivity_rate = + $r->total_cases > 0 ? round( - ($r->total_positive / $r->total_cases) * 100, + ( + $r->total_positive + / $r->total_cases + ) * 100, 1 ) : 0; return $r; + }); + /* + |-------------------------------------------------------------------------- + | RETURN + |-------------------------------------------------------------------------- + */ + return [ - 'section_1' => $rows - ->where('afi_section', 'section_1') - ->values(), + 'section_1' => [ - 'section_2' => $rows - ->where('afi_section', 'section_2') - ->values(), + 'rows' => $rows + ->where( + 'afi_section', + 'section_1' + ) + ->values(), - 'section_3' => $rows - ->where('afi_section', 'section_3') - ->values() + 'totals' => $totalCases + ->where( + 'afi_section', + 'section_1' + ) + ->values() + + ], + + 'section_2' => [ + + 'rows' => $rows + ->where( + 'afi_section', + 'section_2' + ) + ->values(), + + 'totals' => $totalCases + ->where( + 'afi_section', + 'section_2' + ) + ->values() + + ], + + 'section_3' => [ + + 'rows' => $rows + ->where( + 'afi_section', + 'section_3' + ) + ->values(), + + 'totals' => $totalCases + ->where( + 'afi_section', + 'section_3' + ) + ->values() + + ] ]; + } public function programSummaryFast($surveillanceId, $year = null, $week = null, $dateFrom = null, $dateTo = null) { @@ -445,29 +539,29 @@ class DashboardService } $row = $query->selectRaw(" - COUNT(DISTINCT surveillance_cases.lab_code) as total_cases, + COUNT(DISTINCT surveillance_cases.lab_code) as total_cases, - COUNT(DISTINCT CASE - WHEN case_lab_results.is_positive = 1 - THEN surveillance_cases.lab_code - END) as overall_positive, + COUNT(DISTINCT CASE + WHEN case_lab_results.is_positive = 1 + THEN surveillance_cases.lab_code + END) as overall_positive, - COUNT(DISTINCT CASE - WHEN case_lab_results.is_positive = 1 - AND ( - LOWER(case_lab_results.pathogen_name) LIKE '%influenza%' - ) - THEN surveillance_cases.lab_code - END) as influenza_positive, + COUNT(DISTINCT CASE + WHEN case_lab_results.is_positive = 1 + AND ( + LOWER(case_lab_results.pathogen_name) LIKE '%influenza%' + ) + THEN surveillance_cases.lab_code + END) as influenza_positive, - COUNT(DISTINCT CASE - WHEN case_lab_results.is_positive = 1 - AND ( - LOWER(case_lab_results.pathogen_name) LIKE '%covid%' - ) - THEN surveillance_cases.lab_code - END) as covid_positive - ")->first(); + COUNT(DISTINCT CASE + WHEN case_lab_results.is_positive = 1 + AND ( + LOWER(case_lab_results.pathogen_name) LIKE '%covid%' + ) + THEN surveillance_cases.lab_code + END) as covid_positive + ")->first(); if (!$row || $row->total_cases == 0) { return [ diff --git a/dashboard/public/js/dashboard/charts.js b/dashboard/public/js/dashboard/charts.js index 2ce449a..f410d11 100644 --- a/dashboard/public/js/dashboard/charts.js +++ b/dashboard/public/js/dashboard/charts.js @@ -318,7 +318,7 @@ function buildMixedTrendChart(canvasId, labels, samples, lines) { type: 'bar', label: 'Total Cases', data: samples, - backgroundColor: '#d34646', + backgroundColor: '#007ce8', maxBarThickness: 60, yAxisID: 'y', diff --git a/dashboard/public/js/dashboard/export.js b/dashboard/public/js/dashboard/export.js index ac63c2d..6fe4b37 100644 --- a/dashboard/public/js/dashboard/export.js +++ b/dashboard/public/js/dashboard/export.js @@ -242,7 +242,7 @@ function prepareMapForExport() { [14.7, 107.6] ]); - map.fitBounds(bounds); + window.map.fitBounds(bounds); } async function exportFullDashboard() { @@ -273,7 +273,7 @@ async function exportFullDashboard() { if (mapEl && originalMapHTML !== null) { mapEl.innerHTML = originalMapHTML; - map.invalidateSize(); + window.map.invalidateSize(); } const img = canvas.toDataURL("image/jpeg", 0.95); @@ -329,8 +329,8 @@ async function getMapImage() { ctx.fillStyle = "#ffffff"; ctx.fillRect(0, 0, width, height); - const zoom = map.getZoom(); - const projection = map.options.crs; + const zoom = window.map.getZoom(); + const projection = window.map.options.crs; const projectedRings = []; @@ -341,8 +341,7 @@ async function getMapImage() { const rows = window.latestProvinceData || []; rows.forEach(r => { - const province = normalizeProvince(r.patient_province, window.validProvinces); - console.log(province, totals[province]); + const province = window.normalizeProvince(r.patient_province, window.validProvinces); if (!province) return; if (!totals[province]) { @@ -360,7 +359,7 @@ async function getMapImage() { return "#f3f4f600"; } - map.eachLayer(layer => { + window.map.eachLayer(layer => { if (!layer.toGeoJSON) return; if (layer instanceof L.CircleMarker) { diff --git a/dashboard/public/js/globals.js b/dashboard/public/js/globals.js index 6996409..6e20f1c 100644 --- a/dashboard/public/js/globals.js +++ b/dashboard/public/js/globals.js @@ -1,6 +1,6 @@ export const COLORS = [ - '#2563eb', // blue + '#ef4444', // blue '#10b981', // emerald '#f59e0b', // amber '#ef4444', // red diff --git a/dashboard/public/js/program.js b/dashboard/public/js/program.js index a3eabab..9555b56 100644 --- a/dashboard/public/js/program.js +++ b/dashboard/public/js/program.js @@ -93,7 +93,8 @@ function renderTrend(valueId, changeId, current, previous, suffix = '') { function getSubtypeColor(label, index = 0) { const specialColors = { - 'A/H5N1': '#dc2626' + 'A/H5N1': '#dc2626', + 'Influenza': '#b90c00' }; return specialColors[label] @@ -131,7 +132,10 @@ function renderDashboard(data = {}) { 'doughnut', (data.pathogen_distribution || []) .sort((a, b) => b.total - a.total), - 'pathogen' + 'pathogen', + 'total', + getSubtypeColor + ); buildDistributionChart( @@ -254,6 +258,8 @@ function normalizeProvince(name, validSet) { return match || null; } +window.normalizeProvince = normalizeProvince; + function renderProvinceHeatmap(rows = []) { window.latestProvinceData = rows; @@ -264,7 +270,7 @@ function renderProvinceHeatmap(rows = []) { map = L.map('provinceMap') .setView([12.7, 104.9], 7); - + window.map = map; L.tileLayer( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { @@ -593,7 +599,7 @@ function renderProgramTrend(rows = []) { }), - color: '#1976D2' + color: '#d21919' }, { @@ -722,20 +728,33 @@ function renderProgramTrend(rows = []) { ); } + function renderAFITrend( - rows = [], + data = {}, canvasId, COLORS, type = 'trend' ) { + /* + |-------------------------------------------------------------------------- + | DATA + |-------------------------------------------------------------------------- + */ + + const rows = + data.rows || []; + + const totals = + data.totals || []; + /* |-------------------------------------------------------------------------- | EMPTY |-------------------------------------------------------------------------- */ - if (!rows.length) { + if (!totals.length) { if (type === 'donut') { @@ -761,6 +780,57 @@ function renderAFITrend( return; } + /* + |-------------------------------------------------------------------------- + | LABELS + |-------------------------------------------------------------------------- + */ + + const labels = [...new Set( + + totals.map(r => + + `${r.year}-W${r.period}` + + ) + + )].sort((a, b) => { + + const [yearA, weekA] = + a.split('-W').map(Number); + + const [yearB, weekB] = + b.split('-W').map(Number); + + if (yearA !== yearB) { + return yearA - yearB; + } + + return weekA - weekB; + + }); + + /* + |-------------------------------------------------------------------------- + | TOTAL CASES (BLUE BARS) + |-------------------------------------------------------------------------- + */ + + const totalCases = labels.map(label => { + + const row = totals.find(r => + + `${r.year}-W${r.period}` + === label + + ); + + return Number( + row?.total_cases || 0 + ); + + }); + /* |-------------------------------------------------------------------------- | DONUT @@ -800,16 +870,26 @@ function renderAFITrend( 'total' ); - if (charts[canvasId]) { + /* + |-------------------------------------------------------------------------- + | DONUT CENTER TOTAL + |-------------------------------------------------------------------------- + | + | MUST MATCH SUM OF BLUE BARS + | + */ - const totalCases = rows.reduce( - (sum, r) => - sum + Number(r.total_cases || 0), + const donutTotal = + + totalCases.reduce( + (a, b) => a + b, 0 ); + if (charts[canvasId]) { + charts[canvasId].$afiTotalCases = - totalCases; + donutTotal; charts[canvasId].update(); @@ -818,90 +898,6 @@ function renderAFITrend( return; } - /* - |-------------------------------------------------------------------------- - | YEARLY VIEW - |-------------------------------------------------------------------------- - */ - - const years = [...new Set( - rows.map(r => Number(r.year)) - )]; - - const totalYears = - Math.max(...years) - Math.min(...years); - - const useYearlyView = - totalYears >= 5; - - /* - |-------------------------------------------------------------------------- - | LABELS - |-------------------------------------------------------------------------- - */ - - let labels; - - if (useYearlyView) { - - labels = [...new Set( - rows.map(r => String(r.year)) - )].sort(); - - } else { - - labels = [...new Set( - rows.map(r => - `${r.year}-W${r.period}` - ) - )].sort((a, b) => { - - const [yearA, weekA] = - a.split('-W').map(Number); - - const [yearB, weekB] = - b.split('-W').map(Number); - - if (yearA !== yearB) { - return yearA - yearB; - } - - return weekA - weekB; - - }); - - } - - /* - |-------------------------------------------------------------------------- - | TOTAL CASES - |-------------------------------------------------------------------------- - */ - - const totalCases = labels.map(label => { - - if (useYearlyView) { - - return rows - .filter(r => - String(r.year) === label - ) - .reduce( - (sum, r) => - sum + Number(r.total_cases || 0), - 0 - ); - - } - - const row = rows.find(r => - `${r.year}-W${r.period}` === label - ); - - return row?.total_cases || 0; - - }); - /* |-------------------------------------------------------------------------- | PATHOGENS @@ -929,26 +925,6 @@ function renderAFITrend( data: labels.map(label => { - if (useYearlyView) { - - const filtered = rows.filter(r => - String(r.year) === label && - r.pathogen === pathogen - ); - - const total = - filtered.reduce( - (sum, r) => - sum + Number(r.positivity_rate || 0), - 0 - ); - - return filtered.length - ? total / filtered.length - : 0; - - } - const row = rows.find(r => `${r.year}-W${r.period}` @@ -958,7 +934,9 @@ function renderAFITrend( ); - return row?.positivity_rate || 0; + return Number( + row?.positivity_rate || 0 + ); }), @@ -970,6 +948,12 @@ function renderAFITrend( }) ); + /* + |-------------------------------------------------------------------------- + | BUILD + |-------------------------------------------------------------------------- + */ + buildMixedTrendChart( canvasId, labels, @@ -978,6 +962,8 @@ function renderAFITrend( ); } + + function renderPathogenChart(rows = []) { buildDistributionChart( 'pathogenChart', diff --git a/dashboard/resources/views/components/application-logo.blade.php b/dashboard/resources/views/components/application-logo.blade.php index 46579cf..c3b392e 100644 --- a/dashboard/resources/views/components/application-logo.blade.php +++ b/dashboard/resources/views/components/application-logo.blade.php @@ -1,3 +1,6 @@ - + +Logo \ No newline at end of file diff --git a/dashboard/resources/views/layouts/app.blade.php b/dashboard/resources/views/layouts/app.blade.php index 8a05483..375273d 100644 --- a/dashboard/resources/views/layouts/app.blade.php +++ b/dashboard/resources/views/layouts/app.blade.php @@ -4,6 +4,7 @@ NRML Dashboard +