Files
vigilcare-clinical/vigilcare-dashboard/src/components/charts/SofaHistory.vue
T
voltsrage 7751d6df06 fix:
No SOFA trend chart or organ-system timeline
No GCS trend chart or component history
No qSOFA history view
2026-06-23 18:00:43 +08:00

122 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { computed, shallowRef, markRaw } from 'vue'
import { Chart } from 'vue-chartjs'
import { Chart as ChartJS, registerables } from 'chart.js'
import { formatTime } from '@/composables/chartFormat'
ChartJS.register(...registerables)
const props = defineProps({ history: { type: Array, required: true } })
const ORGAN_DATASETS = [
{ label: 'Respiratory', key: 'respiratoryScore', color: '#3b82f6' },
{ label: 'Coagulation', key: 'coagulationScore', color: '#8b5cf6' },
{ label: 'Liver', key: 'liverScore', color: '#f59e0b' },
{ label: 'Cardiovascular', key: 'cardiovascularScore', color: '#ef4444' },
{ label: 'CNS', key: 'cnsScore', color: '#ec4899' },
{ label: 'Renal', key: 'renalScore', color: '#06b6d4' },
]
function sofaRiskBorder(score) {
if (score >= 10) return '#dc2626'
if (score >= 6) return '#f59e0b'
return '#22c55e'
}
const sortedHistory = computed(() =>
[...props.history].sort((a, b) => new Date(a.calculatedAt) - new Date(b.calculatedAt)),
)
const chartData = computed(() => {
const sorted = sortedHistory.value
const labels = sorted.map(h => formatTime(h.calculatedAt))
const organDatasets = ORGAN_DATASETS.map(({ label, key, color }) => ({
type: 'line',
label,
data: sorted.map(h => h[key] ?? 0),
backgroundColor: `${color}66`,
borderColor: color,
borderWidth: 1,
fill: true,
stack: 'organs',
pointRadius: 0,
tension: 0.2,
order: 2,
}))
return {
labels,
datasets: [
...organDatasets,
{
type: 'line',
label: 'SOFA Total',
data: sorted.map(h => h.totalScore),
borderColor: '#111827',
backgroundColor: 'transparent',
pointBackgroundColor: sorted.map(h => sofaRiskBorder(h.totalScore)),
pointBorderColor: sorted.map(h => sofaRiskBorder(h.totalScore)),
pointRadius: 4,
borderWidth: 2,
fill: false,
tension: 0.3,
order: 1,
},
],
}
})
const chartOptions = shallowRef(markRaw({
responsive: true,
maintainAspectRatio: true,
animation: {
duration: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 400,
},
interaction: { mode: 'index', intersect: false },
scales: {
x: { stacked: true },
y: {
stacked: true,
min: 0,
max: 24,
title: { display: true, text: 'SOFA Score' },
},
},
plugins: {
legend: {
display: true,
position: 'bottom',
labels: { boxWidth: 12, font: { size: 11 } },
},
tooltip: {
callbacks: {
footer(items) {
const total = items.find(i => i.dataset.label === 'SOFA Total')
if (!total) return ''
const score = total.parsed.y
if (score >= 10) return 'Risk: High (≥10)'
if (score >= 6) return 'Risk: Moderate (69)'
return 'Risk: Low (05)'
},
},
},
},
}))
</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-1 text-sm font-medium text-gray-700 dark:text-gray-300">SOFA Score Over Time</h3>
<p class="mb-4 text-xs text-gray-500 dark:text-gray-400">
Stacked areas show per-organ contributions; line shows total score.
<span class="text-green-600 dark:text-green-400">05</span>,
<span class="text-amber-600 dark:text-amber-400">69</span>,
<span class="text-red-600 dark:text-red-400">10+</span>.
</p>
<div class="aspect-video w-full min-h-0 overflow-hidden">
<Chart type="line" :data="chartData" :options="chartOptions" />
</div>
</div>
</template>