9bca4b608a
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.
89 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|