64 lines
3.3 KiB
Markdown
64 lines
3.3 KiB
Markdown
---
|
||
name: dyolink-lab-notifications
|
||
description: Lab case activity feed + sidebar tab badge counts. Use when changing notifications API, LabCaseActivity, read state, or Sidebar badges for Cases/Tasks/Treatment.
|
||
---
|
||
|
||
# Lab notifications (tab badges)
|
||
|
||
Backend: [`backend/src/modules/notifications/`](backend/src/modules/notifications/)
|
||
Activity types: [`backend/src/common/lab-case-activity.ts`](backend/src/common/lab-case-activity.ts)
|
||
Frontend hook: [`frontend/src/lib/hooks/useTabBadgeCounts.ts`](frontend/src/lib/hooks/useTabBadgeCounts.ts)
|
||
|
||
## Models
|
||
|
||
- **`LabCaseActivity`** — append-only events: `CASE_SENT`, `CLINIC_COMMENT`, `LAB_COMMENT`, `CASE_IMPORTANT`, `CASE_AMENDED` (stub for Step 7), `TASK_COMPLETED`
|
||
- **`LabCaseUserTabReadState`** — per user/org/tab cursor (`TASKS`) for sidebar badge clearing on tab visit.
|
||
- **`LabCaseUserReadState`** — per user/org/labCase cursor; drives Cases tab count and `hasUnread` on case list cards
|
||
|
||
## Tab badge buckets (Option B — split lab counts)
|
||
|
||
| Org | Tab | Activity types |
|
||
|-----|-----|----------------|
|
||
| LAB | Cases | `CASE_SENT`, `CLINIC_COMMENT`, `CASE_IMPORTANT` |
|
||
| LAB | Tasks | `TASK_COMPLETED`, `LAB_COMMENT` |
|
||
| CLINIC | Treatment | `LAB_COMMENT` (only `visibleToClinic`), `TASK_COMPLETED` — **only lab cases for treatments the user provided** |
|
||
|
||
Counts exclude events where `actorUserId === current user`. Clinic `LAB_COMMENT` counts only when `payload.visibleToClinic === true`.
|
||
|
||
## APIs
|
||
|
||
- `GET /notifications/tab-counts` → `{ cases?, tasks?, treatment? }` — **Cases** count = number of cases with unread Cases-bucket activity (per-case read cursor)
|
||
- `GET /notifications/lab-cases/:labCaseId/activities` — activity feed for a case (clinic-safe lab comments)
|
||
- `POST /notifications/mark-tab-read` `{ tab }` — Tasks only (Cases/Treatment skip tab-level clear)
|
||
- `POST /notifications/mark-case-read` `{ labCaseId }` — opening a case clears that case’s unread dot and updates Cases tab count
|
||
- `GET /treatments/patients/:patientId/lab-cases` — patient shipment summaries for Treatment rail + tracker cards
|
||
- `GET /treatments/lab-cases/unread` — org-wide unread shipment summaries for Treatment “All updates” scope
|
||
|
||
## Emit activity from
|
||
|
||
| Event | Service |
|
||
|-------|---------|
|
||
| First send | `treatments.service` `sendLabCase` → `CASE_SENT` |
|
||
| Comment | `lab-case-comments.service` → `CLINIC_COMMENT` / `LAB_COMMENT` |
|
||
| Mark important | `cases.service` `updateImportant` (only when set true) → `CASE_IMPORTANT` |
|
||
| Task completed | `tasks.service` `updateStatus` → `TASK_COMPLETED` |
|
||
|
||
After mutations, frontend calls `notifyTabBadgesChanged()` (window event).
|
||
|
||
## Frontend pattern (same as org connections)
|
||
|
||
- `useTabBadgeCounts()` — fetch on pathname change + `tab-badges-changed` event
|
||
- `useMarkTabReadOnVisit()` — Tasks page only (Cases/Treatment badges clear when opening unread cases)
|
||
- `NavBadgePill` in [`Sidebar.tsx`](frontend/src/components/ui/shared/Sidebar.tsx)
|
||
- **Organizations** pending connections still use `usePendingConnectionsCount` (separate pending-state API)
|
||
|
||
## Out of scope (later steps)
|
||
|
||
- Push / email
|
||
- Making Cases/Tasks/Treatment lists live via websockets
|
||
- `CASE_AMENDED` emit (Step 7)
|
||
|
||
## Related: header inbox
|
||
|
||
Permission-free bell + `UserNotification` fan-out + Socket.IO — see `.cursor/skills/notifications-inbox/SKILL.md`. Independent of tab badge cursors.
|