feat: add per-patient Local folder in Documents page backed by Cloud Storage

- Documents page shows a "Local Folder" card for each selected patient
  with an "Open in Cloud Storage" button that deep-links to their folder
- Cloud Storage page reads ?folderId URL param on mount and auto-opens
  the folder panel for seamless navigation from Documents
- Backend: GET /api/cloud-storage/patient-folder/:patientId endpoint
  that idempotently gets or creates a top-level CloudFolder per patient
- CloudFolder schema gains optional patientId field linked to Patient
- Disk directories for cloud storage folders now use the folder's name
  (e.g. "Xiaohui Wang/") instead of the opaque "folder-{id}/" path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-05 23:01:56 -04:00
parent 2457e12b5c
commit d5bc96ff39
158 changed files with 1539 additions and 130 deletions

View File

@@ -78,6 +78,7 @@ model Patient {
communications Communication[]
documents PatientDocument[]
conversation PatientConversation?
cloudFolders CloudFolder[] @relation("PatientCloudFolder")
@@index([insuranceId])
@@index([createdAt])
@@ -479,15 +480,18 @@ model CloudFolder {
userId Int
name String
parentId Int?
patientId Int?
parent CloudFolder? @relation("FolderChildren", fields: [parentId], references: [id], onDelete: Cascade)
children CloudFolder[] @relation("FolderChildren")
user User @relation(fields: [userId], references: [id])
patient Patient? @relation("PatientCloudFolder", fields: [patientId], references: [id], onDelete: SetNull)
files CloudFile[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, parentId, name]) // prevents sibling folder name duplicates
@@index([parentId])
@@index([patientId])
}
model CloudFile {
@@ -602,11 +606,11 @@ model AiSettings {
apiKey String
aiEnabled Boolean @default(true)
openAiKey String @default("")
openAiEnabled Boolean @default(true)
openAiEnabled Boolean @default(false)
claudeAiKey String @default("")
claudeAiEnabled Boolean @default(true)
claudeAiEnabled Boolean @default(false)
dentalMgmtKey String @default("")
dentalMgmtEnabled Boolean @default(true)
dentalMgmtEnabled Boolean @default(false)
afterHoursEnabled Boolean @default(true)
openPhoneReply Boolean @default(false)