Commit Graph

155 Commits

Author SHA1 Message Date
Nora 7e4bb4d2c8 Wire Dashboard to live API hooks
Dashboard.tsx was reading claims/providers/activity directly from the
zustand `useAppStore`, which returns the hardcoded sample fixtures
(sampleClaims / sampleProviders / sampleActivity) regardless of whether
a backend session is active. The hooks useClaims / useProviders /
useActivity are wired correctly to /api/* when api.isConfigured, so
the fix is to swap the three direct store reads for the hooks.

buildMonthly's parameter is now typed as Claim[] (the live Claim
shape) instead of the store's heterogeneous slice, and the
`useAppStore` import is gone.

Verified end-to-end: after logging in as admin via the live server,
the Dashboard now shows $0 KPIs, "0 events / No activity yet.",
and the live operator name in the greeting — proving the hooks are
hitting /api/* and not falling back to the sample store.
2026-06-22 17:20:48 -06:00
Nora fb913f0617 fix(ui): Dashboard greeting uses live operator; api.isConfigured proxies through
Two issues surfaced by going live against the dev backend:

1. Dashboard greeting was hardcoded 'Good morning, Jordan.' in
   src/pages/Dashboard.tsx. The sidebar already pulls from useAuth()
   but the dashboard had been missed. Replace with a time-of-day
   greeting ('morning' / 'afternoon' / 'evening') plus the live
   operator's username from useAuth(). Falls back to 'there' while
   /api/auth/me is in flight so we never flash 'undefined' at the
   operator.

2. api.isConfigured was returning false whenever VITE_API_BASE_URL
   was empty, which is the default in .env.example and the
   recommended setting for the Vite dev proxy / nginx. With the flag
   false, every hook (useClaims, useBatches, useActivity, ...)
   threw notConfiguredError() and silently fell back to the
   in-memory zustand store's hardcoded sample data — so 'files not
   serving' was actually the Dashboard rendering fixtures, not the
   real DB. With the flag now true, joinUrl('') produces relative
   URLs that the proxy intercepts and forwards to FastAPI on :8000.
   Set VITE_API_BASE_URL=disabled to opt out (useful for Storybook
   / offline UI work).

Also installs @radix-ui/react-tabs which was listed in package.json
but missing from node_modules — Vite was failing pre-transform on
tabs.tsx and refusing to compile downstream pages until deps were
re-installed via 'npm install'.
2026-06-22 17:05:27 -06:00
Nora 35e0f5a422 feat(auth): disable write affordances for viewer role
Re-apply f91d7b3 manually against current Upload.tsx / Inbox.tsx /
Reconciliation.tsx since main's UI text diverged from the original
cherry-pick (Release to 'parse' span, mono uppercase tracking, etc.).
The write affordances are now wrapped in <RoleGate allow={admin,user}>:

  - Upload.tsx: dropzone + Parse/Clear buttons
  - Inbox.tsx: rejected / payer_rejected / candidates BulkBars
  - Reconciliation.tsx: 'Match selected' button

Test fixtures stub useAuth to return an admin user so RoleGate lets
the BulkBars / Match button render synchronously on first paint —
the tests don't need a full AuthProvider + /api/auth/me probe.
2026-06-22 15:54:38 -06:00
Nora 4402a993d1 feat(auth): sidebar shows current user + logout button 2026-06-22 15:37:02 -06:00
Nora cb87456575 feat(auth): wire AuthProvider + RequireAuth into app shell 2026-06-22 15:37:02 -06:00
Nora 3d0c7766f0 feat(auth): RequireAuth route guard 2026-06-22 15:34:19 -06:00
Nora e76b514872 feat(auth): /login page 2026-06-22 15:34:19 -06:00
Nora 5bb9588f09 feat(auth): RoleGate component 2026-06-22 15:34:19 -06:00
Nora d895854dcc feat(auth): AuthProvider context + useAuth hook 2026-06-22 15:34:18 -06:00
Nora 55a298f05f feat(auth): fetch wrapper with 401 redirect + authApi 2026-06-22 15:34:18 -06:00
Nora 0aea0f64ac feat(types): add User type 2026-06-22 15:34:18 -06:00
Tyler 9bca4b608a feat(release): v0.2.0 — batch 837 export, ClaimCard, theme tokens
Backend:

- New POST /api/batches/{id}/export-837: regenerate X12 837 files
  for a list of claim_ids into a ZIP using HCPF file naming standards,
  with a unique interchange/group control number per export. Wire
  the clearhouse Loop 1000A (NM1*41 + PER) and per-payer receiver
  (NM1*40) blocks so the serializer no longer falls back to
  CYCLONE / RECEIVER placeholders.
- /api/parse-837 and /api/parse-835 now surface the server-side
  batch_id in both JSON and NDJSON response shapes so the frontend
  can hit batch-scoped endpoints without an extra listBatches
  round-trip.
- Filename helpers and the 837 serializer updated to match the new
  HCPF envelope; tests cover batch export, parse batch_id, and the
  serializer's control-number uniqueness guarantee.

Frontend:
- New shared components: ClaimCard, ClaimCard837, DominantKpiCard,
  EditorialNote, ExportBar, TickerTape, and a charts/ set
  (BarChart, HBarChart, SegmentedBar, AgingBars).
- New useBatchExport hook driving ExportBar's download flow against
  the new endpoint.
- ClaimDrawer, Lane, and Layout migrated from raw CSS-variable
  colors to Tailwind theme tokens (bg-card, text-foreground,
  border/60, etc.) for consistency with the rest of the instrument
  chrome; the active tab indicator gains a subtle accent glow.
- Upload, Inbox, Batches, BatchDiff, Reconciliation, and Acks pages
  reworked to compose the new shared components and consume the new
  batch-scoped API surface (notably ExportBar wired into Batches).

Tooling / Docs:
- Add audit-uiux.mjs and a docs/goodclaim.x12 sample fixture.
- Update ClaimDrawer testids and add coverage for the new
  components and the useBatchExport hook.

Rolls up into the v0.2.0 release tag.
2026-06-22 11:01:58 -06:00
Tyler 22720168c4 test(claims): wrap page in DrillStackProvider, switch to DrillDrawerHeader testids
Phase 5 Task 5.8 (PartiesGrid) and 5.9 (ValidationPanel) added useDrillStack()
calls, so Claims.test.tsx needs a DrillStackProvider wrapper around the
page render to keep the hook happy. Task 5.10 refactored the ClaimDrawer
header onto the shared DrillDrawerHeader, so the deep-link and close-button
tests need to find the title via <h2> and the close button via
aria-label='Close drawer' instead of the now-removed header-id /
header-close testids.

Without this fix the page-level Claims tests report 3 failures
(deep link, URL clear after close, ?-mark with drawer closed) that
were not failing on the Phase 4 base (9a313d2).
2026-06-21 18:10:43 -06:00
Tyler 1c0d855b8e refactor(claim-drawer): mount on shared DrillDrawerHeader shell 2026-06-21 18:10:43 -06:00
Tyler 8db5db7610 feat(claim-drawer): validation rule opens peek with rule catalog 2026-06-21 18:10:43 -06:00
Tyler 786ead8c94 feat(claim-drawer): payer name opens PeekModal on top of drawer 2026-06-21 18:10:43 -06:00
Tyler 6c773c1159 feat(upload): streamed claim cards offer drill to persisted entity 2026-06-21 18:10:43 -06:00
Tyler 5c7e9b6168 feat(batch-diff): claim ids drillable to /claims?claim=ID 2026-06-21 18:10:43 -06:00
Tyler ac87ed4908 feat(reconciliation): card body drillable, select button split 2026-06-21 18:10:43 -06:00
Tyler 4a8ce1a524 feat(inbox): rejected + payer_rejected + done_today rows drillable 2026-06-21 18:10:43 -06:00
Tyler 5053a1ea8e feat(acks): row click opens AckDrawer 2026-06-21 18:10:43 -06:00
Tyler 33fa899217 feat(drill): AckDrawer with header + segment status list 2026-06-21 18:10:43 -06:00
Tyler 6fdbceefc2 feat(drill): useAckDrawerUrlState — ?ack= URL sync 2026-06-21 18:10:43 -06:00
Tyler 9a313d2c1b feat(activity): remit_received events drill to RemitDrawer 2026-06-21 17:19:31 -06:00
Tyler 2eb61f16ff feat(reconciliation): candidate rows drill to RemitDrawer or ClaimDrawer 2026-06-21 17:19:31 -06:00
Tyler 12c2913ba1 feat(batchdiff): remit rows drill to RemitDrawer 2026-06-21 17:19:31 -06:00
Tyler 54440da2cd feat(inbox): candidates + unmatched rows drill to RemitDrawer or ClaimDrawer 2026-06-21 17:19:31 -06:00
Tyler 7427838292 feat(remits): row click opens RemitDrawer (replaces inline CAS expand) 2026-06-21 17:19:31 -06:00
Tyler 08e83da91d feat(drill): ProviderDrawer — Claims + Activity tabs from extended /providers/{npi} 2026-06-21 16:56:36 -06:00
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
Tyler d15c04d983 feat(claims): provider cell drillable to /providers?provider=NPI 2026-06-21 16:41:15 -06:00
Tyler 980627b675 feat(providers): directory cards drillable — opens ProviderDrawer 2026-06-21 16:41:15 -06:00
Tyler 1db6b8841c fix(drill): ProviderDrawer quality pass — error branching, demo fallback, flex layout
Quality-fix iteration on 078c9ad. Resolves the 4 Important issues
flagged in the code review while preserving everything that's working.

Issue 1 — ProviderDrawer silently swallowed error states (infinite
skeleton on any failure). Now mirrors ClaimDrawer/RemitDrawer:
  - Destructure { data, isLoading, isError, error, refetch }
  - Compute errorKind = ApiError(404) → not_found, else → network
  - New ProviderDrawerError.tsx renders the right copy + retry/close
  - Body branches on errorKind → loading → data

Issue 2 — useProviderDetail did not match the useClaimDetail contract:
  - 404 retry guard (no retries on 404, <=3 on everything else)
  - Explicit typed return shape { data, isLoading, isError, error,
    refetch }
  - npi === null short-circuit after the useQuery call

Issue 3 — Provider drilldown was non-functional in demo mode. Added
the useSyncExternalStore fallback to useAppStore.providers (same
pattern as useProviders), gated by api.isConfigured. Unknown NPI in
demo mode surfaces via the error branch instead of an infinite
skeleton.

Issue 4 — Test was shape-only (1 case). Rewrote with vi.hoisted +
vi.fn() hook mock and 7 cases covering null/loading/404/network/
close/success/hook-call-shape.

Issue 5 — Replaced h-[calc(100%-64px)] with a flex layout
(flex h-full flex-col on DialogContent, flex h-full flex-col
overflow-y-auto on inner wrappers). Mirrors RemitDrawer's pattern;
no more coupling to DrillDrawerHeader's padding/font sizes.

Issue 7 — Removed the dead npi === null ? null : (...) wrapper
inside DialogContent; Dialog open={npi !== null} already gates it.

Issue 8 — Trailing newlines added across all touched + created files.

Self-review: tsc clean on changed files; drawer test suite 211/211
passing; full test suite 369/372 (3 pre-existing Inbox/Reconciliation
failures unchanged). ProviderDrawer now 7/7.
2026-06-21 16:41:15 -06:00
Tyler 0678e25de7 feat(drill): ProviderDrawer with Overview tab + DrillDrawerHeader shell 2026-06-21 16:41:15 -06:00
Tyler 5b3b8619e6 feat(drill): useProviderDrawerUrlState — provider drawer URL state (mirrors remit hook) 2026-06-21 16:41:15 -06:00
Cyclone UI 388ea6e49b Refine Batch Diff page with hybrid dark/paper treatment
- Replace small header with editorial hero: massive 'Compare *batches.*'
  (clamp 48-80px serif, italic for 'batches.') + ghost 'DIFF' watermark
  + GitCompareArrows pill (837P/835/first vs second) + pick-a-batch hint
- Add torn-page fold with '↘ A → B ↙' label and 48-dot perforation
- Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and
  double-bordered title block: 'SHEET 01 · COMPARE TWO BATCHES' over
  'Compare them.' (clamp 48-96px) plus A→B wire indicator (blue A,
  amber B, paper-ink-3 placeholder)
- New Folio helper renders the § N + vertical-rl italic label; reused
  on the picks and diff sections
- § 01 The picks: BatchPicker gets a paper-toned card with side badge
  (A blue / B amber tinted square) and claim count, preserving the
  data-testid='diff-picker-{a,b}' and 'diff-picker-kind-{kind}' and
  'diff-picker-option-{id}' contracts
- § 02 The diff: conditional rendering (awaiting-picks dashed card,
  loading skeleton, not-found, error, empty diff, full diff view)
  with paper-toned wrappers
- BatchDiffView refactored to paper-toned chrome: SideMeta cards
  (warm surface, surface-ink text), SummaryCards → new DiffKpiTile
  helper (success/destructive/amber/ink accent rails with lucide
  icons), SectionTables → tone='paper' tables with cream header band
  and surface-ink text. All section/row/indicator testids preserved.
- Action footer: 'Comparing A with B.' italic note + Refresh / Clear
  picks buttons; paper-toned container, sits inside the paper plane
- Bottom footer: 'End of sheet 01 / Cyclone · Batch diff / A vs B
  or awaiting picks'

Round 11/11 — last page in the hybrid sweep.
2026-06-21 14:37:36 -06:00
Cyclone UI 736bf4d333 Refine Batches page with hybrid dark/paper treatment
- Add tone='paper' prop to shared Table/TableHeader/TableRow/TableHead/
  TableCell components; paper-toned chrome swaps dark muted/20 header
  for cream paper, dark hover for warm cream hover, dark selected for
  paper blue tint, muted-foreground text for surface-ink. Dark tone
  is the default and unchanged.
- Extend BatchesList with tone='paper' variant: cream hover, warm
  surface-ink text, paper-tinted open-row (blue tint + ring). Kind
  badge (text-sky-300 / text-amber-300 + data-testid) and row
  data-testid='batch-row-{id}' preserved for the test contract.
- Replace PageHeader with editorial hero: massive 'Parsed *batches.*'
  (clamp 48-80px serif, italic for 'batches.') + ghost 'PARSED'
  watermark + Files pill (837P/835/envelope) + tap-to-open hint
- Add torn-page fold with '↘ THE REGISTER ↙' label and 48-dot perforation
- Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and
  double-bordered title block: 'REGISTER · PARSED BATCHES' over
  'The register.' (clamp 48-96px) plus on-file count column
- Folio system: § 01 Vital signs (4 paper KPI tiles via new
  BatchesKpiTile: On file / 837P / 835 / Claims with ink/blue/amber/
  success accent rails and Icons from lucide), § 02 The register
  (paper-toned BatchesList)
- Error/Loading/Empty states wrapped in paper-toned containers
- Footer: 'End of register / Cyclone · Batches / N rows'

Round 10/11.
2026-06-21 14:34:26 -06:00
Tyler 7db5e448cd feat(dashboard): Recent denials row drillable to /claims?claim=ID 2026-06-21 14:28:49 -06:00
Tyler 5d6b5894f3 feat(dashboard): Top providers row drillable to /providers?provider=NPI 2026-06-21 14:28:49 -06:00
Tyler 88da3a6246 feat(dashboard): KPI tiles drillable — navigate to /claims with filter 2026-06-21 14:28:49 -06:00
Tyler 50dc0b2fb3 feat(drill): PayerPeekContent + usePayerSummary + api.getPayerSummary 2026-06-21 14:28:49 -06:00
Tyler e6ae364dad feat(api): extend /api/config/providers/{npi} with recent_claims + recent_activity 2026-06-21 14:28:49 -06:00
Tyler 9129543f5d feat(drill): hover affordance CSS + App wrapped in DrillStackProvider 2026-06-21 14:28:49 -06:00
Tyler fb7a5c6d2e feat(drill): PeekModal — centered Radix Dialog with eyebrow + title 2026-06-21 14:28:49 -06:00
Tyler 7dd6a5d025 feat(drill): DrillableCell — hover-reveal button wrapper 2026-06-21 14:28:49 -06:00
Tyler be93dbff72 feat(drill): DrillStackProvider — zustand-backed peek stack with 2-level cap 2026-06-21 14:28:49 -06:00
Cyclone UI feb15bf48d Refine Acks page with hybrid dark/paper treatment
- Replace PageHeader with editorial hero: massive 'Acknowledgments, *received.*'
  (clamp 48-80px serif, italic) + ghost '999' watermark + ShieldCheck pill
  + ?-shortcut kbd hint
- Add torn-page fold with '↘ 999 REGISTER ↙' label and 48-dot perforation
- Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and
  double-bordered title block: 'REGISTER · 999 IMPLEMENTATION ACKS' over
  'The register.' (clamp 48-96px) plus on-file count column
- Folio system: § 01 Vital signs (4 paper KPI tiles via new AckKpiTile),
  § 02 The register (paper-toned Table with blue selected-row tint)
- Paper-toned AckCodeBadge (success/destructive/warning tints), paper-toned
  DownloadButton
- Error/Loading/Empty states wrapped in paper-toned containers
- Footer: 'End of register / Cyclone · 999 ACKs / N rows'

Round 9/11.
2026-06-21 14:27:16 -06:00
Tyler ff0985d244 Refine Inbox with editorial hero and paper-toned queue ledger
InboxHeader:
- Editorial dark hero replaces the 30px sticky header
- Massive 'Inbox.' (clamp 48-80px) with ghost amber 'TRIAGE' watermark
- Italic subtitle: 'Five lanes, one queue. N need eyes; N done today.'
- Right column: SUN JUN 21 / 14:15 / local
- Live status row: amber pulse + counts + date

Inbox page:
- Amber fold with '↘ FIVE LANES · ONE QUEUE ↙' between header and lanes
- Lanes stay dark (working surface preserved)
- New paper-toned Queue ledger panel below lanes:
  - Title block: 'The eye-flow, at a glance.' with need-eyes total
  - 5 paper tiles (Rejected / Payer rejected / Candidates / Unmatched / Done today)
  - Each tile shows lane accent dot + count + 'Clear'/'rows' chip
- Loading and error states now also use the new InboxHeader for consistency
- SummaryTile helper with paper-toned metric cards
2026-06-21 14:15:25 -06:00
Tyler 94990932f9 Refine Reconciliation page with hybrid dark/paper treatment
- Dark editorial hero: 'Manual pairing.' (80px serif, italic pairing)
- 'AUTO-MATCH PAUSED' amber status pill on the right
- Ghost 'PAIRED' watermark behind the hero
- Dramatic torn-page fold with shadow + perforation + 'PAIR THEM BY HAND' label
- Paper plane with title block ('RECONCILIATION · SHEET 01' / 'Pair them.' 96px)
- Folio system (§ 01 Vital signs, § 02 The match, § 03 The action)
- KPI strip with 3 paper-toned tiles (Unmatched claims / remits / in queue)
- Two-column pairing surface with paper cards, blue/amber accent rails
  and a center vertical bridge that flips to green-merge when both selected
- Active row uses paper-tinted blue (claim) or amber (remit) backgrounds
- Action footer with italic 'Ready to pair' / 'Now pick a remit' states
- Empty state: 'Nothing pending.' with circle-dashed icon on cream
- Footer: 'END OF SHEET 01 / CYCLONE · RECONCILIATION / N ROWS PENDING'
2026-06-21 14:05:05 -06:00
Tyler d0a18bd796 Refine Upload page with hybrid dark/paper treatment
- Dark editorial hero: 'Parse an X12 file.' (80px serif, italic X12)
- Ghost 'DROP' watermark behind the hero
- Dramatic torn-page fold with shadow + perforation + 'DROP & PARSE' label
- Paper plane with title block ('INGESTION · SHEET 01' / 'Drop a file.' 96px)
- Right column accepts indicator (.txt / 837P · 835 / backend status pill)
- Folio system (§ 01 The drop, § 02 The stream, § 03 The archive)
- Paper-toned drop zone, selects, and claim cards (replaces dark surface-2)
- Stream section with progress bar (gradient fill) and 2-column totals
- Recent batches list with 'batches' count in footer
- Empty state with inbox icon when no batches exist
- Footer: 'END OF SHEET 01 / CYCLONE · INGESTION / N BATCHES'
2026-06-21 13:55:22 -06:00