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

@@ -0,0 +1,102 @@
-- CreateEnum
CREATE TYPE "SymptomType" AS ENUM ('FATIGUE', 'NAUSEA', 'PAIN', 'APPETITE', 'SLEEP', 'MOOD', 'CUSTOM');
-- AlterTable
ALTER TABLE "Medication" ADD COLUMN "lastRefillDate" TIMESTAMP(3),
ADD COLUMN "pillCount" INTEGER,
ADD COLUMN "pillsPerDose" INTEGER DEFAULT 1,
ADD COLUMN "refillThreshold" INTEGER DEFAULT 7;
-- AlterTable
ALTER TABLE "Workspace" ADD COLUMN "allergies" TEXT,
ADD COLUMN "bloodType" TEXT,
ADD COLUMN "medicalConditions" TEXT,
ADD COLUMN "patientDOB" TIMESTAMP(3),
ADD COLUMN "patientName" TEXT,
ADD COLUMN "physicianPhone" TEXT,
ADD COLUMN "primaryPhysician" TEXT;
-- CreateTable
CREATE TABLE "Symptom" (
"id" TEXT NOT NULL,
"workspaceId" TEXT NOT NULL,
"type" "SymptomType" NOT NULL,
"customName" TEXT,
"severity" INTEGER NOT NULL,
"notes" TEXT,
"recordedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deletedAt" TIMESTAMP(3),
"createdById" TEXT NOT NULL,
"version" INTEGER NOT NULL DEFAULT 1,
"syncedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AppointmentChecklist" (
"id" TEXT NOT NULL,
"workspaceId" TEXT NOT NULL,
"appointmentId" TEXT NOT NULL,
"item" TEXT NOT NULL,
"isReady" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AppointmentChecklist_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PushSubscription" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"workspaceId" TEXT NOT NULL,
"endpoint" TEXT NOT NULL,
"p256dh" TEXT NOT NULL,
"auth" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PushSubscription_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Symptom_workspaceId_recordedAt_idx" ON "Symptom"("workspaceId", "recordedAt");
-- CreateIndex
CREATE INDEX "Symptom_workspaceId_type_idx" ON "Symptom"("workspaceId", "type");
-- CreateIndex
CREATE INDEX "Symptom_workspaceId_deletedAt_idx" ON "Symptom"("workspaceId", "deletedAt");
-- CreateIndex
CREATE INDEX "Symptom_syncedAt_idx" ON "Symptom"("syncedAt");
-- CreateIndex
CREATE INDEX "AppointmentChecklist_appointmentId_idx" ON "AppointmentChecklist"("appointmentId");
-- CreateIndex
CREATE INDEX "AppointmentChecklist_workspaceId_idx" ON "AppointmentChecklist"("workspaceId");
-- CreateIndex
CREATE INDEX "PushSubscription_workspaceId_idx" ON "PushSubscription"("workspaceId");
-- CreateIndex
CREATE UNIQUE INDEX "PushSubscription_userId_endpoint_key" ON "PushSubscription"("userId", "endpoint");
-- AddForeignKey
ALTER TABLE "Symptom" ADD CONSTRAINT "Symptom_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Symptom" ADD CONSTRAINT "Symptom_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AppointmentChecklist" ADD CONSTRAINT "AppointmentChecklist_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AppointmentChecklist" ADD CONSTRAINT "AppointmentChecklist_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PushSubscription" ADD CONSTRAINT "PushSubscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PushSubscription" ADD CONSTRAINT "PushSubscription_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -31,6 +31,8 @@ model User {
loggedDoses DoseLog[] @relation("DoseLoggedBy")
undoneDoses DoseLog[] @relation("DoseUndoneBy")
auditLogs AuditLog[]
symptoms Symptom[]
pushSubscriptions PushSubscription[]
@@index([email])
}
@@ -78,15 +80,27 @@ model Workspace {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Emergency info fields
patientName String?
patientDOB DateTime?
bloodType String?
allergies String? // Comma-separated or free text
medicalConditions String? // Comma-separated or free text
primaryPhysician String?
physicianPhone String?
// Relations
members WorkspaceMember[]
inviteTokens InviteToken[]
appointments Appointment[]
medications Medication[]
notes Note[]
doseLogs DoseLog[]
auditLogs AuditLog[]
syncCursors SyncCursor[]
members WorkspaceMember[]
inviteTokens InviteToken[]
appointments Appointment[]
medications Medication[]
notes Note[]
doseLogs DoseLog[]
auditLogs AuditLog[]
syncCursors SyncCursor[]
symptoms Symptom[]
appointmentChecklists AppointmentChecklist[]
pushSubscriptions PushSubscription[]
@@index([name])
}
@@ -152,9 +166,10 @@ model Appointment {
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("AppointmentCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("AppointmentUpdatedBy", fields: [updatedById], references: [id])
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("AppointmentCreatedBy", fields: [createdById], references: [id])
updatedBy User @relation("AppointmentUpdatedBy", fields: [updatedById], references: [id])
checklists AppointmentChecklist[]
@@index([workspaceId, datetime])
@@index([workspaceId, deletedAt])
@@ -188,6 +203,12 @@ model Medication {
createdById String
updatedById String
// Refill tracking fields
pillCount Int?
pillsPerDose Int? @default(1)
refillThreshold Int? @default(7)
lastRefillDate DateTime?
// Sync tracking
version Int @default(1)
syncedAt DateTime @default(now())
@@ -290,6 +311,88 @@ model AuditLog {
@@index([entityType, entityId])
}
// ============================================
// SYMPTOMS
// ============================================
enum SymptomType {
FATIGUE
NAUSEA
PAIN
APPETITE
SLEEP
MOOD
CUSTOM
}
model Symptom {
id String @id @default(cuid())
workspaceId String
type SymptomType
customName String? // Only used when type is CUSTOM
severity Int // 1-5 scale
notes String?
recordedAt DateTime @default(now())
deletedAt DateTime?
createdById String
// Sync tracking
version Int @default(1)
syncedAt DateTime @default(now())
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation(fields: [createdById], references: [id])
@@index([workspaceId, recordedAt])
@@index([workspaceId, type])
@@index([workspaceId, deletedAt])
@@index([syncedAt])
}
// ============================================
// APPOINTMENT CHECKLIST
// ============================================
model AppointmentChecklist {
id String @id @default(cuid())
workspaceId String
appointmentId String
item String
isReady Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
@@unique([workspaceId, appointmentId, item])
@@index([appointmentId])
@@index([workspaceId])
}
// ============================================
// PUSH NOTIFICATIONS
// ============================================
model PushSubscription {
id String @id @default(cuid())
userId String
workspaceId String
endpoint String
p256dh String
auth String
createdAt DateTime @default(now())
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@unique([userId, endpoint])
@@index([workspaceId])
}
// ============================================
// SYNC
// ============================================