Files
cyclone/src/components/Layout.tsx
T
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

89 lines
3.5 KiB
TypeScript

import { Outlet } from "react-router-dom";
import { useIsFetching } from "@tanstack/react-query";
import { cn } from "@/lib/utils";
import { Sidebar } from "./Sidebar";
import { SkipLink } from "@/components/ui/skip-link";
import { SearchBar } from "./SearchBar";
/**
* App shell. Three columns:
* - Sidebar (240px, fixed)
* - Top bar (sticky, lightweight utility row)
* - Outlet (page content, max-w-[1400px] centered)
*
* The hairline scan element at the very top of the viewport is a 1px
* line that slides an accent bar across the entire width whenever
* the React Query layer has an in-flight request. The intent is to
* surface "we're doing something" without a spinner — a moving line
* reads as a sensor sweep, not a loading state.
*/
export function Layout() {
const isFetching = useIsFetching();
const showScan = isFetching > 0;
return (
<div className="relative min-h-screen z-10">
<SkipLink />
{/* Ambient page halo — a soft, fixed radial that creates a
subtle lighter zone behind the page content (centered
just below the top bar). Pure decoration; pointer-events
disabled so it never intercepts clicks. Two layered
radials: a neutral softbox centered above the page
header, and a cool accent in the upper-right that
mirrors the body::before composition. The halo sits
behind everything else (z-0 inside the z-10 root). */}
<div
aria-hidden
className="pointer-events-none fixed inset-x-0 top-0 z-0 h-[70vh]"
style={{
background: `
radial-gradient(ellipse 55% 30% at 50% 6%, hsla(220, 32%, 30%, 0.16), transparent 70%),
radial-gradient(ellipse 35% 25% at 82% 10%, hsla(212, 70%, 60%, 0.10), transparent 65%)
`,
}}
/>
<div
className="fixed top-0 left-0 right-0 z-50 h-px overflow-hidden pointer-events-none transition-opacity duration-200"
style={{ opacity: showScan ? 1 : 0 }}
aria-hidden
>
<div
className="h-full w-1/3 bg-accent animate-scan"
style={{ boxShadow: "0 0 8px hsl(var(--accent) / 0.5)" }}
/>
</div>
<Sidebar />
<main
id="main-content"
tabIndex={-1}
className="md:pl-60 outline-none"
>
<div
className="sticky top-0 z-20 mx-auto max-w-[1400px] px-6 lg:px-8 h-12 flex items-center justify-end gap-3 bg-background/70 backdrop-blur-md border-b border-border/40"
data-print="hide"
>
{/* Tailing pill on the left edge of the top bar — a tiny
global "system is alive" indicator. It lights up whenever
any query is in flight, complementing the hairline scan. */}
<div className="mr-auto flex items-center gap-2 text-[10.5px] mono uppercase tracking-[0.14em] text-muted-foreground/60">
<span className="relative inline-flex h-1.5 w-1.5">
<span
className={cn(
"absolute inline-flex h-full w-full rounded-full bg-[hsl(var(--success))] opacity-60",
showScan && "animate-ping"
)}
/>
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-[hsl(var(--success))]" />
</span>
{showScan ? "Working" : "Idle"}
</div>
<SearchBar />
</div>
<div className="mx-auto max-w-[1400px] px-6 lg:px-8 py-8 lg:py-10">
<Outlet />
</div>
</main>
</div>
);
}