From a79668192daa9da09fbcc8c073ef74ab985adaa2 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 20 Jun 2026 17:25:40 -0600 Subject: [PATCH] feat(frontend): global Cmd-K search across claims/remits/activity --- src/components/Layout.tsx | 13 + src/components/SearchBar.test.tsx | 444 ++++++++++++++++++++++++++ src/components/SearchBar.tsx | 301 +++++++++++++++++ src/components/SearchResults.test.tsx | 300 +++++++++++++++++ src/components/SearchResults.tsx | 263 +++++++++++++++ src/hooks/useSearch.test.ts | 400 +++++++++++++++++++++++ src/hooks/useSearch.ts | 438 +++++++++++++++++++++++++ 7 files changed, 2159 insertions(+) create mode 100644 src/components/SearchBar.test.tsx create mode 100644 src/components/SearchBar.tsx create mode 100644 src/components/SearchResults.test.tsx create mode 100644 src/components/SearchResults.tsx create mode 100644 src/hooks/useSearch.test.ts create mode 100644 src/hooks/useSearch.ts diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 92d570f..56a79de 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -2,6 +2,7 @@ import { Outlet } from "react-router-dom"; import { useIsFetching } from "@tanstack/react-query"; import { Sidebar } from "./Sidebar"; import { SkipLink } from "@/components/ui/skip-link"; +import { SearchBar } from "./SearchBar"; export function Layout() { // useIsFetching() returns the number of in-flight queries and is 0 on @@ -37,6 +38,18 @@ export function Layout() { tabIndex={-1} className="md:pl-60 outline-none" > + {/* + Top bar — sits inside
so it shares the sidebar's right + column. Sticky to the viewport top so the search chip stays + reachable while the user scrolls long claim lists. The + hairline + horizontal padding mirrors the main content + container below it for visual continuity. The Sidebar's + own header is taller and brand-marked; this is the + lightweight "always-on utility" row. + */} +
+ +
diff --git a/src/components/SearchBar.test.tsx b/src/components/SearchBar.test.tsx new file mode 100644 index 0000000..48ad373 --- /dev/null +++ b/src/components/SearchBar.test.tsx @@ -0,0 +1,444 @@ +// @vitest-environment happy-dom +// SearchBar is the top-level component — it owns the dialog, the +// input, the keyboard shortcut, and navigation. We need both the +// act-aware env flag AND a MemoryRouter so `useNavigate` doesn't +// blow up at render time. +(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { MemoryRouter } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { describe, expect, it, vi } from "vitest"; + +import { SearchBar } from "./SearchBar"; +import { useAppStore } from "@/store"; +import type { Activity, Claim, Remittance } from "@/types"; + +// --------------------------------------------------------------------------- +// Mocks — same as useSearch.test.ts: useSearch falls back to the +// in-memory store when `api.isConfigured` is false. We rely on that +// for the SearchBar tests too so we don't need to wire a fake fetch. +// --------------------------------------------------------------------------- + +vi.mock("@/lib/api", () => ({ + api: { + isConfigured: false, + listClaims: vi.fn(), + listRemittances: vi.fn(), + listActivity: vi.fn(), + }, +})); + +// --------------------------------------------------------------------------- +// Fixtures — minimal: enough to make sure hits show up. +// --------------------------------------------------------------------------- + +const CLAIMS: Claim[] = [ + { + id: "CLM-10428", + patientName: "Avery Nguyen", + providerNpi: "1730187395", + payerName: "Blue Cross Blue Shield", + cptCode: "99213", + billedAmount: 240, + receivedAmount: 240, + status: "paid", + submissionDate: "2026-05-01T00:00:00Z", + }, +]; + +const REMITS: Remittance[] = [ + { + id: "REM-7782", + claimId: "CLM-10428", + payerName: "Blue Cross Blue Shield", + paidAmount: 240, + adjustmentAmount: 0, + receivedDate: "2026-05-10T00:00:00Z", + checkNumber: "EFT-100123", + status: "reconciled", + }, +]; + +const ACTIVITY: Activity[] = [ + { + id: "ACT-CLM-10428", + kind: "claim_paid", + message: "Paid CLM-10428 · Avery Nguyen", + timestamp: "2026-05-10T00:00:00Z", + npi: "1730187395", + amount: 240, + }, +]; + +/** + * Set an input's value the way React's synthetic event system expects. + * + * React 17+ installs an internal value tracker on `` / + * `