mirror of
https://github.com/Tony0410/nextstep.git
synced 2026-05-24 21:31:43 +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:
998
docs/designs/2026-03-01-eight-features-design.md
Normal file
998
docs/designs/2026-03-01-eight-features-design.md
Normal file
@@ -0,0 +1,998 @@
|
||||
# Design: 8 New Features for Next Step
|
||||
|
||||
**Date:** 2026-03-01
|
||||
**Priority:** Urgent
|
||||
**Scope:** Medium (3-5 days)
|
||||
**Target Users:** End users (patients & family caregivers)
|
||||
|
||||
---
|
||||
|
||||
## Established Patterns (must follow)
|
||||
|
||||
All new features must follow these exact conventions from the codebase:
|
||||
|
||||
### File Patterns
|
||||
- **Pages:** `src/app/(app)/<feature>/page.tsx` — `'use client'`, uses `useApp()` for workspace, `Header` + `PageContainer` layout
|
||||
- **API routes:** `src/app/api/workspaces/[id]/<feature>/route.ts` — uses `withAuth`, `checkWorkspaceAccess`, `canEdit`, Zod validation, audit log on writes
|
||||
- **Components:** `src/components/<feature>/ComponentName.tsx` — `'use client'`, uses UI kit (`Card`, `Button`, `showToast`)
|
||||
- **Validation:** `src/lib/validation/schemas.ts` — Zod schemas with type exports
|
||||
- **Dexie tables:** `src/lib/sync/db.ts` — interface + table definition, bump version
|
||||
- **Sync ops:** `src/lib/sync/manager.ts` — add entity types and op handlers
|
||||
|
||||
### UI Patterns
|
||||
- Colors: `primary-*` (sage green), `secondary-*` (warm stone), `accent-*` (terracotta), `alert-*` (soft red), `cream-*` (warm neutral)
|
||||
- Semantic: `bg-background`, `bg-surface`, `bg-muted`, `border-border`
|
||||
- Cards: `<Card>` with `shadow-card`, `rounded-card` (20px)
|
||||
- Touch: `min-h-touch` (48px), large tap targets
|
||||
- Typography: `font-display` for headings, `text-secondary-900` for titles, `text-secondary-500` for meta
|
||||
- Icons: `lucide-react`, 6x6 default, stroke color matching text
|
||||
- States: `LoadingState`, `EmptyState`, `ErrorState` from `@/components/ui`
|
||||
- Toast: `showToast('message', 'success'|'error')`
|
||||
- Page structure: `<Header title="X" />` then `<PageContainer className="pt-4 space-y-6">`
|
||||
|
||||
### API Patterns
|
||||
```typescript
|
||||
export const GET = withAuth(async (req: AuthenticatedRequest, { params }) => {
|
||||
const { id: workspaceId } = await params
|
||||
const access = await checkWorkspaceAccess(workspaceId, req.session.user.id)
|
||||
if (!access) return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
// ... logic
|
||||
})
|
||||
|
||||
export const POST = withAuth(async (req: AuthenticatedRequest, { params }) => {
|
||||
const { id: workspaceId } = await params
|
||||
const access = await checkWorkspaceAccess(workspaceId, req.session.user.id)
|
||||
if (!access || !canEdit(access.role)) return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
const body = await req.json()
|
||||
const result = schema.safeParse(body)
|
||||
if (!result.success) return NextResponse.json({ error: 'Invalid input', details: result.error.flatten() }, { status: 400 })
|
||||
// ... create + audit log
|
||||
})
|
||||
```
|
||||
|
||||
### Data fetch pattern (pages)
|
||||
```typescript
|
||||
// 1. useLiveQuery from Dexie for offline-first
|
||||
const localData = useLiveQuery(() => db.table.where('workspaceId').equals(id)..., [id])
|
||||
// 2. Also fetch from server
|
||||
const fetchData = useCallback(async () => { ... }, [currentWorkspace.id])
|
||||
// 3. Combine: prefer server, fallback to local
|
||||
const data = serverData.length > 0 ? serverData : localData || []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prisma Schema Additions
|
||||
|
||||
All 8 features in a single migration:
|
||||
|
||||
```prisma
|
||||
// ============================================
|
||||
// TEMPERATURE LOG
|
||||
// ============================================
|
||||
|
||||
model TemperatureLog {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
recordedAt DateTime @default(now())
|
||||
tempCelsius Float
|
||||
method String? // "oral", "forehead", "ear", "armpit"
|
||||
notes String?
|
||||
createdById String
|
||||
deletedAt DateTime?
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("TempLogCreatedBy", fields: [createdById], references: [id])
|
||||
|
||||
@@index([workspaceId, recordedAt])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CONTACT DIRECTORY
|
||||
// ============================================
|
||||
|
||||
model Contact {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
name String
|
||||
role String // "Oncologist", "Pharmacist", etc.
|
||||
category String // "ONCOLOGY", "HOSPITAL", "PHARMACY", "INSURANCE", "FAMILY", "OTHER"
|
||||
phone String
|
||||
phone2 String?
|
||||
email String?
|
||||
address String?
|
||||
hours String? // "Mon-Fri 8am-5pm"
|
||||
notes String?
|
||||
isEmergency Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String
|
||||
updatedById String
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("ContactCreatedBy", fields: [createdById], references: [id])
|
||||
updatedBy User @relation("ContactUpdatedBy", fields: [updatedById], references: [id])
|
||||
|
||||
@@index([workspaceId, category])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// WEIGHT LOG
|
||||
// ============================================
|
||||
|
||||
model WeightLog {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
recordedAt DateTime @default(now())
|
||||
weightKg Float
|
||||
notes String?
|
||||
createdById String
|
||||
deletedAt DateTime?
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("WeightLogCreatedBy", fields: [createdById], references: [id])
|
||||
|
||||
@@index([workspaceId, recordedAt])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TREATMENT TIMELINE
|
||||
// ============================================
|
||||
|
||||
enum MilestoneStatus {
|
||||
SCHEDULED
|
||||
COMPLETED
|
||||
DELAYED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model TreatmentMilestone {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
type String // "CHEMO_CYCLE", "SURGERY", "RADIATION", "SCAN", "CONSULTATION", "OTHER"
|
||||
title String
|
||||
description String?
|
||||
plannedDate DateTime
|
||||
actualDate DateTime?
|
||||
status MilestoneStatus @default(SCHEDULED)
|
||||
sortOrder Int @default(0)
|
||||
notes String?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String
|
||||
updatedById String
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("MilestoneCreatedBy", fields: [createdById], references: [id])
|
||||
updatedBy User @relation("MilestoneUpdatedBy", fields: [updatedById], references: [id])
|
||||
|
||||
@@index([workspaceId, plannedDate])
|
||||
@@index([workspaceId, status])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CAREGIVER TASKS
|
||||
// ============================================
|
||||
|
||||
enum TaskStatus {
|
||||
TODO
|
||||
IN_PROGRESS
|
||||
DONE
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum TaskPriority {
|
||||
URGENT
|
||||
HIGH
|
||||
NORMAL
|
||||
LOW
|
||||
}
|
||||
|
||||
model CaregiverTask {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
title String
|
||||
description String?
|
||||
category String // "MEDICAL", "ERRANDS", "MEALS", "EMOTIONAL", "OTHER"
|
||||
priority TaskPriority @default(NORMAL)
|
||||
status TaskStatus @default(TODO)
|
||||
assignedToId String?
|
||||
dueDate DateTime?
|
||||
completedAt DateTime?
|
||||
completedById String?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String
|
||||
updatedById String
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("TaskCreatedBy", fields: [createdById], references: [id])
|
||||
updatedBy User @relation("TaskUpdatedBy", fields: [updatedById], references: [id])
|
||||
assignedTo User? @relation("TaskAssignedTo", fields: [assignedToId], references: [id])
|
||||
completedBy User? @relation("TaskCompletedBy", fields: [completedById], references: [id])
|
||||
|
||||
@@index([workspaceId, status])
|
||||
@@index([workspaceId, assignedToId])
|
||||
@@index([workspaceId, dueDate])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// LAB RESULTS
|
||||
// ============================================
|
||||
|
||||
model LabResult {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
testDate DateTime
|
||||
panelName String // "Complete Blood Count", "Comprehensive Metabolic", etc.
|
||||
labName String? // "Quest", "Hospital Lab"
|
||||
results Json // Array of { marker, value, unit, refMin, refMax, flag }
|
||||
notes String?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String
|
||||
updatedById String
|
||||
|
||||
// Sync
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("LabResultCreatedBy", fields: [createdById], references: [id])
|
||||
updatedBy User @relation("LabResultUpdatedBy", fields: [updatedById], references: [id])
|
||||
|
||||
@@index([workspaceId, testDate])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MEDICAL DOCUMENTS
|
||||
// ============================================
|
||||
|
||||
model MedicalDocument {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
title String
|
||||
category String // "LAB_REPORT", "SCAN", "INSURANCE", "ID_CARD", "PRESCRIPTION", "OTHER"
|
||||
fileName String
|
||||
fileSize Int // bytes
|
||||
mimeType String // "application/pdf", "image/jpeg"
|
||||
fileData Bytes // Store in DB as bytes (self-hosted, no S3)
|
||||
dateTaken DateTime?
|
||||
expiryDate DateTime?
|
||||
notes String?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdById String
|
||||
|
||||
// Sync (no offline sync for file blobs — too large)
|
||||
version Int @default(1)
|
||||
syncedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("DocCreatedBy", fields: [createdById], references: [id])
|
||||
|
||||
@@index([workspaceId, category])
|
||||
@@index([workspaceId, deletedAt])
|
||||
@@index([syncedAt])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// DRUG INTERACTIONS (cached lookups)
|
||||
// ============================================
|
||||
|
||||
model DrugInteraction {
|
||||
id String @id @default(cuid())
|
||||
workspaceId String
|
||||
medication1Id String
|
||||
medication2Id String
|
||||
severity String // "MINOR", "MODERATE", "MAJOR", "CONTRAINDICATED"
|
||||
description String
|
||||
checkedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
medication1 Medication @relation("Interaction1", fields: [medication1Id], references: [id], onDelete: Cascade)
|
||||
medication2 Medication @relation("Interaction2", fields: [medication2Id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([workspaceId, medication1Id, medication2Id])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
```
|
||||
|
||||
### User model additions (relations)
|
||||
```prisma
|
||||
// Add to User model:
|
||||
temperatureLogs TemperatureLog[] @relation("TempLogCreatedBy")
|
||||
createdContacts Contact[] @relation("ContactCreatedBy")
|
||||
updatedContacts Contact[] @relation("ContactUpdatedBy")
|
||||
weightLogs WeightLog[] @relation("WeightLogCreatedBy")
|
||||
createdMilestones TreatmentMilestone[] @relation("MilestoneCreatedBy")
|
||||
updatedMilestones TreatmentMilestone[] @relation("MilestoneUpdatedBy")
|
||||
createdTasks CaregiverTask[] @relation("TaskCreatedBy")
|
||||
updatedTasks CaregiverTask[] @relation("TaskUpdatedBy")
|
||||
assignedTasks CaregiverTask[] @relation("TaskAssignedTo")
|
||||
completedTasks CaregiverTask[] @relation("TaskCompletedBy")
|
||||
createdLabResults LabResult[] @relation("LabResultCreatedBy")
|
||||
updatedLabResults LabResult[] @relation("LabResultUpdatedBy")
|
||||
createdDocuments MedicalDocument[] @relation("DocCreatedBy")
|
||||
|
||||
// Add to Workspace model:
|
||||
temperatureLogs TemperatureLog[]
|
||||
contacts Contact[]
|
||||
weightLogs WeightLog[]
|
||||
milestones TreatmentMilestone[]
|
||||
caregiverTasks CaregiverTask[]
|
||||
labResults LabResult[]
|
||||
medicalDocuments MedicalDocument[]
|
||||
drugInteractions DrugInteraction[]
|
||||
|
||||
// Add to Medication model:
|
||||
interactions1 DrugInteraction[] @relation("Interaction1")
|
||||
interactions2 DrugInteraction[] @relation("Interaction2")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dexie DB (Version 3)
|
||||
|
||||
Add to `src/lib/sync/db.ts`:
|
||||
|
||||
```typescript
|
||||
// New interfaces
|
||||
export interface LocalTemperatureLog {
|
||||
id: string
|
||||
workspaceId: string
|
||||
recordedAt: string
|
||||
tempCelsius: number
|
||||
method: string | null
|
||||
notes: string | null
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
createdBy?: { id: string; name: string }
|
||||
}
|
||||
|
||||
export interface LocalContact {
|
||||
id: string
|
||||
workspaceId: string
|
||||
name: string
|
||||
role: string
|
||||
category: string
|
||||
phone: string
|
||||
phone2: string | null
|
||||
email: string | null
|
||||
address: string | null
|
||||
hours: string | null
|
||||
notes: string | null
|
||||
isEmergency: boolean
|
||||
sortOrder: number
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
}
|
||||
|
||||
export interface LocalWeightLog {
|
||||
id: string
|
||||
workspaceId: string
|
||||
recordedAt: string
|
||||
weightKg: number
|
||||
notes: string | null
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
createdBy?: { id: string; name: string }
|
||||
}
|
||||
|
||||
export interface LocalMilestone {
|
||||
id: string
|
||||
workspaceId: string
|
||||
type: string
|
||||
title: string
|
||||
description: string | null
|
||||
plannedDate: string
|
||||
actualDate: string | null
|
||||
status: string
|
||||
sortOrder: number
|
||||
notes: string | null
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
}
|
||||
|
||||
export interface LocalCaregiverTask {
|
||||
id: string
|
||||
workspaceId: string
|
||||
title: string
|
||||
description: string | null
|
||||
category: string
|
||||
priority: string
|
||||
status: string
|
||||
assignedToId: string | null
|
||||
dueDate: string | null
|
||||
completedAt: string | null
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
assignedTo?: { id: string; name: string }
|
||||
createdBy?: { id: string; name: string }
|
||||
}
|
||||
|
||||
export interface LocalLabResult {
|
||||
id: string
|
||||
workspaceId: string
|
||||
testDate: string
|
||||
panelName: string
|
||||
labName: string | null
|
||||
results: Array<{
|
||||
marker: string
|
||||
value: number
|
||||
unit: string
|
||||
refMin: number | null
|
||||
refMax: number | null
|
||||
flag: string | null // "LOW", "HIGH", "CRITICAL_LOW", "CRITICAL_HIGH", null
|
||||
}>
|
||||
notes: string | null
|
||||
deletedAt: string | null
|
||||
version: number
|
||||
syncedAt: string
|
||||
}
|
||||
|
||||
// Version 3 stores
|
||||
this.version(3).stores({
|
||||
appointments: 'id, workspaceId, datetime, deletedAt',
|
||||
medications: 'id, workspaceId, active, deletedAt',
|
||||
notes: 'id, workspaceId, type, deletedAt',
|
||||
doseLogs: 'id, medicationId, workspaceId, takenAt',
|
||||
workspaces: 'id',
|
||||
symptoms: 'id, workspaceId, type, recordedAt, deletedAt',
|
||||
temperatureLogs: 'id, workspaceId, recordedAt, deletedAt',
|
||||
contacts: 'id, workspaceId, category, deletedAt',
|
||||
weightLogs: 'id, workspaceId, recordedAt, deletedAt',
|
||||
milestones: 'id, workspaceId, plannedDate, status, deletedAt',
|
||||
caregiverTasks: 'id, workspaceId, status, assignedToId, deletedAt',
|
||||
labResults: 'id, workspaceId, testDate, deletedAt',
|
||||
outbox: 'id, workspaceId, timestamp',
|
||||
syncMeta: 'id, workspaceId',
|
||||
})
|
||||
```
|
||||
|
||||
Note: Medical documents are NOT stored in Dexie (too large for IndexedDB). They are server-only.
|
||||
|
||||
---
|
||||
|
||||
## Zod Validation Schemas
|
||||
|
||||
Add to `src/lib/validation/schemas.ts`:
|
||||
|
||||
```typescript
|
||||
// Temperature Log
|
||||
export const temperatureLogSchema = z.object({
|
||||
tempCelsius: z.number().min(30).max(45),
|
||||
method: z.enum(['oral', 'forehead', 'ear', 'armpit']).nullable().optional(),
|
||||
notes: z.string().max(500).nullable().optional(),
|
||||
recordedAt: z.string().datetime().optional(),
|
||||
})
|
||||
|
||||
// Contact
|
||||
export const contactSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required').max(200),
|
||||
role: z.string().min(1, 'Role is required').max(100),
|
||||
category: z.enum(['ONCOLOGY', 'HOSPITAL', 'PHARMACY', 'INSURANCE', 'FAMILY', 'OTHER']),
|
||||
phone: z.string().min(1, 'Phone is required').max(50),
|
||||
phone2: z.string().max(50).nullable().optional(),
|
||||
email: z.string().email().max(200).nullable().optional(),
|
||||
address: z.string().max(500).nullable().optional(),
|
||||
hours: z.string().max(200).nullable().optional(),
|
||||
notes: z.string().max(1000).nullable().optional(),
|
||||
isEmergency: z.boolean().default(false),
|
||||
sortOrder: z.number().int().min(0).default(0),
|
||||
})
|
||||
|
||||
// Weight Log
|
||||
export const weightLogSchema = z.object({
|
||||
weightKg: z.number().min(1).max(500),
|
||||
notes: z.string().max(500).nullable().optional(),
|
||||
recordedAt: z.string().datetime().optional(),
|
||||
})
|
||||
|
||||
// Treatment Milestone
|
||||
export const milestoneSchema = z.object({
|
||||
type: z.enum(['CHEMO_CYCLE', 'SURGERY', 'RADIATION', 'SCAN', 'CONSULTATION', 'OTHER']),
|
||||
title: z.string().min(1, 'Title is required').max(200),
|
||||
description: z.string().max(1000).nullable().optional(),
|
||||
plannedDate: z.string().datetime(),
|
||||
actualDate: z.string().datetime().nullable().optional(),
|
||||
status: z.enum(['SCHEDULED', 'COMPLETED', 'DELAYED', 'CANCELLED']).default('SCHEDULED'),
|
||||
notes: z.string().max(2000).nullable().optional(),
|
||||
})
|
||||
|
||||
// Caregiver Task
|
||||
export const caregiverTaskSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required').max(200),
|
||||
description: z.string().max(2000).nullable().optional(),
|
||||
category: z.enum(['MEDICAL', 'ERRANDS', 'MEALS', 'EMOTIONAL', 'OTHER']),
|
||||
priority: z.enum(['URGENT', 'HIGH', 'NORMAL', 'LOW']).default('NORMAL'),
|
||||
status: z.enum(['TODO', 'IN_PROGRESS', 'DONE', 'CANCELLED']).default('TODO'),
|
||||
assignedToId: z.string().cuid().nullable().optional(),
|
||||
dueDate: z.string().datetime().nullable().optional(),
|
||||
})
|
||||
|
||||
// Lab Result
|
||||
const labMarkerSchema = z.object({
|
||||
marker: z.string().min(1).max(50),
|
||||
value: z.number(),
|
||||
unit: z.string().max(20),
|
||||
refMin: z.number().nullable().optional(),
|
||||
refMax: z.number().nullable().optional(),
|
||||
flag: z.enum(['LOW', 'HIGH', 'CRITICAL_LOW', 'CRITICAL_HIGH']).nullable().optional(),
|
||||
})
|
||||
|
||||
export const labResultSchema = z.object({
|
||||
testDate: z.string().datetime(),
|
||||
panelName: z.string().min(1).max(200),
|
||||
labName: z.string().max(200).nullable().optional(),
|
||||
results: z.array(labMarkerSchema).min(1),
|
||||
notes: z.string().max(2000).nullable().optional(),
|
||||
})
|
||||
|
||||
// Medical Document (metadata only — file sent as multipart)
|
||||
export const medicalDocumentSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required').max(200),
|
||||
category: z.enum(['LAB_REPORT', 'SCAN', 'INSURANCE', 'ID_CARD', 'PRESCRIPTION', 'OTHER']),
|
||||
dateTaken: z.string().datetime().nullable().optional(),
|
||||
expiryDate: z.string().datetime().nullable().optional(),
|
||||
notes: z.string().max(1000).nullable().optional(),
|
||||
})
|
||||
|
||||
// Drug Interaction Check
|
||||
export const interactionCheckSchema = z.object({
|
||||
medicationIds: z.array(z.string().cuid()).min(2).max(20),
|
||||
})
|
||||
|
||||
// Type exports
|
||||
export type TemperatureLogInput = z.infer<typeof temperatureLogSchema>
|
||||
export type ContactInput = z.infer<typeof contactSchema>
|
||||
export type WeightLogInput = z.infer<typeof weightLogSchema>
|
||||
export type MilestoneInput = z.infer<typeof milestoneSchema>
|
||||
export type CaregiverTaskInput = z.infer<typeof caregiverTaskSchema>
|
||||
export type LabMarker = z.infer<typeof labMarkerSchema>
|
||||
export type LabResultInput = z.infer<typeof labResultSchema>
|
||||
export type MedicalDocumentInput = z.infer<typeof medicalDocumentSchema>
|
||||
export type InteractionCheckInput = z.infer<typeof interactionCheckSchema>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sync Ops Extensions
|
||||
|
||||
Add to `syncOpSchema.type`:
|
||||
```
|
||||
'LOG_TEMP', 'DELETE_TEMP',
|
||||
'CREATE_CONTACT', 'UPDATE_CONTACT', 'DELETE_CONTACT',
|
||||
'LOG_WEIGHT', 'DELETE_WEIGHT',
|
||||
'CREATE_MILESTONE', 'UPDATE_MILESTONE', 'DELETE_MILESTONE',
|
||||
'CREATE_TASK', 'UPDATE_TASK', 'DELETE_TASK', 'COMPLETE_TASK',
|
||||
'CREATE_LAB', 'UPDATE_LAB', 'DELETE_LAB'
|
||||
```
|
||||
|
||||
Add to `syncOpSchema.entityType`:
|
||||
```
|
||||
'TEMPERATURE_LOG', 'CONTACT', 'WEIGHT_LOG', 'MILESTONE', 'CAREGIVER_TASK', 'LAB_RESULT'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Feature 1: Temperature Log
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/temperature` | List logs (query: from, to, limit) |
|
||||
| POST | `/api/workspaces/[id]/temperature` | Create log |
|
||||
| DELETE | `/api/workspaces/[id]/temperature/[tempId]` | Soft delete |
|
||||
|
||||
### Feature 2: Contact Directory
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/contacts` | List contacts (query: category) |
|
||||
| POST | `/api/workspaces/[id]/contacts` | Create contact |
|
||||
| PATCH | `/api/workspaces/[id]/contacts/[contactId]` | Update contact |
|
||||
| DELETE | `/api/workspaces/[id]/contacts/[contactId]` | Soft delete |
|
||||
|
||||
### Feature 3: Weight Log
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/weight` | List logs (query: from, to, limit) |
|
||||
| POST | `/api/workspaces/[id]/weight` | Create log |
|
||||
| DELETE | `/api/workspaces/[id]/weight/[weightId]` | Soft delete |
|
||||
|
||||
### Feature 4: Treatment Timeline
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/milestones` | List milestones |
|
||||
| POST | `/api/workspaces/[id]/milestones` | Create milestone |
|
||||
| PATCH | `/api/workspaces/[id]/milestones/[milestoneId]` | Update (inc. status) |
|
||||
| DELETE | `/api/workspaces/[id]/milestones/[milestoneId]` | Soft delete |
|
||||
|
||||
### Feature 5: Caregiver Tasks
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/tasks` | List tasks (query: status, assignedTo) |
|
||||
| POST | `/api/workspaces/[id]/tasks` | Create task |
|
||||
| PATCH | `/api/workspaces/[id]/tasks/[taskId]` | Update task |
|
||||
| POST | `/api/workspaces/[id]/tasks/[taskId]/complete` | Mark complete |
|
||||
| DELETE | `/api/workspaces/[id]/tasks/[taskId]` | Soft delete |
|
||||
|
||||
### Feature 6: Lab Results
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/lab-results` | List results (query: from, to) |
|
||||
| GET | `/api/workspaces/[id]/lab-results/trends` | Trend data for specific marker |
|
||||
| POST | `/api/workspaces/[id]/lab-results` | Create result |
|
||||
| PATCH | `/api/workspaces/[id]/lab-results/[labId]` | Update result |
|
||||
| DELETE | `/api/workspaces/[id]/lab-results/[labId]` | Soft delete |
|
||||
|
||||
### Feature 7: Medical Documents
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/workspaces/[id]/documents` | List documents (metadata only) |
|
||||
| POST | `/api/workspaces/[id]/documents` | Upload (multipart/form-data) |
|
||||
| GET | `/api/workspaces/[id]/documents/[docId]` | Download file |
|
||||
| DELETE | `/api/workspaces/[id]/documents/[docId]` | Soft delete |
|
||||
|
||||
### Feature 8: Drug Interactions
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/api/workspaces/[id]/medications/check-interactions` | Check all meds |
|
||||
| GET | `/api/workspaces/[id]/medications/interactions` | Get cached results |
|
||||
|
||||
---
|
||||
|
||||
## UI/Page Design
|
||||
|
||||
### Navigation Change
|
||||
|
||||
The bottom nav currently has 5 items. With 8 new features, the "More" (Settings) tab becomes a hub. The new features are accessed via:
|
||||
|
||||
- **Today page** surfaces: Temperature, Weight (quick log cards), upcoming tasks, next milestone
|
||||
- **Bottom nav** stays as-is (Today, Appts, Meds, Symptoms, More)
|
||||
- **"More" page** (`/settings`) becomes a menu with sections:
|
||||
- Account & Settings (existing)
|
||||
- **Health Tracking**: Temperature, Weight, Lab Results
|
||||
- **Care Team**: Contact Directory, Caregiver Tasks
|
||||
- **Treatment**: Timeline, Medical Documents
|
||||
- **Safety**: Drug Interactions
|
||||
|
||||
### Feature 1: Temperature Log — `/temperature`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Temperature" with History icon (right)
|
||||
- Quick log card: big number input (keyboard type=decimal), method selector (4 pill buttons: Oral/Forehead/Ear/Armpit), optional notes, "Log Temperature" button
|
||||
- **Fever alert banner**: if last reading >= 38.0°C, show red alert card: "FEVER DETECTED — 38.3°C" with "Call Clinic" button using `tel:` link from workspace.clinicPhone
|
||||
- Last 7 days: mini chart (horizontal bar or sparkline showing daily temps)
|
||||
- Recent readings: list of TemperatureCard components
|
||||
|
||||
**Components:**
|
||||
- `src/components/temperature/TempQuickLog.tsx` — number input, method pills, submit
|
||||
- `src/components/temperature/TempCard.tsx` — single reading display
|
||||
- `src/components/temperature/TempChart.tsx` — 7-day chart (pure CSS bars, no lib needed)
|
||||
- `src/components/temperature/FeverAlert.tsx` — red alert banner with call button
|
||||
|
||||
**Key UX:**
|
||||
- Default unit based on locale (°C for AU). Display toggle °C/°F.
|
||||
- 38.0°C threshold → yellow warning. 38.5°C → red emergency.
|
||||
- Number input: show decimal keyboard on mobile via `inputMode="decimal"`
|
||||
|
||||
### Feature 2: Contact Directory — `/contacts`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Care Team" with Plus icon (right)
|
||||
- Category filter tabs: All / Oncology / Hospital / Pharmacy / Insurance / Family
|
||||
- Emergency contacts section at top (if any marked isEmergency)
|
||||
- Contact cards: avatar circle (first letter), name, role, big green CALL button
|
||||
- Tap card → expand to show all details
|
||||
|
||||
**Components:**
|
||||
- `src/components/contacts/ContactCard.tsx` — name, role, call button, expandable
|
||||
- `src/components/contacts/ContactForm.tsx` — modal form for create/edit
|
||||
- `src/components/contacts/CategoryTabs.tsx` — horizontal scroll filter
|
||||
|
||||
### Feature 3: Weight Log — `/weight`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Weight" with History icon
|
||||
- Quick log: large number input (kg), small toggle for kg/lbs, notes, "Log Weight" button
|
||||
- Trend card: 30-day line chart (CSS-based or simple SVG)
|
||||
- Alert card: if weight changed >2kg in 24hrs, show warning
|
||||
- Recent readings: list
|
||||
|
||||
**Components:**
|
||||
- `src/components/weight/WeightQuickLog.tsx`
|
||||
- `src/components/weight/WeightCard.tsx`
|
||||
- `src/components/weight/WeightChart.tsx` — simple SVG line chart
|
||||
- `src/components/weight/WeightAlert.tsx` — rapid change warning
|
||||
|
||||
### Feature 4: Treatment Timeline — `/timeline`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Treatment Journey" with Plus icon
|
||||
- Progress bar at top: "Cycle 4 of 6 — 67% Complete" (calculated from completed/total milestones)
|
||||
- Vertical timeline: milestones sorted by plannedDate
|
||||
- Left: date
|
||||
- Center: dot (green=completed, blue=scheduled, orange=delayed, gray=cancelled)
|
||||
- Right: title, type badge, notes
|
||||
- Bottom: "Add Milestone" button
|
||||
|
||||
**Components:**
|
||||
- `src/components/timeline/TimelineView.tsx` — vertical timeline layout
|
||||
- `src/components/timeline/MilestoneCard.tsx` — single milestone
|
||||
- `src/components/timeline/ProgressBar.tsx` — overall progress
|
||||
- `src/components/timeline/MilestoneForm.tsx` — modal for create/edit
|
||||
|
||||
**Key UX:**
|
||||
- Completed milestones have a subtle celebration effect (checkmark)
|
||||
- Auto-scroll to "now" position in timeline
|
||||
- Color by type: blue=chemo, orange=surgery, purple=radiation, green=scan
|
||||
|
||||
### Feature 5: Caregiver Tasks — `/tasks`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Tasks" with Plus icon
|
||||
- Filter tabs: My Tasks / All / Done
|
||||
- Task list grouped by priority (Urgent at top)
|
||||
- Each task: title, assignee avatar, due date, category chip, priority indicator
|
||||
- Swipe right to complete (or tap checkbox)
|
||||
- FAB or bottom "Add Task" button
|
||||
|
||||
**Components:**
|
||||
- `src/components/tasks/TaskCard.tsx` — task with checkbox, assignee, due date
|
||||
- `src/components/tasks/TaskForm.tsx` — modal with assignee picker (workspace members)
|
||||
- `src/components/tasks/TaskFilters.tsx` — status/assignee filter
|
||||
|
||||
**Key UX:**
|
||||
- "Quick add" templates: "Pick up prescription", "Drive to appointment", "Prepare meals"
|
||||
- Overdue tasks highlighted in accent/red
|
||||
- Completion shows brief success animation
|
||||
|
||||
### Feature 6: Lab Results — `/lab-results`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Lab Results" with Plus icon
|
||||
- Tab: Recent / Trends
|
||||
- **Recent tab:** List of lab result cards sorted by date, showing panel name, date, flag count
|
||||
- **Trends tab:** Marker selector (dropdown: WBC, RBC, Platelets, Hemoglobin, etc.) → SVG line chart with reference range shaded
|
||||
- Add result: modal with panel template selector (CBC template pre-fills common markers)
|
||||
|
||||
**Components:**
|
||||
- `src/components/labs/LabResultCard.tsx` — panel summary with flagged values highlighted
|
||||
- `src/components/labs/LabResultForm.tsx` — panel selector + marker rows (marker/value/unit/range)
|
||||
- `src/components/labs/LabTrendChart.tsx` — SVG chart with ref range shading
|
||||
- `src/components/labs/MarkerRow.tsx` — single marker with flag coloring
|
||||
|
||||
**Key UX:**
|
||||
- Pre-built panel templates: CBC (WBC, RBC, Hemoglobin, Hematocrit, Platelets), CMP, Liver, Tumor Markers
|
||||
- Flag colors: green=normal, yellow=borderline, red=out of range, dark red=critical
|
||||
- "Share with doctor" → links to print page
|
||||
|
||||
### Feature 7: Medical Documents — `/documents`
|
||||
|
||||
**Page structure:**
|
||||
- Header: "Documents" with Plus icon (upload)
|
||||
- Category filter: All / Lab Reports / Scans / Insurance / Prescriptions
|
||||
- Document grid: 2 columns, thumbnail (icon by type), title, date, category badge
|
||||
- Tap → full-screen viewer (PDF in iframe, images native)
|
||||
- Upload: file picker, category select, title, date, notes
|
||||
|
||||
**Components:**
|
||||
- `src/components/documents/DocumentCard.tsx` — thumbnail, title, category badge
|
||||
- `src/components/documents/DocumentUpload.tsx` — file picker modal
|
||||
- `src/components/documents/DocumentViewer.tsx` — full-screen view
|
||||
|
||||
**Key UX:**
|
||||
- Max file size: 10MB
|
||||
- Accepted types: PDF, JPG, PNG
|
||||
- Expiry badge on insurance cards approaching expiry
|
||||
- No offline sync for documents (too large) — show "Requires internet" badge
|
||||
|
||||
### Feature 8: Drug Interaction Checker — `/meds` (integrated)
|
||||
|
||||
**Not a separate page.** Integrated into the medications section:
|
||||
- "Check Interactions" button on meds list page
|
||||
- Results shown as a modal/sheet with severity-colored cards
|
||||
- Warning banner on individual medication detail pages if interactions exist
|
||||
|
||||
**Implementation approach (simplified, no external API for v1):**
|
||||
- Ship with a local lookup table of ~200 common chemo drug interactions (JSON file)
|
||||
- `src/lib/interactions/checker.ts` — pure function that takes med names, returns known interactions
|
||||
- `src/lib/interactions/data.ts` — curated interaction database
|
||||
- Can upgrade to external API (OpenFDA/RxNorm) later
|
||||
|
||||
**Components:**
|
||||
- `src/components/medications/InteractionCheck.tsx` — button + results modal
|
||||
- `src/components/medications/InteractionCard.tsx` — severity badge, description
|
||||
- `src/components/medications/InteractionBanner.tsx` — warning on med detail
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order & Tasks
|
||||
|
||||
### Batch 1: Schema & Infrastructure (do first)
|
||||
|
||||
- [ ] **Prisma schema migration** `priority:1` `time:30min`
|
||||
- files: `prisma/schema.prisma`
|
||||
- Add all 8 models + User/Workspace/Medication relation updates
|
||||
- Run `npx prisma migrate dev --name add-eight-features`
|
||||
- Verify migration succeeds
|
||||
|
||||
- [ ] **Zod validation schemas** `priority:1` `time:20min`
|
||||
- files: `src/lib/validation/schemas.ts`
|
||||
- Add all 8 schema definitions and type exports
|
||||
|
||||
- [ ] **Dexie DB version 3** `priority:1` `time:20min`
|
||||
- files: `src/lib/sync/db.ts`
|
||||
- Add interfaces and version 3 stores
|
||||
|
||||
- [ ] **Sync ops expansion** `priority:1` `time:15min`
|
||||
- files: `src/lib/sync/manager.ts`, `src/lib/validation/schemas.ts`
|
||||
- Add new entity types and op types to sync schema
|
||||
|
||||
### Batch 2: Low Complexity Features (build fast)
|
||||
|
||||
- [ ] **Feature 1: Temperature Log** `priority:2` `deps:Batch 1` `time:3hr`
|
||||
- API: `src/app/api/workspaces/[id]/temperature/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/temperature/[tempId]/route.ts`
|
||||
- Components: `src/components/temperature/TempQuickLog.tsx`
|
||||
- Components: `src/components/temperature/TempCard.tsx`
|
||||
- Components: `src/components/temperature/TempChart.tsx`
|
||||
- Components: `src/components/temperature/FeverAlert.tsx`
|
||||
- Page: `src/app/(app)/temperature/page.tsx`
|
||||
- Page: `src/app/(app)/temperature/history/page.tsx`
|
||||
|
||||
- [ ] **Feature 2: Contact Directory** `priority:2` `deps:Batch 1` `time:3hr`
|
||||
- API: `src/app/api/workspaces/[id]/contacts/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/contacts/[contactId]/route.ts`
|
||||
- Components: `src/components/contacts/ContactCard.tsx`
|
||||
- Components: `src/components/contacts/ContactForm.tsx`
|
||||
- Components: `src/components/contacts/CategoryTabs.tsx`
|
||||
- Page: `src/app/(app)/contacts/page.tsx`
|
||||
|
||||
- [ ] **Feature 3: Weight Log** `priority:2` `deps:Batch 1` `time:2.5hr`
|
||||
- API: `src/app/api/workspaces/[id]/weight/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/weight/[weightId]/route.ts`
|
||||
- Components: `src/components/weight/WeightQuickLog.tsx`
|
||||
- Components: `src/components/weight/WeightCard.tsx`
|
||||
- Components: `src/components/weight/WeightChart.tsx`
|
||||
- Components: `src/components/weight/WeightAlert.tsx`
|
||||
- Page: `src/app/(app)/weight/page.tsx`
|
||||
- Page: `src/app/(app)/weight/history/page.tsx`
|
||||
|
||||
### Batch 3: Medium Complexity Features
|
||||
|
||||
- [ ] **Feature 4: Treatment Timeline** `priority:3` `deps:Batch 1` `time:4hr`
|
||||
- API: `src/app/api/workspaces/[id]/milestones/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/milestones/[milestoneId]/route.ts`
|
||||
- Components: `src/components/timeline/TimelineView.tsx`
|
||||
- Components: `src/components/timeline/MilestoneCard.tsx`
|
||||
- Components: `src/components/timeline/ProgressBar.tsx`
|
||||
- Components: `src/components/timeline/MilestoneForm.tsx`
|
||||
- Page: `src/app/(app)/timeline/page.tsx`
|
||||
|
||||
- [ ] **Feature 5: Caregiver Tasks** `priority:3` `deps:Batch 1` `time:4hr`
|
||||
- API: `src/app/api/workspaces/[id]/tasks/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/tasks/[taskId]/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/tasks/[taskId]/complete/route.ts`
|
||||
- Components: `src/components/tasks/TaskCard.tsx`
|
||||
- Components: `src/components/tasks/TaskForm.tsx`
|
||||
- Components: `src/components/tasks/TaskFilters.tsx`
|
||||
- Page: `src/app/(app)/tasks/page.tsx`
|
||||
|
||||
### Batch 4: High Complexity Features
|
||||
|
||||
- [ ] **Feature 6: Lab Results** `priority:4` `deps:Batch 1` `time:5hr`
|
||||
- Lib: `src/lib/labs/panels.ts` (CBC, CMP, Liver panel templates)
|
||||
- API: `src/app/api/workspaces/[id]/lab-results/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/lab-results/trends/route.ts`
|
||||
- API: `src/app/api/workspaces/[id]/lab-results/[labId]/route.ts`
|
||||
- Components: `src/components/labs/LabResultCard.tsx`
|
||||
- Components: `src/components/labs/LabResultForm.tsx`
|
||||
- Components: `src/components/labs/LabTrendChart.tsx`
|
||||
- Components: `src/components/labs/MarkerRow.tsx`
|
||||
- Page: `src/app/(app)/lab-results/page.tsx`
|
||||
|
||||
- [ ] **Feature 7: Medical Documents** `priority:4` `deps:Batch 1` `time:4hr`
|
||||
- API: `src/app/api/workspaces/[id]/documents/route.ts` (multipart upload)
|
||||
- API: `src/app/api/workspaces/[id]/documents/[docId]/route.ts` (download + delete)
|
||||
- Components: `src/components/documents/DocumentCard.tsx`
|
||||
- Components: `src/components/documents/DocumentUpload.tsx`
|
||||
- Components: `src/components/documents/DocumentViewer.tsx`
|
||||
- Page: `src/app/(app)/documents/page.tsx`
|
||||
|
||||
- [ ] **Feature 8: Drug Interactions** `priority:4` `deps:Batch 1` `time:3hr`
|
||||
- Lib: `src/lib/interactions/data.ts` (curated interaction database)
|
||||
- Lib: `src/lib/interactions/checker.ts` (lookup logic)
|
||||
- API: `src/app/api/workspaces/[id]/medications/check-interactions/route.ts`
|
||||
- Components: `src/components/medications/InteractionCheck.tsx`
|
||||
- Components: `src/components/medications/InteractionCard.tsx`
|
||||
- Components: `src/components/medications/InteractionBanner.tsx`
|
||||
|
||||
### Batch 5: Integration & Polish
|
||||
|
||||
- [ ] **Update Settings/More page** `priority:5` `time:1hr`
|
||||
- files: `src/app/(app)/settings/page.tsx`
|
||||
- Add navigation links to all new features grouped by section
|
||||
|
||||
- [ ] **Update Today dashboard** `priority:5` `time:2hr`
|
||||
- files: `src/app/(app)/today/page.tsx`
|
||||
- Add cards: latest temp, pending tasks, next milestone, weight trend
|
||||
- Fever alert banner at top if applicable
|
||||
|
||||
- [ ] **Update EmptyState component** `priority:5` `time:15min`
|
||||
- files: `src/components/ui/states.tsx`
|
||||
- Add new icon types: temperature, contacts, weight, timeline, tasks, labs, documents
|
||||
|
||||
- [ ] **Tests** `priority:5` `time:2hr`
|
||||
- Temperature threshold logic
|
||||
- Weight change alert calculations
|
||||
- Lab result flag detection
|
||||
- Drug interaction checker
|
||||
- All Zod schemas validation
|
||||
|
||||
---
|
||||
|
||||
## Total Estimated Time
|
||||
|
||||
| Batch | Time |
|
||||
|-------|------|
|
||||
| Batch 1: Schema & Infrastructure | ~1.5hr |
|
||||
| Batch 2: Temperature + Contacts + Weight | ~8.5hr |
|
||||
| Batch 3: Timeline + Tasks | ~8hr |
|
||||
| Batch 4: Labs + Documents + Interactions | ~12hr |
|
||||
| Batch 5: Integration & Polish | ~5hr |
|
||||
| **Total** | **~35hr** |
|
||||
|
||||
With parallel work on independent features, achievable in 4-5 focused days.
|
||||
Reference in New Issue
Block a user