fix: finish all patient related upgrades
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user