mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-24 21:31:43 +08:00
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.
92 lines
2.8 KiB
TypeScript
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>
|
|
)
|
|
}
|