hospital-management/templates/emr/partials/vital_signs_chart.html
2025-08-12 13:33:25 +03:00

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>