Files
cyclone/src/lib/event-routing.ts
T
Tyler 65d98cf674 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.
2026-06-21 16:41:15 -06:00

56 lines
2.0 KiB
TypeScript

import type { Activity } from "@/types";
/**
* The minimum an activity event must carry to be routable. Centralizing
* this here means callers can pass an `Activity` (full feed row) or a
* narrower shape (e.g. a future "activity event" peek payload) — both
* satisfy the kind + entity-id fields the helper inspects.
*
* - `claimId` / `remittanceId` come from the backend `ActivityEvent`
* ORM columns (SP21 Task 2.5). `claimId` is set on `claim_*` events;
* `remittanceId` is set on `remit_received`.
* - `npi` is the existing `Activity.npi` field. For `provider_added`
* events the provider NPI IS the entity id (the URL target).
*/
export type RoutableEvent = Pick<
Activity,
"kind" | "claimId" | "remittanceId" | "npi"
>;
/**
* Maps an activity event to the URL the operator should land on when
* clicking the event. Used by:
*
* - Dashboard "Recent activity" card (this PR, Task 2.5).
* - `/activity` log page (Task 2.5 wired here; full integration is
* a later task).
*
* Returns `null` for kinds that don't have a drill target yet, or
* when the entity id field is missing on the event. Callers are
* expected to surface a "coming soon" toast in that case so the click
* still gives feedback.
*/
export function eventKindToUrl(event: RoutableEvent): string | null {
switch (event.kind) {
case "claim_submitted":
case "claim_paid":
case "claim_denied":
case "claim_accepted": {
if (!event.claimId) return null;
return `/claims?claim=${encodeURIComponent(event.claimId)}`;
}
case "remit_received":
// Phase 4 — the `RemitDrawer` isn't built yet; the helper stays
// honest and returns null so the caller's "coming soon" toast
// fires. When the drawer lands, the route becomes
// `/remittances?remit=${encodeURIComponent(event.remittanceId ?? "")}`.
return null;
case "provider_added": {
if (!event.npi) return null;
return `/providers?provider=${encodeURIComponent(event.npi)}`;
}
default:
return null;
}
}