fix: finish all patient related upgrades

This commit is contained in:
voltsrage
2026-06-23 18:17:13 +08:00
parent 7751d6df06
commit 5d46200941
20 changed files with 985 additions and 19 deletions
@@ -0,0 +1,53 @@
export function formatMedicationLabel(med) {
const name = med.drugName?.length > 12
? `${med.drugName.slice(0, 11)}`
: med.drugName
return name ?? 'Med'
}
export function formatMedicationTooltip(med) {
return `${med.drugName} ${med.dose}${med.doseUnit} (${med.route}) — ${new Date(med.administeredAt).toLocaleString()}`
}
export function filterMedicationsForWindow(observations, code, medications) {
const series = (observations ?? [])
.filter(o => o.observationCode === code)
.sort((a, b) => new Date(a.recordedAt) - new Date(b.recordedAt))
if (!series.length) return []
const min = new Date(series[0].recordedAt).getTime()
const max = new Date(series[series.length - 1].recordedAt).getTime()
return (medications ?? []).filter(m => {
const t = new Date(m.administeredAt).getTime()
if (min === max) return Math.abs(t - min) <= 60_000
return t >= min && t <= max
})
}
export function getMedicationsNearTimestamp(medications, targetMs, windowMs = 15 * 60 * 1000) {
return (medications ?? []).filter(m => {
const t = new Date(m.administeredAt).getTime()
return Math.abs(t - targetMs) <= windowMs
})
}
export function xPixelForMedicationTime(administeredAt, timestamps, chartArea) {
if (!timestamps?.length || !chartArea) return null
const medTime = new Date(administeredAt).getTime()
const times = timestamps.map(t => new Date(t).getTime())
if (times.length === 1) {
if (Math.abs(medTime - times[0]) > 60_000) return null
return (chartArea.left + chartArea.right) / 2
}
const min = times[0]
const max = times[times.length - 1]
if (medTime < min || medTime > max) return null
const ratio = (medTime - min) / (max - min)
return chartArea.left + ratio * chartArea.width
}
@@ -0,0 +1,90 @@
const BLOOD_TYPE_LABELS = {
APositive: 'A+',
ANegative: 'A-',
BPositive: 'B+',
BNegative: 'B-',
AbPositive: 'AB+',
AbNegative: 'AB-',
OPositive: 'O+',
ONegative: 'O-',
'A+': 'A+',
'A-': 'A-',
'B+': 'B+',
'B-': 'B-',
'AB+': 'AB+',
'AB-': 'AB-',
'O+': 'O+',
'O-': 'O-',
}
const DEPARTMENT_LABELS = {
Icu: 'ICU',
GeneralMedicine: 'General Medicine',
Emergency: 'Emergency',
Cardiology: 'Cardiology',
Surgery: 'Surgery',
Pediatrics: 'Pediatrics',
ICU: 'ICU',
GENERAL_MEDICINE: 'General Medicine',
EMERGENCY: 'Emergency',
CARDIOLOGY: 'Cardiology',
SURGERY: 'Surgery',
PEDIATRICS: 'Pediatrics',
}
const GENDER_LABELS = {
M: 'Male',
F: 'Female',
O: 'Other',
U: 'Unknown',
}
export function formatBloodType(value) {
if (value == null || value === '') return '—'
return BLOOD_TYPE_LABELS[value] ?? String(value)
}
export function formatDepartment(value) {
if (value == null || value === '') return '—'
return DEPARTMENT_LABELS[value] ?? String(value).replace(/_/g, ' ')
}
export function formatGender(value) {
if (value == null || value === '') return '—'
return GENDER_LABELS[value] ?? value
}
export function computeAge(dateOfBirth, asOf = new Date()) {
if (!dateOfBirth) return null
const dob = new Date(dateOfBirth)
if (Number.isNaN(dob.getTime())) return null
let age = asOf.getFullYear() - dob.getFullYear()
const monthDelta = asOf.getMonth() - dob.getMonth()
if (monthDelta < 0 || (monthDelta === 0 && asOf.getDate() < dob.getDate())) {
age -= 1
}
return age
}
export function formatDateOfBirth(dateOfBirth) {
if (!dateOfBirth) return '—'
const d = new Date(dateOfBirth)
if (Number.isNaN(d.getTime())) return dateOfBirth
return d.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })
}
export function formatDobWithAge(dateOfBirth) {
const formatted = formatDateOfBirth(dateOfBirth)
const age = computeAge(dateOfBirth)
if (age == null) return formatted
return `${formatted} (${age}y)`
}
export function hasKnownAllergies(allergies) {
return Boolean(allergies?.trim())
}
export function formatAllergiesDisplay(allergies) {
return hasKnownAllergies(allergies) ? allergies.trim() : 'NKDA'
}
@@ -0,0 +1,61 @@
import { alertTypeLabel, observationCodeLabel } from '@/api/normalize'
export const TIMELINE_EVENT_TYPES = [
{ id: 'observation', label: 'Observations' },
{ id: 'alert', label: 'Alerts' },
{ id: 'order', label: 'Orders' },
{ id: 'medication', label: 'Medications' },
{ id: 'status', label: 'Status' },
]
export function timelineEventStyles(type) {
const styles = {
observation: {
dot: 'bg-blue-500',
border: 'border-blue-500',
badge: 'info',
},
alert: {
dot: 'bg-red-500',
border: 'border-red-500',
badge: 'critical',
},
order: {
dot: 'bg-purple-500',
border: 'border-purple-500',
badge: 'info',
},
medication: {
dot: 'bg-green-500',
border: 'border-green-500',
badge: 'success',
},
status: {
dot: 'bg-gray-500',
border: 'border-gray-400',
badge: 'info',
},
}
return styles[type] ?? styles.status
}
export function formatTimelineEvent(event) {
switch (event.type) {
case 'observation':
return `${observationCodeLabel(event.observationCode)} ${event.value} ${event.unit ?? ''}`.trim()
case 'alert':
return `${alertTypeLabel(event.alertType)}${event.severity} (${event.status})`
case 'order':
return `${event.description}${event.status}`
case 'medication':
return `${event.drugName} ${event.dose}${event.doseUnit} (${event.route})`
case 'status':
return event.label ?? `Status: ${event.status}`
default:
return event.type
}
}
export function timelineTypeLabel(type) {
return TIMELINE_EVENT_TYPES.find(t => t.id === type)?.label ?? type
}
@@ -12,6 +12,7 @@ export function useChartData(observations, code) {
return {
labels: filtered.map(o => formatTime(o.recordedAt)),
timestamps: filtered.map(o => o.recordedAt),
datasets: [{
label: code,
data: filtered.map(o => o.value),