feature: Clinical Review Mode: Charts, Replay Controls & Alert Reasoning
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<script setup>
|
||||
import { computed, shallowRef, markRaw } from 'vue'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
import { formatTime } from '@/composables/chartFormat'
|
||||
|
||||
Chart.register(...registerables)
|
||||
|
||||
const props = defineProps({ history: { type: Array, required: true } })
|
||||
|
||||
const chartData = computed(() => ({
|
||||
labels: props.history.map(h => formatTime(h.calculatedAt)),
|
||||
datasets: [{
|
||||
label: 'NEWS2',
|
||||
data: props.history.map(h => h.totalScore),
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: props.history.map(h => {
|
||||
if (h.totalScore >= 7) return 'rgba(220, 38, 38, 0.3)'
|
||||
if (h.totalScore >= 5) return 'rgba(245, 158, 11, 0.3)'
|
||||
return 'rgba(34, 197, 94, 0.3)'
|
||||
}),
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 4,
|
||||
}],
|
||||
}))
|
||||
|
||||
const chartOptions = shallowRef(markRaw({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: {
|
||||
duration: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 400,
|
||||
},
|
||||
scales: {
|
||||
y: { min: 0, max: 20, title: { display: true, text: 'NEWS2 Score' } },
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full min-w-0 rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
||||
<h3 class="mb-4 text-sm font-medium text-gray-700 dark:text-gray-300">NEWS2 Score Over Time</h3>
|
||||
<div class="aspect-video w-full min-h-0 overflow-hidden">
|
||||
<Line :data="chartData" :options="chartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
import VitalTrendChart from './VitalTrendChart.vue'
|
||||
|
||||
defineProps({ observations: { type: Array, required: true } })
|
||||
|
||||
const charts = [
|
||||
{ code: 'HEART_RATE', title: 'Heart Rate (bpm)', yMin: 30, yMax: 180 },
|
||||
{ code: 'RESP_RATE', title: 'Respiratory Rate (/min)', yMin: 0, yMax: 40 },
|
||||
{ code: 'SYSTOLIC_BP', title: 'Systolic BP (mmHg)', yMin: 60, yMax: 250 },
|
||||
{ code: 'SPO2', title: 'SpO₂ (%)', yMin: 80, yMax: 100 },
|
||||
{ code: 'TEMP_C', title: 'Temperature (°C)', yMin: 34, yMax: 42 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid w-full min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<VitalTrendChart
|
||||
v-for="chart in charts"
|
||||
:key="chart.code"
|
||||
:observations="observations"
|
||||
:code="chart.code"
|
||||
:title="chart.title"
|
||||
:y-min="chart.yMin"
|
||||
:y-max="chart.yMax"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { computed, toValue, shallowRef, markRaw } from 'vue'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
|
||||
Chart.register(...registerables)
|
||||
|
||||
const props = defineProps({
|
||||
chartData: { type: Object, required: true },
|
||||
title: { type: String, required: true },
|
||||
yMin: { type: Number, default: undefined },
|
||||
yMax: { type: Number, default: undefined },
|
||||
})
|
||||
|
||||
const lineData = computed(() => {
|
||||
const data = toValue(props.chartData)
|
||||
return data?.datasets ? data : { labels: [], datasets: [] }
|
||||
})
|
||||
|
||||
const chartOptions = shallowRef(markRaw({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: {
|
||||
duration: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 400,
|
||||
},
|
||||
scales: {
|
||||
y: { min: props.yMin, max: props.yMax },
|
||||
x: { ticks: { maxTicksAuto: true, maxRotation: 45 } },
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full min-w-0 rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
||||
<h3 class="mb-4 text-sm font-medium text-gray-700 dark:text-gray-300">{{ title }}</h3>
|
||||
<div class="aspect-video w-full min-h-0 overflow-hidden">
|
||||
<Line :data="lineData" :options="chartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import { useChartData } from '@/composables/useChartData'
|
||||
import VitalChart from './VitalChart.vue'
|
||||
|
||||
const props = defineProps({
|
||||
observations: { type: Array, required: true },
|
||||
code: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
yMin: { type: Number, default: undefined },
|
||||
yMax: { type: Number, default: undefined },
|
||||
})
|
||||
|
||||
const { chartData } = useChartData(() => props.observations, props.code)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VitalChart
|
||||
:chart-data="chartData"
|
||||
:title="title"
|
||||
:y-min="yMin"
|
||||
:y-max="yMax"
|
||||
/>
|
||||
</template>
|
||||
Reference in New Issue
Block a user