improvement: notification feature polished. feature tabs following the same notification socket emmited data to be updated.
This commit is contained in:
@@ -37,3 +37,7 @@ CLINIC + LAB KPIs/charts in `modules/today/today.service.ts`. Deep links: `compo
|
||||
## Lab case share link
|
||||
|
||||
QR + URL for **sent** cases; focus page `/lab-case/[token]`. Auth redirect via `postAuthRedirect.ts`. Skill: `.cursor/skills/lab-case-share-link/SKILL.md`.
|
||||
|
||||
## Notifications (inbox + live tabs)
|
||||
|
||||
Header bell: `UserNotification` + Socket.IO. Same `notification.created` also drives sidebar tab badges and soft list refresh on **currently open** Cases/Tasks/Treatment/Orgs pages. Skills: `.cursor/skills/notifications-inbox/SKILL.md`, `.cursor/skills/tab-badges/SKILL.md`.
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
---
|
||||
description: Lab tab badges — activity model, tab-counts API, read cursors
|
||||
description: Sidebar tab badges (Cases/Tasks/Treatment) — activity model, tab-counts API, read cursors
|
||||
globs: backend/src/modules/notifications/**,backend/src/common/lab-case-activity.ts,frontend/src/lib/hooks/useTabBadgeCounts.ts,frontend/src/lib/tabBadgeUtils.ts,frontend/src/lib/api/notifications.ts,frontend/src/components/ui/shared/NavBadgePill.tsx,frontend/src/components/ui/shared/Sidebar.tsx
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Lab tab badges
|
||||
# Tab badges (Cases / Tasks / Treatment)
|
||||
|
||||
- **Split counts (Option B):** Lab Cases = sent + clinic comments + important; Lab Tasks = completions + lab comments; Clinic Treatment = visible lab comments + completions.
|
||||
- **API:** `GET /notifications/tab-counts`; **Cases + Treatment** use per-case read + `hasUnread` on list cards; Tasks marks read on tab visit. Treatment rail uses `TreatmentLabCasesPanel` + `LabCaseTrackerCard` + activity feed.
|
||||
- **API:** `GET /notifications/tab-counts`; **Cases + Treatment** use per-case read + `hasUnread` on list cards; Tasks marks read on tab visit. Treatment rail uses `TreatmentLabCasesPanel` + `LabCaseTrackerCard` + activity feed.
|
||||
- **Pattern:** `useTabBadgeCounts` + `notifyTabBadgesChanged()` — same shape as `usePendingConnectionsCount`.
|
||||
- **Live:** inbox Socket.IO `notification.created` → `notifyTabBadgesChanged()` (and org pending event when relevant). **Mounted** Cases/Tasks/Treatment/Orgs pages soft-refetch lists; unmounted tabs do not. Sidebar badge counts always refetch (hook is always mounted).
|
||||
- **Orgs connections badge** stays on separate `pending-count` endpoint.
|
||||
- **Header inbox** (bell) is separate — `.cursor/skills/notifications-inbox/SKILL.md`.
|
||||
|
||||
Full map: `.cursor/skills/lab-notifications/SKILL.md`
|
||||
Full map: `.cursor/skills/tab-badges/SKILL.md`
|
||||
@@ -40,6 +40,10 @@ List item shape: `prosthesisGroups: { prosthesisTypeCode, teeth[] }[]` from task
|
||||
- Mark read: `POST /notifications/mark-case-read` on select (Cases tab badge)
|
||||
- **Share link (sent cases):** `shareUrl` on detail; attachment preview left + QR thumb right; `LabCaseShareQrDialog` (`react-qr-code`). Full flow: `.cursor/skills/lab-case-share-link/SKILL.md`.
|
||||
|
||||
## Live soft refresh
|
||||
|
||||
Inbox Socket.IO `notification.created` → `notifyTabBadgesChanged()` → silent `loadCases` + selected `loadDetail` (keeps filters/selection; no full remount). Same event refreshes sidebar Cases badge via `useTabBadgeCounts`. See `.cursor/skills/notifications-inbox/SKILL.md`.
|
||||
|
||||
## Permissions
|
||||
|
||||
`TAB_CASES_READ` / `TAB_CASES_EDIT`; owner always has Cases access. `LabOrgGuard` on routes.
|
||||
|
||||
@@ -91,9 +91,11 @@ Keep changes minimal — match existing `sm:` breakpoint patterns elsewhere in t
|
||||
- **Tasks filters:** filter `<select>`s use the same 44px mobile height as other form controls.
|
||||
- **Treatment lab dispatch:** shipment card `p-3 sm:p-4`; **Send to lab** is `w-full sm:w-auto`.
|
||||
|
||||
## Tab badges
|
||||
## Tab badges + live soft refresh
|
||||
|
||||
See `.cursor/skills/lab-notifications/SKILL.md` — split lab Cases/Tasks counts, clinic Treatment; `useTabBadgeCounts` + `notifyTabBadgesChanged`.
|
||||
See `.cursor/skills/tab-badges/SKILL.md` — split lab Cases/Tasks counts, clinic Treatment; `useTabBadgeCounts` + `notifyTabBadgesChanged`.
|
||||
|
||||
Inbox Socket.IO `notification.created` → `notifyTabBadgesChanged()` → silent `loadTasks({ silent: true })` on an open Tasks page (filters preserved; no remount). Details: `.cursor/skills/notifications-inbox/SKILL.md`.
|
||||
|
||||
## Permissions
|
||||
|
||||
|
||||
@@ -12,11 +12,28 @@ Permission-free **feature** (every dashboard user sees the bell). **Cards** are
|
||||
| | Inbox (`UserNotification`) | Sidebar badges (`LabCaseActivity`) |
|
||||
|--|--|--|
|
||||
| Entry | Header bell → dropdown + `/notifications` | Sidebar Cases/Tasks/Treatment/Orgs |
|
||||
| Live | Socket.IO (`/realtime`) | REST + `tab-badges-changed` |
|
||||
| Live | Socket.IO (`/realtime`) | Same socket → `notifyTabBadgesChanged()` → REST tab-counts |
|
||||
| Read | Per-card `readAt` only | Per-case / tab cursors |
|
||||
|
||||
Do **not** clear tab badges when marking an inbox card read.
|
||||
|
||||
## Live cascade (one socket)
|
||||
|
||||
On `notification.created`, [`RealtimeProvider`](frontend/src/lib/realtime/RealtimeProvider.tsx):
|
||||
|
||||
1. Updates inbox state (bell list + unread).
|
||||
2. Calls `notifyTabBadgesChanged()` → Sidebar refetches `GET /notifications/tab-counts`.
|
||||
3. For connection-request types, also `notifyPendingConnectionsChanged()`.
|
||||
4. **Currently mounted** feature pages soft-refresh (no remount, no form wipe). Unmounted tabs do **not** fetch list data until opened:
|
||||
- **Cases** — silent list + selected detail reload
|
||||
- **Tasks** — silent task list reload
|
||||
- **Treatment** (`TreatmentWorkspace`, not thin `page.tsx`) — silent patient lab-cases + unread rail
|
||||
- **Organizations** — silent connections list on `pending-connections-changed` only
|
||||
|
||||
Sidebar **badge counts** always refetch (hook lives in the always-mounted Sidebar). Treatment **draft/form state** is not cleared by this cascade.
|
||||
|
||||
Full tab-badge map: `.cursor/skills/tab-badges/SKILL.md`.
|
||||
|
||||
## Backend
|
||||
|
||||
- Model: `UserNotification` + `UserNotificationType` in Prisma
|
||||
@@ -26,10 +43,13 @@ Do **not** clear tab badges when marking an inbox card read.
|
||||
|
||||
## Emit sites (parallel to LabCaseActivity)
|
||||
|
||||
CASE_SENT, CLINIC_COMMENT, LAB_COMMENT (+ LAB_COMMENT_CLINIC), CASE_IMPORTANT, TASK_COMPLETED, TASK_ASSIGNED, CONNECTION_REQUEST, STAFF_INVITE — see plan / service call sites.
|
||||
CASE_SENT, CLINIC_COMMENT, LAB_COMMENT (+ LAB_COMMENT_CLINIC), CASE_IMPORTANT, TASK_COMPLETED, TASK_ASSIGNED, CONNECTION_REQUEST, STAFF_INVITE — see service call sites.
|
||||
|
||||
## Frontend
|
||||
**Inbox card context** is denormalized inside `UserNotificationService.notify()` (`enrichInboxPayload`) from ids already on the payload (`labCaseId`, `taskId`, `fromOrganizationId`). Emit sites stay thin (`{ labCaseId }`, etc.). Inbox list/read does **not** join related tables. Older rows may lack these fields until new events are emitted.
|
||||
|
||||
- `RealtimeProvider` in dashboard layout
|
||||
- `NotificationBell` + `NotificationsPage` + `NotificationCard`
|
||||
**Realtime auth:** after access-token refresh (proactive, axios 401 retry, or `checkAuth`), frontend dispatches `dyolink:access-token-refreshed` so `RealtimeProvider` reconnects with the new cookie.
|
||||
|
||||
## Frontend UI
|
||||
|
||||
- `NotificationBell` + `NotificationsPage` + `NotificationCard` (full-width page list; same card height; extra context truncated on one line)
|
||||
- Deep links: `/cases?caseId=`, `/tasks?taskId=`, `/treatment?labCaseId=`, `/organizations`, `/staff`
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
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.
|
||||
name: dyolink-tab-badges
|
||||
description: 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.
|
||||
---
|
||||
|
||||
# Lab notifications (tab badges)
|
||||
# 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/`](backend/src/modules/notifications/)
|
||||
Activity types: [`backend/src/common/lab-case-activity.ts`](backend/src/common/lab-case-activity.ts)
|
||||
@@ -15,7 +17,7 @@ Frontend hook: [`frontend/src/lib/hooks/useTabBadgeCounts.ts`](frontend/src/lib/
|
||||
- **`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)
|
||||
## Tab badge buckets (Option B)
|
||||
|
||||
| Org | Tab | Activity types |
|
||||
|-----|-----|----------------|
|
||||
@@ -45,17 +47,25 @@ Counts exclude events where `actorUserId === current user`. Clinic `LAB_COMMENT`
|
||||
|
||||
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()` — fetch on pathname change + `tab-badges-changed` event
|
||||
- `useTabBadgeCounts()` — always mounted in dashboard Sidebar; fetch on pathname change + `tab-badges-changed`
|
||||
- `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)
|
||||
- **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-changed` only
|
||||
|
||||
## Out of scope (later steps)
|
||||
|
||||
- Push / email
|
||||
- Making Cases/Tasks/Treatment lists live via websockets
|
||||
- Prefetching unmounted tab list data on every socket event
|
||||
- Pushing full page remounts / wiping Treatment draft state on live events
|
||||
- `CASE_AMENDED` emit (Step 7)
|
||||
|
||||
## Related: header inbox
|
||||
@@ -131,7 +131,9 @@ Comments for a shipment live in the Lab dispatch panel (and Cases/Tasks/share),
|
||||
- UI: `DetailLabCaseCommentsSection` → `LabCaseCommentsPanel` + `treatmentsApi` comment endpoints (`viewerSide="CLINIC"`).
|
||||
- Backend includes `tasks: { select: { id, status } }` on lab cases; `mapDetail` exposes `taskProgress: { completed, total }`.
|
||||
|
||||
## Live lab rail refresh
|
||||
|
||||
Inbox Socket.IO `notification.created` → `notifyTabBadgesChanged()` → silent refresh of patient lab cases + unread rail. Does **not** remount the workspace or clear draft/form state. See `.cursor/skills/notifications-inbox/SKILL.md`.
|
||||
|
||||
## Appointments default selection
|
||||
|
||||
|
||||
Reference in New Issue
Block a user