feat(dashboard): Recent activity events route to entity by kind

SP21 Task 2.5 — the Dashboard's 'Recent activity' card now routes clicks
to the matching entity drawer / page by event kind:

  - claim_*       → /claims?claim=<id>  (drawer in Phase 5)
  - provider_added → /providers?provider=<npi>  (ProviderDrawer)
  - remit_received → toast 'coming in a later phase' (RemitDrawer in Phase 4)
  - anything else  → toast (manual_match, unknown kinds)

Implementation:
  - New src/lib/event-routing.ts with the eventKindToUrl() helper,
    plus a unit test covering all 6 + default branches.
  - src/components/ActivityFeed.tsx gains an optional onItemClick
    prop; when set, each row gets role='button', tabIndex=0, the
    drillable hover affordance (chevron + tint), and an Enter/Space
    keybinding. e.stopPropagation() is called before the handler so a
    parent row click can't double-fire (same fix as Task 2.4).
  - src/pages/Dashboard.tsx wires the handler on the 'Recent activity'
    card via eventKindToUrl + sonner toast for unhandled kinds.
  - Backend: CycloneStore.recent_activity() now exposes claimId and
    remittanceId on each row (read from ActivityEvent.claim_id /
    remittance_id) so the routing helper has the entity ids it needs.
  - The frontend Activity interface gains optional claimId /
    remittanceId fields; the in-memory sample data and the
    addClaim store action populate them so the dashboard works in
    both API-configured and sample-data modes.
This commit is contained in:
Tyler
2026-06-21 16:27:56 -06:00
parent d15c04d983
commit 65d98cf674
10 changed files with 495 additions and 8 deletions
+6
View File
@@ -164,6 +164,12 @@ function buildActivity(claims: Claim[]): Activity[] {
timestamp: c.submissionDate,
npi: c.providerNpi,
amount: c.billedAmount,
// SP21 Task 2.5: mirror the backend `recent_activity()` wire
// shape so the Dashboard routing helper can find the claim id.
// Sample-data remits are never a 1:1 Activity row, so the
// remittance id is always null here.
claimId: c.id,
remittanceId: null,
};
});
return events.sort(