add sequencing tab
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -163,4 +190,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user