Add 11 major features for caregiver health management

Features added:
- Emergency Info Card: Full-screen emergency view with patient info
- Refill Tracker: Track pill counts with auto-decrement on dose
- Activity Feed: View caregiver activity with filtering
- Symptom Tracker: Log symptoms with severity and offline sync
- Print Views: Daily meds, appointments, doctor visit summaries
- iCal Export: Calendar subscription for appointments
- PDF Export: Medical summary for doctor visits
- Calendar View: Monthly calendar for appointments
- Appointment Preparation: Checklist for upcoming appointments
- Medication Reminders: PWA push notifications with quiet hours

Bug fixes:
- Fix invite workflow: Register/login now properly redirect back
- Add undo for doctor questions (can unmark "asked" questions)
- Fix API route type annotations for Next.js 14 compatibility
- Add Suspense boundary for useSearchParams in login/register

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gemini Agent
2026-01-23 09:42:46 +00:00
parent 515376e126
commit dd4ef2c4cd
70 changed files with 7322 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
import { db, generateTempId, type SyncOp } from './db'
import type { LocalAppointment, LocalMedication, LocalNote, LocalDoseLog } from './db'
import type { LocalAppointment, LocalMedication, LocalNote, LocalDoseLog, LocalSymptom } from './db'
const SYNC_INTERVAL = 30000 // 30 seconds
const MAX_RETRIES = 3
@@ -70,12 +70,20 @@ export async function pullChanges(workspaceId: string): Promise<boolean> {
const data = await response.json()
// Update local database
await db.transaction('rw', [db.appointments, db.medications, db.notes, db.doseLogs, db.workspaces, db.syncMeta], async () => {
// Update workspace
await db.transaction('rw', [db.appointments, db.medications, db.notes, db.doseLogs, db.symptoms, db.workspaces, db.syncMeta], async () => {
// Update workspace (including emergency info fields)
if (data.workspace) {
await db.workspaces.put({
...data.workspace,
updatedAt: data.workspace.updatedAt || new Date().toISOString(),
// Ensure emergency fields are properly set (even if null)
patientName: data.workspace.patientName || null,
patientDOB: data.workspace.patientDOB || null,
bloodType: data.workspace.bloodType || null,
allergies: data.workspace.allergies || null,
medicalConditions: data.workspace.medicalConditions || null,
primaryPhysician: data.workspace.primaryPhysician || null,
physicianPhone: data.workspace.physicianPhone || null,
})
}
@@ -132,6 +140,18 @@ export async function pullChanges(workspaceId: string): Promise<boolean> {
}
}
// Update symptoms
for (const symptom of data.symptoms || []) {
const existing = await db.symptoms.get(symptom.id)
if (!existing || new Date(symptom.syncedAt) > new Date(existing.syncedAt)) {
await db.symptoms.put({
...symptom,
recordedAt: symptom.recordedAt,
syncedAt: symptom.syncedAt,
})
}
}
// Update sync cursor
await db.syncMeta.put({
id: workspaceId,
@@ -179,7 +199,7 @@ export async function pushChanges(workspaceId: string): Promise<boolean> {
const data = await response.json()
// Process results and remove successful ops from outbox
await db.transaction('rw', [db.outbox, db.appointments, db.notes], async () => {
await db.transaction('rw', [db.outbox, db.appointments, db.notes, db.symptoms], async () => {
for (const result of data.results) {
if (result.success) {
// Find the op
@@ -198,6 +218,12 @@ export async function pushChanges(workspaceId: string): Promise<boolean> {
await db.notes.delete(op.entityId)
await db.notes.put({ ...local, id: result.entityId })
}
} else if (op.entityType === 'SYMPTOM') {
const local = await db.symptoms.get(op.entityId)
if (local) {
await db.symptoms.delete(op.entityId)
await db.symptoms.put({ ...local, id: result.entityId })
}
}
}
@@ -408,6 +434,14 @@ export async function logDose(
}
await db.doseLogs.add(doseLog)
// Decrement pill count if tracking is enabled
const localMed = await db.medications.get(medicationId)
if (localMed && localMed.pillCount !== null && localMed.pillsPerDose !== null) {
const newCount = Math.max(0, localMed.pillCount - (localMed.pillsPerDose || 1))
await db.medications.update(medicationId, { pillCount: newCount })
}
await addToOutbox({
workspaceId,
type: 'TAKE_DOSE',
@@ -424,6 +458,13 @@ export async function undoDose(doseLog: LocalDoseLog): Promise<void> {
const now = new Date().toISOString()
await db.doseLogs.update(doseLog.id, { undoneAt: now })
// Restore pill count if tracking is enabled
const localMed = await db.medications.get(doseLog.medicationId)
if (localMed && localMed.pillCount !== null && localMed.pillsPerDose !== null) {
const newCount = localMed.pillCount + (localMed.pillsPerDose || 1)
await db.medications.update(doseLog.medicationId, { pillCount: newCount })
}
await addToOutbox({
workspaceId: doseLog.workspaceId,
type: 'UNDO_DOSE',
@@ -445,3 +486,75 @@ export async function markQuestionAsked(note: LocalNote): Promise<void> {
timestamp: Date.now(),
})
}
export async function unmarkQuestionAsked(note: LocalNote): Promise<void> {
await db.notes.update(note.id, { askedAt: null })
await addToOutbox({
workspaceId: note.workspaceId,
type: 'UNMARK_ASKED',
entityType: 'NOTE',
entityId: note.id,
timestamp: Date.now(),
})
}
export async function logSymptom(
workspaceId: string,
data: {
type: LocalSymptom['type']
customName?: string
severity: number
notes?: string
}
): Promise<LocalSymptom> {
const id = generateTempId()
const now = new Date().toISOString()
const symptom: LocalSymptom = {
id,
workspaceId,
type: data.type,
customName: data.customName || null,
severity: data.severity,
notes: data.notes || null,
recordedAt: now,
deletedAt: null,
version: 1,
syncedAt: now,
}
await db.symptoms.add(symptom)
await addToOutbox({
workspaceId,
type: 'LOG_SYMPTOM',
entityType: 'SYMPTOM',
entityId: id,
data: {
type: data.type,
customName: data.customName,
severity: data.severity,
notes: data.notes,
recordedAt: now,
},
timestamp: Date.now(),
})
return symptom
}
export async function deleteSymptom(symptom: LocalSymptom): Promise<void> {
const now = new Date().toISOString()
await db.symptoms.update(symptom.id, {
deletedAt: now,
version: symptom.version + 1,
syncedAt: now,
})
await addToOutbox({
workspaceId: symptom.workspaceId,
type: 'DELETE_SYMPTOM',
entityType: 'SYMPTOM',
entityId: symptom.id,
timestamp: Date.now(),
})
}