654 lines
26 KiB
HTML
654 lines
26 KiB
HTML
{% load static %}
|
|
|
|
<div class="vital-signs-chart-container">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0">Vital Signs Trends</h5>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-primary active" data-range="24h">24h</button>
|
|
<button type="button" class="btn btn-outline-primary" data-range="7d">7d</button>
|
|
<button type="button" class="btn btn-outline-primary" data-range="30d">30d</button>
|
|
<button type="button" class="btn btn-outline-primary" data-range="custom">Custom</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="custom-date-range" style="display: none;">
|
|
<div class="row mb-3">
|
|
<div class="col-md-5">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text">From</span>
|
|
<input type="date" class="form-control" id="dateRangeStart">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text">To</span>
|
|
<input type="date" class="form-control" id="dateRangeEnd">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="button" class="btn btn-sm btn-primary w-100" id="applyDateRange">Apply</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<ul class="nav nav-tabs" id="vitalSignsTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="temperature-tab" data-bs-toggle="tab" data-bs-target="#temperature" type="button" role="tab" aria-controls="temperature" aria-selected="true">Temperature</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="blood-pressure-tab" data-bs-toggle="tab" data-bs-target="#blood-pressure" type="button" role="tab" aria-controls="blood-pressure" aria-selected="false">Blood Pressure</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="heart-rate-tab" data-bs-toggle="tab" data-bs-target="#heart-rate" type="button" role="tab" aria-controls="heart-rate" aria-selected="false">Heart Rate</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="respiratory-rate-tab" data-bs-toggle="tab" data-bs-target="#respiratory-rate" type="button" role="tab" aria-controls="respiratory-rate" aria-selected="false">Respiratory Rate</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="oxygen-saturation-tab" data-bs-toggle="tab" data-bs-target="#oxygen-saturation" type="button" role="tab" aria-controls="oxygen-saturation" aria-selected="false">O₂ Saturation</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="all-vitals-tab" data-bs-toggle="tab" data-bs-target="#all-vitals" type="button" role="tab" aria-controls="all-vitals" aria-selected="false">All Vitals</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="vitalSignsTabsContent">
|
|
<div class="tab-pane fade show active" id="temperature" role="tabpanel" aria-labelledby="temperature-tab">
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas id="temperatureChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="tab-pane fade" id="blood-pressure" role="tabpanel" aria-labelledby="blood-pressure-tab">
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas id="bloodPressureChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="tab-pane fade" id="heart-rate" role="tabpanel" aria-labelledby="heart-rate-tab">
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas id="heartRateChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="tab-pane fade" id="respiratory-rate" role="tabpanel" aria-labelledby="respiratory-rate-tab">
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas id="respiratoryRateChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="tab-pane fade" id="oxygen-saturation" role="tabpanel" aria-labelledby="oxygen-saturation-tab">
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas id="oxygenSaturationChart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="tab-pane fade" id="all-vitals" role="tabpanel" aria-labelledby="all-vitals-tab">
|
|
<div class="chart-container" style="position: relative; height: 400px;">
|
|
<canvas id="allVitalsChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
<div class="col-md-12">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-bordered table-striped" id="vitalSignsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>Temp (°C)</th>
|
|
<th>BP (mmHg)</th>
|
|
<th>HR (bpm)</th>
|
|
<th>RR (bpm)</th>
|
|
<th>O₂ Sat (%)</th>
|
|
<th>Pain (0-10)</th>
|
|
<th>Recorded By</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for vital in vital_signs %}
|
|
<tr>
|
|
<td>{{ vital.recorded_at|date:"M d, Y H:i" }}</td>
|
|
<td>{{ vital.temperature }}</td>
|
|
<td>{{ vital.systolic }}/{{ vital.diastolic }}</td>
|
|
<td>{{ vital.heart_rate }}</td>
|
|
<td>{{ vital.respiratory_rate }}</td>
|
|
<td>{{ vital.oxygen_saturation }}</td>
|
|
<td>{{ vital.pain_score }}</td>
|
|
<td>{{ vital.recorded_by.get_full_name }}</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center">No vital signs recorded</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Chart.js must be loaded in the parent template
|
|
|
|
// Parse vital signs data from Django template
|
|
const vitalSigns = {{ vital_signs_json|safe }};
|
|
|
|
// Extract data for charts
|
|
const labels = vitalSigns.map(v => v.recorded_at);
|
|
const temperatureData = vitalSigns.map(v => v.temperature);
|
|
const systolicData = vitalSigns.map(v => v.systolic);
|
|
const diastolicData = vitalSigns.map(v => v.diastolic);
|
|
const heartRateData = vitalSigns.map(v => v.heart_rate);
|
|
const respiratoryRateData = vitalSigns.map(v => v.respiratory_rate);
|
|
const oxygenSaturationData = vitalSigns.map(v => v.oxygen_saturation);
|
|
|
|
// Temperature Chart
|
|
const temperatureCtx = document.getElementById('temperatureChart').getContext('2d');
|
|
const temperatureChart = new Chart(temperatureCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Temperature (°C)',
|
|
data: temperatureData,
|
|
borderColor: 'rgba(255, 99, 132, 1)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false,
|
|
suggestedMin: 35,
|
|
suggestedMax: 40
|
|
}
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
normalRangeLow: {
|
|
type: 'line',
|
|
yMin: 36.1,
|
|
yMax: 36.1,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Low',
|
|
enabled: true,
|
|
position: 'start'
|
|
}
|
|
},
|
|
normalRangeHigh: {
|
|
type: 'line',
|
|
yMin: 37.2,
|
|
yMax: 37.2,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal High',
|
|
enabled: true,
|
|
position: 'end'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Blood Pressure Chart
|
|
const bloodPressureCtx = document.getElementById('bloodPressureChart').getContext('2d');
|
|
const bloodPressureChart = new Chart(bloodPressureCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'Systolic (mmHg)',
|
|
data: systolicData,
|
|
borderColor: 'rgba(231, 76, 60, 1)',
|
|
backgroundColor: 'rgba(231, 76, 60, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false
|
|
},
|
|
{
|
|
label: 'Diastolic (mmHg)',
|
|
data: diastolicData,
|
|
borderColor: 'rgba(52, 152, 219, 1)',
|
|
backgroundColor: 'rgba(52, 152, 219, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false,
|
|
suggestedMin: 40,
|
|
suggestedMax: 180
|
|
}
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
systolicNormalHigh: {
|
|
type: 'line',
|
|
yMin: 120,
|
|
yMax: 120,
|
|
borderColor: 'rgba(231, 76, 60, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Systolic High',
|
|
enabled: true,
|
|
position: 'end'
|
|
}
|
|
},
|
|
diastolicNormalHigh: {
|
|
type: 'line',
|
|
yMin: 80,
|
|
yMax: 80,
|
|
borderColor: 'rgba(52, 152, 219, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Diastolic High',
|
|
enabled: true,
|
|
position: 'end'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Heart Rate Chart
|
|
const heartRateCtx = document.getElementById('heartRateChart').getContext('2d');
|
|
const heartRateChart = new Chart(heartRateCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Heart Rate (bpm)',
|
|
data: heartRateData,
|
|
borderColor: 'rgba(155, 89, 182, 1)',
|
|
backgroundColor: 'rgba(155, 89, 182, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false,
|
|
suggestedMin: 40,
|
|
suggestedMax: 120
|
|
}
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
normalRangeLow: {
|
|
type: 'line',
|
|
yMin: 60,
|
|
yMax: 60,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Low',
|
|
enabled: true,
|
|
position: 'start'
|
|
}
|
|
},
|
|
normalRangeHigh: {
|
|
type: 'line',
|
|
yMin: 100,
|
|
yMax: 100,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal High',
|
|
enabled: true,
|
|
position: 'end'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Respiratory Rate Chart
|
|
const respiratoryRateCtx = document.getElementById('respiratoryRateChart').getContext('2d');
|
|
const respiratoryRateChart = new Chart(respiratoryRateCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Respiratory Rate (bpm)',
|
|
data: respiratoryRateData,
|
|
borderColor: 'rgba(241, 196, 15, 1)',
|
|
backgroundColor: 'rgba(241, 196, 15, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false,
|
|
suggestedMin: 8,
|
|
suggestedMax: 30
|
|
}
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
normalRangeLow: {
|
|
type: 'line',
|
|
yMin: 12,
|
|
yMax: 12,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Low',
|
|
enabled: true,
|
|
position: 'start'
|
|
}
|
|
},
|
|
normalRangeHigh: {
|
|
type: 'line',
|
|
yMin: 20,
|
|
yMax: 20,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal High',
|
|
enabled: true,
|
|
position: 'end'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Oxygen Saturation Chart
|
|
const oxygenSaturationCtx = document.getElementById('oxygenSaturationChart').getContext('2d');
|
|
const oxygenSaturationChart = new Chart(oxygenSaturationCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'O₂ Saturation (%)',
|
|
data: oxygenSaturationData,
|
|
borderColor: 'rgba(26, 188, 156, 1)',
|
|
backgroundColor: 'rgba(26, 188, 156, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false,
|
|
suggestedMin: 90,
|
|
suggestedMax: 100
|
|
}
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
normalRangeLow: {
|
|
type: 'line',
|
|
yMin: 95,
|
|
yMax: 95,
|
|
borderColor: 'rgba(46, 204, 113, 0.5)',
|
|
borderWidth: 2,
|
|
borderDash: [5, 5],
|
|
label: {
|
|
content: 'Normal Low',
|
|
enabled: true,
|
|
position: 'start'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// All Vitals Chart
|
|
const allVitalsCtx = document.getElementById('allVitalsChart').getContext('2d');
|
|
const allVitalsChart = new Chart(allVitalsCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'Temperature (°C)',
|
|
data: temperatureData,
|
|
borderColor: 'rgba(255, 99, 132, 1)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-temperature'
|
|
},
|
|
{
|
|
label: 'Systolic (mmHg)',
|
|
data: systolicData,
|
|
borderColor: 'rgba(231, 76, 60, 1)',
|
|
backgroundColor: 'rgba(231, 76, 60, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-bp'
|
|
},
|
|
{
|
|
label: 'Diastolic (mmHg)',
|
|
data: diastolicData,
|
|
borderColor: 'rgba(52, 152, 219, 1)',
|
|
backgroundColor: 'rgba(52, 152, 219, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-bp'
|
|
},
|
|
{
|
|
label: 'Heart Rate (bpm)',
|
|
data: heartRateData,
|
|
borderColor: 'rgba(155, 89, 182, 1)',
|
|
backgroundColor: 'rgba(155, 89, 182, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-hr'
|
|
},
|
|
{
|
|
label: 'Respiratory Rate (bpm)',
|
|
data: respiratoryRateData,
|
|
borderColor: 'rgba(241, 196, 15, 1)',
|
|
backgroundColor: 'rgba(241, 196, 15, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-rr'
|
|
},
|
|
{
|
|
label: 'O₂ Saturation (%)',
|
|
data: oxygenSaturationData,
|
|
borderColor: 'rgba(26, 188, 156, 1)',
|
|
backgroundColor: 'rgba(26, 188, 156, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.1,
|
|
fill: false,
|
|
yAxisID: 'y-o2'
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
'y-temperature': {
|
|
type: 'linear',
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: 'Temperature (°C)'
|
|
},
|
|
suggestedMin: 35,
|
|
suggestedMax: 40,
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
},
|
|
'y-bp': {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'Blood Pressure (mmHg)'
|
|
},
|
|
suggestedMin: 40,
|
|
suggestedMax: 180,
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
},
|
|
'y-hr': {
|
|
type: 'linear',
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: 'Heart Rate (bpm)'
|
|
},
|
|
suggestedMin: 40,
|
|
suggestedMax: 120,
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
},
|
|
'y-rr': {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'Respiratory Rate (bpm)'
|
|
},
|
|
suggestedMin: 8,
|
|
suggestedMax: 30,
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
},
|
|
'y-o2': {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'O₂ Saturation (%)'
|
|
},
|
|
suggestedMin: 90,
|
|
suggestedMax: 100,
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Date range buttons
|
|
document.querySelectorAll('[data-range]').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
// Remove active class from all buttons
|
|
document.querySelectorAll('[data-range]').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
|
|
// Add active class to clicked button
|
|
this.classList.add('active');
|
|
|
|
// Show/hide custom date range inputs
|
|
if (this.dataset.range === 'custom') {
|
|
document.querySelector('.custom-date-range').style.display = 'block';
|
|
} else {
|
|
document.querySelector('.custom-date-range').style.display = 'none';
|
|
|
|
// Filter data based on selected range
|
|
filterDataByRange(this.dataset.range);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Apply custom date range
|
|
document.getElementById('applyDateRange').addEventListener('click', function() {
|
|
const startDate = document.getElementById('dateRangeStart').value;
|
|
const endDate = document.getElementById('dateRangeEnd').value;
|
|
|
|
if (startDate && endDate) {
|
|
filterDataByCustomRange(startDate, endDate);
|
|
}
|
|
});
|
|
|
|
// Function to filter data by predefined range
|
|
function filterDataByRange(range) {
|
|
// Implementation would filter the data and update all charts
|
|
// This is a placeholder for the actual implementation
|
|
console.log(`Filtering by range: ${range}`);
|
|
|
|
// Example implementation would:
|
|
// 1. Calculate the date range based on the selected option
|
|
// 2. Filter the vital signs data
|
|
// 3. Update all charts with the filtered data
|
|
// 4. Update the table with the filtered data
|
|
}
|
|
|
|
// Function to filter data by custom date range
|
|
function filterDataByCustomRange(startDate, endDate) {
|
|
// Implementation would filter the data and update all charts
|
|
// This is a placeholder for the actual implementation
|
|
console.log(`Filtering by custom range: ${startDate} to ${endDate}`);
|
|
|
|
// Example implementation would:
|
|
// 1. Parse the start and end dates
|
|
// 2. Filter the vital signs data
|
|
// 3. Update all charts with the filtered data
|
|
// 4. Update the table with the filtered data
|
|
}
|
|
});
|
|
</script>
|
|
|