add sequencing tab
This commit is contained in:
77579
case_lab_results.sql
Normal file
77579
case_lab_results.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -155,19 +155,49 @@ class DashboardController extends Controller
|
||||
// return response()->json(['error' => 'Missing epiweek range'], 400);
|
||||
// }
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
// return response()->json($data);
|
||||
// }
|
||||
|
||||
public function fetchSourceData(){
|
||||
try{
|
||||
public function fetchSourceData()
|
||||
{
|
||||
try {
|
||||
$this->dataRetrievalService->getSurveillanceData();
|
||||
return response()->json(['message' => 'Data loaded successfully!'], 200);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => 'Data loaded unsuccessfully!'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sequencing Dashboard
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
public function sequencing(Request $request)
|
||||
{
|
||||
$surveillanceId = (int) $request->query('surveillance_id');
|
||||
$range = $this->getEpiRange($request);
|
||||
|
||||
if (!$surveillanceId || !$range) {
|
||||
return response()->json(['error' => 'Missing parameters'], 400);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'trend' => $this->service->sequencingTrend(
|
||||
$surveillanceId,
|
||||
$range['startYear'],
|
||||
$range['startWeek'],
|
||||
$range['endYear'],
|
||||
$range['endWeek']
|
||||
),'distribution' => $this->service->subtypeDistribution(
|
||||
$surveillanceId,
|
||||
$range['startYear'],
|
||||
$range['startWeek'],
|
||||
$range['endYear'],
|
||||
$range['endWeek']
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -727,28 +727,20 @@ class DashboardService
|
||||
$q->select('lab_code')
|
||||
->from('surveillance_cases')
|
||||
->where('surveillance_id', $surveillanceId)
|
||||
->whereRaw(
|
||||
"(year_data > ? OR (year_data = ? AND week_data >= ?))",
|
||||
[$startYear, $startYear, $startWeek]
|
||||
)
|
||||
->whereRaw(
|
||||
"(year_data < ? OR (year_data = ? AND week_data <= ?))",
|
||||
[$endYear, $endYear, $endWeek]
|
||||
);
|
||||
->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
|
||||
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
|
||||
})
|
||||
|
||||
->where('case_lab_results.is_positive', 1)
|
||||
|
||||
->selectRaw("
|
||||
case_lab_results.subtype,
|
||||
COALESCE(NULLIF(case_lab_results.subtype, ''), 'Unsubtyped') as subtype,
|
||||
COUNT(DISTINCT case_lab_results.lab_code) as total
|
||||
")
|
||||
|
||||
->whereNotNull('case_lab_results.subtype')
|
||||
->where('case_lab_results.subtype', '!=', '')
|
||||
->where('case_lab_results.subtype', '!=', 'Positive')
|
||||
|
||||
->groupBy('case_lab_results.subtype')
|
||||
->groupBy('subtype')
|
||||
->orderByDesc('total')
|
||||
->get();
|
||||
|
||||
@@ -846,5 +838,40 @@ class DashboardService
|
||||
->get();
|
||||
|
||||
}
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| sequencing trend
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
public function sequencingTrend($surveillanceId, $startYear, $startWeek, $endYear, $endWeek)
|
||||
{
|
||||
return CaseLabResult::join(
|
||||
'surveillance_cases',
|
||||
'case_lab_results.lab_code',
|
||||
'=',
|
||||
'surveillance_cases.lab_code'
|
||||
)
|
||||
->where('surveillance_cases.surveillance_id', $surveillanceId)
|
||||
|
||||
->where('case_lab_results.is_positive', 1)
|
||||
|
||||
->whereNotNull('subtype')
|
||||
|
||||
->where(function ($q) use ($startYear, $startWeek, $endYear, $endWeek) {
|
||||
$q->whereRaw("(year_data > ? OR (year_data = ? AND week_data >= ?))", [$startYear, $startYear, $startWeek])
|
||||
->whereRaw("(year_data < ? OR (year_data = ? AND week_data <= ?))", [$endYear, $endYear, $endWeek]);
|
||||
})
|
||||
|
||||
->selectRaw("
|
||||
surveillance_cases.week_data as period,
|
||||
subtype,
|
||||
COUNT(DISTINCT surveillance_cases.lab_code) as total
|
||||
")
|
||||
|
||||
->groupBy('period', 'subtype')
|
||||
->orderBy('period')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
@@ -481,3 +481,5 @@ function renderSubtypeChart(rows) {
|
||||
rows.map(r => r.total)
|
||||
);
|
||||
}
|
||||
|
||||
//Seq
|
||||
|
||||
238
dashboard/public/js/sequencing.js
Normal file
238
dashboard/public/js/sequencing.js
Normal file
@@ -0,0 +1,238 @@
|
||||
let sequencingChart;
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const canvas = document.getElementById('sequencingChart');
|
||||
if (!canvas) return;
|
||||
new DashboardFilter((startYear, startWeek, endYear, endWeek) => {
|
||||
|
||||
fetch(`/api/dashboard/sequencing?surveillance_id=${window.SURVEILLANCE_ID}&start_year=${startYear}&start_week=${startWeek}&end_year=${endYear}&end_week=${endWeek}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
renderSequencingChart(data.trend || []);
|
||||
renderSequencingCountChart(data.trend || []);
|
||||
renderSequencingPieChart(data.distribution || []);
|
||||
renderSequencingTotalChart(data.trend || []);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function renderSequencingCountChart(rows) {
|
||||
|
||||
const ctx = document.getElementById('sequencingCountChart');
|
||||
|
||||
Chart.getChart('sequencingCountChart')?.destroy();
|
||||
|
||||
rows = processTopSubtypes(rows);
|
||||
|
||||
const weeks = [...new Set(rows.map(r => r.period))];
|
||||
const subtypes = [...new Set(rows.map(r => r.subtype))];
|
||||
|
||||
const colors = [
|
||||
'#2563eb', '#10b981', '#f59e0b', '#ef4444',
|
||||
'#8b5cf6', '#14b8a6', '#f97316', '#84cc16'
|
||||
];
|
||||
|
||||
const datasets = subtypes.map((sub, i) => ({
|
||||
label: sub,
|
||||
data: weeks.map(w => {
|
||||
const found = rows.find(r => r.period === w && r.subtype === sub);
|
||||
return found ? found.total : 0;
|
||||
}),
|
||||
backgroundColor: colors[i % colors.length]
|
||||
}));
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: weeks.map(w => `W${w}`),
|
||||
datasets
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: { stacked: true },
|
||||
y: { stacked: true }
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function renderSequencingPieChart(rows) {
|
||||
|
||||
const ctx = document.getElementById('sequencingPieChart');
|
||||
|
||||
Chart.getChart('sequencingPieChart')?.destroy();
|
||||
|
||||
const top = rows.slice(0, 8);
|
||||
|
||||
const labels = top.map(r => r.subtype);
|
||||
const values = top.map(r => r.total);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function renderSequencingTotalChart(rows) {
|
||||
|
||||
const ctx = document.getElementById('sequencingTotalChart');
|
||||
|
||||
Chart.getChart('sequencingTotalChart')?.destroy();
|
||||
|
||||
const totals = {};
|
||||
|
||||
rows.forEach(r => {
|
||||
totals[r.period] = (totals[r.period] || 0) + Number(r.total);
|
||||
});
|
||||
|
||||
const weeks = Object.keys(totals);
|
||||
const values = Object.values(totals);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: weeks.map(w => `W${w}`),
|
||||
datasets: [{
|
||||
label: 'Total Samples',
|
||||
data: values,
|
||||
backgroundColor: '#0B8F3C'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
function processTopSubtypes(rows) {
|
||||
|
||||
const totals = {};
|
||||
|
||||
rows.forEach(r => {
|
||||
totals[r.subtype] = (totals[r.subtype] || 0) + Number(r.total);
|
||||
});
|
||||
|
||||
const sorted = Object.entries(totals)
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
const top = sorted.slice(0, 8).map(([k]) => k);
|
||||
|
||||
return rows.map(r => {
|
||||
if (!top.includes(r.subtype)) {
|
||||
return { ...r, subtype: 'Others' };
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}
|
||||
function renderSequencingChart(rows) {
|
||||
|
||||
rows = processTopSubtypes(rows);
|
||||
|
||||
const ctx = document.getElementById('sequencingChart');
|
||||
|
||||
if (sequencingChart) {
|
||||
sequencingChart.destroy();
|
||||
}
|
||||
|
||||
const colors = [
|
||||
'#2563eb', '#10b981', '#f59e0b', '#ef4444',
|
||||
'#8b5cf6', '#14b8a6', '#f97316', '#84cc16',
|
||||
'#6b7280'
|
||||
];
|
||||
|
||||
const aggregated = {};
|
||||
|
||||
rows.forEach(r => {
|
||||
const key = `${r.period}_${r.subtype}`;
|
||||
if (!aggregated[key]) {
|
||||
aggregated[key] = { ...r };
|
||||
} else {
|
||||
aggregated[key].total += Number(r.total);
|
||||
}
|
||||
});
|
||||
|
||||
const cleanRows = Object.values(aggregated);
|
||||
|
||||
const weeks = [...new Set(cleanRows.map(r => r.period))];
|
||||
const subtypes = [...new Set(cleanRows.map(r => r.subtype))]
|
||||
.sort((a, b) => {
|
||||
const sum = s => cleanRows
|
||||
.filter(r => r.subtype === s)
|
||||
.reduce((t, r) => t + r.total, 0);
|
||||
return sum(b) - sum(a);
|
||||
});
|
||||
|
||||
const datasets = subtypes.map((sub, i) => {
|
||||
return {
|
||||
label: sub,
|
||||
data: weeks.map(w => {
|
||||
const weekRows = cleanRows.filter(r => r.period === w);
|
||||
const total = weekRows.reduce((s, r) => s + Number(r.total), 0);
|
||||
|
||||
const found = weekRows.find(r => r.subtype === sub);
|
||||
return total ? ((found?.total || 0) / total) * 100 : 0;
|
||||
}),
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
backgroundColor: colors[i % colors.length],
|
||||
borderColor: colors[i % colors.length]
|
||||
};
|
||||
});
|
||||
|
||||
sequencingChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: weeks.map(w => `W${w}`),
|
||||
datasets: datasets
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center'
|
||||
},
|
||||
datalabels: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: { stacked: true },
|
||||
y: {
|
||||
stacked: true,
|
||||
max: 100,
|
||||
ticks: {
|
||||
callback: v => v + '%'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -21,29 +21,14 @@
|
||||
</select>
|
||||
|
||||
<div id="custom_range_container" style="display:none;" class="align-items-center gap-1">
|
||||
|
||||
<select id="start_year" class="form-select"></select>
|
||||
<select id="start_week" class="form-select"></select>
|
||||
|
||||
<span class="mx-1">to</span>
|
||||
|
||||
<select id="end_year" class="form-select"></select>
|
||||
<select id="end_week" class="form-select"></select>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- STATUS -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<b>Current {{ $selected->code }} Status:</b>
|
||||
<span id="activityStatus">Loading...</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- SUMMARY CARDS -->
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>NRML Surveillance Dashboard</title>
|
||||
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
<h3 class="mb-4">NRML Surveillance Dashboard</h3>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body row g-3 align-items-end">
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Surveillance Program</label>
|
||||
<select id="surveillance_id" class="form-select">
|
||||
@foreach($programs as $program)
|
||||
<option value="{{ $program->id }}">
|
||||
{{ $program->code }} - {{ $program->name_en }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Period</label>
|
||||
<select id="period_type" class="form-select">
|
||||
<option value="week">Epiweek</option>
|
||||
<option value="month">Month</option>
|
||||
<option value="year">Year</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Date From</label>
|
||||
<input type="date" id="date_from" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Date To</label>
|
||||
<input type="date" id="date_to" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<button onclick="loadDashboard()" class="btn btn-primary w-100">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="row" id="summary_cards"></div>
|
||||
|
||||
<!-- Trend Chart -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h5>Epidemic Trend</h5>
|
||||
<canvas id="trendChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Province Table -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h5>Cases by Province</h5>
|
||||
<table class="table table-bordered" id="provinceTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Province</th>
|
||||
<th>Total Cases</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let trendChart;
|
||||
|
||||
function loadDashboard() {
|
||||
|
||||
const surveillanceId = document.getElementById('surveillance_id').value;
|
||||
const periodType = document.getElementById('period_type').value;
|
||||
const dateFrom = document.getElementById('date_from').value;
|
||||
const dateTo = document.getElementById('date_to').value;
|
||||
|
||||
loadSummary(dateFrom, dateTo);
|
||||
loadTrend(periodType, dateFrom, dateTo);
|
||||
loadProvince(surveillanceId, dateFrom, dateTo);
|
||||
}
|
||||
|
||||
function loadSummary(dateFrom, dateTo) {
|
||||
|
||||
fetch(`/api/dashboard/summary?date_from=${dateFrom}&date_to=${dateTo}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
let html = '';
|
||||
|
||||
data.forEach(item => {
|
||||
html += `
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h6>${item.code}</h6>
|
||||
<h4>${item.current_total}</h4>
|
||||
<small class="${item.percent_change >= 0 ? 'text-success' : 'text-danger'}">
|
||||
${item.percent_change}%
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('summary_cards').innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
function loadTrend(periodType, dateFrom, dateTo) {
|
||||
|
||||
fetch(`/api/dashboard/trend?period_type=${periodType}&date_from=${dateFrom}&date_to=${dateTo}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
if (trendChart) trendChart.destroy();
|
||||
|
||||
const labelsSet = new Set();
|
||||
|
||||
Object.values(data).forEach(program => {
|
||||
program.forEach(row => {
|
||||
labelsSet.add(`${row.year ?? ''}-${row.period}`);
|
||||
});
|
||||
});
|
||||
|
||||
const labels = Array.from(labelsSet).sort();
|
||||
|
||||
const datasets = [];
|
||||
|
||||
const colors = {
|
||||
SARI: 'red',
|
||||
ILI: 'blue',
|
||||
LBM: 'green'
|
||||
};
|
||||
|
||||
Object.keys(data).forEach(code => {
|
||||
|
||||
const values = labels.map(label => {
|
||||
const found = data[code].find(row =>
|
||||
`${row.year ?? ''}-${row.period}` === label
|
||||
);
|
||||
return found ? found.total : 0;
|
||||
});
|
||||
|
||||
datasets.push({
|
||||
label: code,
|
||||
data: values,
|
||||
borderColor: colors[code] || 'black',
|
||||
fill: false
|
||||
});
|
||||
});
|
||||
|
||||
trendChart = new Chart(document.getElementById('trendChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadProvince(surveillanceId, dateFrom, dateTo) {
|
||||
|
||||
fetch(`/api/dashboard/province?surveillance_id=${surveillanceId}&date_from=${dateFrom}&date_to=${dateTo}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
let html = '';
|
||||
|
||||
data.forEach(item => {
|
||||
html += `
|
||||
<tr>
|
||||
<td>${item.site_province_name}</td>
|
||||
<td>${item.total}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.querySelector('#provinceTable tbody').innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const past = new Date();
|
||||
past.setDate(past.getDate() - 30);
|
||||
|
||||
document.getElementById('date_from').value = past.toISOString().split('T')[0];
|
||||
document.getElementById('date_to').value = today;
|
||||
|
||||
loadDashboard();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
69
dashboard/resources/views/dashboard/sequencing.blade.php
Normal file
69
dashboard/resources/views/dashboard/sequencing.blade.php
Normal file
@@ -0,0 +1,69 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
|
||||
<h4 class="fw-bold">Sequencing Analysis</h4>
|
||||
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
|
||||
<select id="trend_range" class="form-select w-auto">
|
||||
<option value="8" selected>Last 8 weeks</option>
|
||||
<option value="12">Last 12 weeks</option>
|
||||
<option value="26">Last 26 weeks</option>
|
||||
<option value="custom">Custom range</option>
|
||||
</select>
|
||||
|
||||
<div id="custom_range_container" style="display:none;" class="align-items-center gap-1">
|
||||
<select id="start_year" class="form-select"></select>
|
||||
<select id="start_week" class="form-select"></select>
|
||||
<span class="mx-1">to</span>
|
||||
<select id="end_year" class="form-select"></select>
|
||||
<select id="end_week" class="form-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" style="height:600px;">
|
||||
<canvas id="sequencingChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
|
||||
<!-- Counts -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body" style="height:600px;">
|
||||
<h6 class="fw-bold">Lineage Counts Over Time</h6>
|
||||
<canvas id="sequencingCountChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Distribution -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body" style="height:600px;">
|
||||
<h6 class="fw-bold">Total Sequenced Samples Over Time</h6>
|
||||
<canvas id="sequencingTotalChart" style="height: 550px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
window.SURVEILLANCE_ID = 6;
|
||||
</script>
|
||||
|
||||
<script src="/js/sequencing.js"></script>
|
||||
@endsection
|
||||
@@ -40,7 +40,7 @@
|
||||
display: flex;
|
||||
background: white;
|
||||
border-bottom: 1px solid #dcdcdc;
|
||||
padding-left: 15px;
|
||||
padding: 0 20px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
@@ -49,6 +49,17 @@
|
||||
}
|
||||
|
||||
/* NAV ITEMS */
|
||||
.btn-theme-outline {
|
||||
background-color: #fff;
|
||||
color: #0B8F3C;
|
||||
border: 1px solid #0B8F3C;
|
||||
}
|
||||
|
||||
.btn-theme-outline:hover {
|
||||
background-color: #cce0d4;
|
||||
color: #0B8F3C;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 12px 18px;
|
||||
text-decoration: none;
|
||||
@@ -92,7 +103,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 10px;
|
||||
border-radius: 0px !important;
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
@@ -112,9 +123,7 @@
|
||||
</div>
|
||||
|
||||
<div class="ms-auto small">
|
||||
Last update: 12:05 |
|
||||
Data latency: 5–10 min |
|
||||
User: National - Read Only
|
||||
Last update: 12:05 | 2026-03-15
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -126,16 +135,34 @@
|
||||
Overview
|
||||
</a>
|
||||
|
||||
@foreach($programs as $program)
|
||||
<!-- @foreach($programs as $program)
|
||||
<a href="/dashboard/{{ strtolower($program->code) }}"
|
||||
class="nav-item {{ request()->is('dashboard/' . strtolower($program->code)) ? 'active-tab' : '' }}">
|
||||
{{ $program->code }}
|
||||
</a>
|
||||
@endforeach -->
|
||||
@foreach($programs->where('code', '!=', 'NDS') as $program)
|
||||
|
||||
@if($program->code === 'SEQ')
|
||||
|
||||
<a href="/dashboard/seq"
|
||||
class="nav-item {{ request()->is('dashboard/seq') ? 'active-tab' : '' }}">
|
||||
SEQ
|
||||
</a>
|
||||
@else
|
||||
<a href="/dashboard/{{ strtolower($program->code) }}"
|
||||
class="nav-item {{ request()->is('dashboard/' . strtolower($program->code)) ? 'active-tab' : '' }}">
|
||||
{{ $program->code }}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@endforeach
|
||||
|
||||
<button type="button" onclick="reloadDataSource()" class="btn btn-sm btn-warning" style="height: 27px; position: absolute; right: 40px; top: 70px;">
|
||||
Refresh Data
|
||||
</button>
|
||||
<div class="ms-auto d-flex align-items-center gap-4 pe-3">
|
||||
<button type="button" onclick="reloadDataSource()" class="btn btn-sm btn-theme-outline">
|
||||
Refresh Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ Route::get('/dashboard/program', [DashboardController::class, 'program']);
|
||||
Route::get('/dashboard/province-circles', [DashboardController::class, 'provinceCircles']);
|
||||
Route::get('/dashboard/sentinel-map', [DashboardController::class, 'sentinelMap']);
|
||||
Route::get('/dashboard/reload', [DashboardController::class, 'fetchSourceData']);
|
||||
Route::get('/dashboard/sequencing', [DashboardController::class, 'sequencing']);
|
||||
|
||||
@@ -9,9 +9,9 @@ Route::get('/', function () {
|
||||
});
|
||||
|
||||
|
||||
|
||||
Route::get('/dashboard/seq', function () {
|
||||
return view('dashboard.sequencing');
|
||||
});
|
||||
Route::get('/dashboard', [DashboardController::class, 'overview']);
|
||||
Route::get('/dashboard/{code}', [DashboardController::class, 'detail']);
|
||||
Route::get('/test-change', function () {
|
||||
return "TEST_CHANGE_WORKING";
|
||||
});
|
||||
|
||||
|
||||
47973
surveillance_cases.sql
Normal file
47973
surveillance_cases.sql
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user