mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-25 13:51:40 +08:00
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.
This commit is contained in:
91
src/components/labs/LabResultCard.tsx
Normal file
91
src/components/labs/LabResultCard.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user