4.3 KiB
name, description
| name | description |
|---|---|
| dyolink-tab-badges | Sidebar tab badge counts (Cases/Tasks/Treatment) from LabCaseActivity + read cursors. Use when changing tab-counts API, activity emit, read state, or Sidebar badges — distinct from the header inbox bell. |
Tab badges (Cases / Tasks / Treatment)
Sidebar unread pills for lab Cases, lab Tasks, and clinic Treatment. Activity is stored as LabCaseActivity (shared clinic↔lab case events); badges are org-type-specific.
Backend: backend/src/modules/notifications/
Activity types: backend/src/common/lab-case-activity.ts
Frontend hook: 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_COMPLETEDLabCaseUserTabReadState— per user/org/tab cursor (TASKS) for sidebar badge clearing on tab visit.LabCaseUserReadState— per user/org/labCase cursor; drives Cases tab count andhasUnreadon case list cards
Tab badge buckets (Option B)
| 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 countGET /treatments/patients/:patientId/lab-cases— patient shipment summaries for Treatment rail + tracker cardsGET /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).
Live path: inbox Socket.IO notification.created also dispatches that event so sidebar counts refresh without navigation. Only currently mounted feature pages soft-reload list data on the same event (silent; Treatment form/draft untouched). Unmounted tabs do not fetch list data until the user opens them. See .cursor/skills/notifications-inbox/SKILL.md.
Frontend pattern (same as org connections)
useTabBadgeCounts()— always mounted in dashboard Sidebar; fetch on pathname change +tab-badges-changeduseMarkTabReadOnVisit()— Tasks page only (Cases/Treatment badges clear when opening unread cases)NavBadgePillinSidebar.tsx- Organizations pending connections still use
usePendingConnectionsCount(separate pending-state API) - Live soft refresh (mounted page only):
- Cases → silent list + selected detail
- Tasks → silent task list
- Treatment (
TreatmentWorkspace) → silent patient lab cases + unread rail - Orgs → silent list on
pending-connections-changedonly
Out of scope (later steps)
- Push / email
- Prefetching unmounted tab list data on every socket event
- Pushing full page remounts / wiping Treatment draft state on live events
CASE_AMENDEDemit (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.