Files
nextstep/src/components/labs/LabResultCard.tsx
Tony0410 f0f674945c feat: implement all 8 new health management features
This commit implements all features specified in the eight-features design doc:

Features Added:
- Temperature Log: Track body temperature with fever alerts and trend charts
- Contact Directory: Manage healthcare contacts with categories and roles
- Weight Log: Monitor weight changes with BMI calculation and alerts
- Treatment Timeline: Track treatment milestones and visualize progress
- Caregiver Tasks: Manage delegated care tasks with completion tracking
- Lab Results: Record lab tests with reference ranges and trend analysis
- Medical Documents: Upload and organize medical documents
- Drug Interactions: Check for interactions between medications

Technical Changes:
- Added 8 new Prisma models (TemperatureLog, Contact, WeightLog,
  TreatmentMilestone, CaregiverTask, LabResult, MedicalDocument, DrugInteraction)
- Created 56 new components across 8 feature domains
- Implemented 23 new API routes with full CRUD operations
- Added comprehensive Zod schemas for type validation
- Extended Dexie DB (v3) for offline-first sync support
- Created lab panel templates (CBC, CMP, Liver, Tumor Markers) with flag computation
- Built drug interaction checker with curated interaction database
- Added 76 new tests (99 total) covering all new functionality

Bug Fixes:
- Fixed operator precedence bug in interaction checker
- Fixed timezone handling in calculator tests
- Aligned test expectations with grace window behavior

All 99 tests pass and build completes successfully.
2026-03-02 11:17:38 +00:00

92 lines
2.8 KiB
TypeScript

'use client'
import { format } from 'date-fns'
import { FileText } from 'lucide-react'
import { Card } from '@/components/ui'
import { MarkerRow } from './MarkerRow'
interface MarkerData {
marker: string
value: number
unit: string
refMin: number | null
refMax: number | null
flag: string | null
}
interface LabResultData {
id: string
testDate: string
panelName: string
labName: string | null
results: MarkerData[]
notes: string | null
createdBy?: { id: string; name: string }
}
interface LabResultCardProps {
result: LabResultData
onEdit?: (result: LabResultData) => void
}
export function LabResultCard({ result, onEdit }: LabResultCardProps) {
const markers = result.results || []
const flaggedCount = markers.filter((m) => m.flag).length
const criticalCount = markers.filter((m) => m.flag?.startsWith('CRITICAL')).length
return (
<Card
className="cursor-pointer hover:shadow-card-hover transition-shadow"
onClick={() => onEdit?.(result)}
>
<div className="p-4">
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-blue-100 flex items-center justify-center">
<FileText className="w-5 h-5 text-blue-600" />
</div>
<div>
<h3 className="font-semibold text-secondary-900">{result.panelName}</h3>
<p className="text-xs text-secondary-500">
{format(new Date(result.testDate), 'MMM d, yyyy')}
{result.labName && ` · ${result.labName}`}
</p>
</div>
</div>
{/* Flag summary */}
<div className="flex gap-1.5">
{criticalCount > 0 && (
<span className="text-xs font-bold px-2 py-0.5 rounded-full bg-red-100 text-red-700">
{criticalCount} critical
</span>
)}
{flaggedCount > 0 && flaggedCount !== criticalCount && (
<span className="text-xs font-bold px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700">
{flaggedCount - criticalCount} flagged
</span>
)}
{flaggedCount === 0 && markers.length > 0 && (
<span className="text-xs font-bold px-2 py-0.5 rounded-full bg-green-100 text-green-700">
All normal
</span>
)}
</div>
</div>
{/* Marker rows */}
<div className="space-y-1">
{markers.map((m, i) => (
<MarkerRow key={`${m.marker}-${i}`} marker={m} />
))}
</div>
{/* Notes */}
{result.notes && (
<p className="text-xs text-secondary-500 mt-3 italic">{result.notes}</p>
)}
</div>
</Card>
)
}