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.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// TickerTape — live vital-signs strip for the dashboard hero.
|
||||
//
|
||||
// A horizontal tape of small numeric readouts separated by hairline
|
||||
// dividers, rendered on the dark canvas. Each cell shows a label
|
||||
// (uppercase mono) and a value (mono). One cell can be marked
|
||||
// `accent` to draw the eye, and the optional `live` cell shows a
|
||||
// pulsing dot — the dashboard's only acknowledgement of time.
|
||||
//
|
||||
// This is a static ticker (not scrolling); the cells are just laid
|
||||
// out left-to-right with dividers. We use it to give the dark hero
|
||||
// data density without committing to a real chart.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface TickerCell {
|
||||
/** Stable id used as React key. */
|
||||
id: string;
|
||||
/** Uppercase mono label (e.g. "Billed", "Pending", "Denial"). */
|
||||
label: string;
|
||||
/** The value, rendered verbatim in mono. Format it before passing. */
|
||||
value: string;
|
||||
/** Optional secondary value shown muted (e.g. delta). */
|
||||
delta?: string;
|
||||
/** Highlight this cell with the accent colour. */
|
||||
accent?: boolean;
|
||||
}
|
||||
|
||||
export interface TickerTapeProps {
|
||||
cells: TickerCell[];
|
||||
/** Show the "LIVE" pulse on the leading edge. */
|
||||
live?: boolean;
|
||||
/** Live label (default "Live"). */
|
||||
liveLabel?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TickerTape({
|
||||
cells,
|
||||
live = false,
|
||||
liveLabel = "Live",
|
||||
className,
|
||||
}: TickerTapeProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex items-stretch w-full overflow-hidden",
|
||||
"rounded-md border border-border/50 bg-card/40 backdrop-blur",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Left rail — amber signal accent for the LIVE indicator */}
|
||||
{live ? (
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 border-r border-border/60 shrink-0"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(180deg, hsl(36 92% 56% / 0.06), transparent)",
|
||||
}}
|
||||
>
|
||||
<span className="relative inline-flex h-1.5 w-1.5 shrink-0">
|
||||
<span
|
||||
className="absolute inline-flex h-full w-full rounded-full opacity-60 animate-ping"
|
||||
style={{ backgroundColor: "hsl(var(--signal))" }}
|
||||
/>
|
||||
<span
|
||||
className="relative inline-flex h-1.5 w-1.5 rounded-full"
|
||||
style={{ backgroundColor: "hsl(var(--signal))" }}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className="mono uppercase tracking-[0.18em] font-semibold"
|
||||
style={{
|
||||
color: "hsl(var(--signal))",
|
||||
fontSize: 10,
|
||||
}}
|
||||
>
|
||||
{liveLabel}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Cells */}
|
||||
<div className="flex items-stretch flex-1 min-w-0 overflow-x-auto">
|
||||
{cells.map((c, i) => (
|
||||
<div
|
||||
key={c.id}
|
||||
className={cn(
|
||||
"flex-1 min-w-[110px] px-4 py-2.5 flex flex-col gap-0.5",
|
||||
i > 0 ? "border-l border-border/40" : ""
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className="mono uppercase tracking-[0.18em] font-medium"
|
||||
style={{
|
||||
color: c.accent
|
||||
? "hsl(var(--accent))"
|
||||
: "hsl(var(--muted-foreground))",
|
||||
fontSize: 9.5,
|
||||
}}
|
||||
>
|
||||
{c.label}
|
||||
</span>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span
|
||||
className="display mono tabular-nums"
|
||||
style={{
|
||||
color: c.accent
|
||||
? "hsl(var(--foreground))"
|
||||
: "hsl(var(--foreground))",
|
||||
fontSize: 16,
|
||||
letterSpacing: "-0.01em",
|
||||
}}
|
||||
>
|
||||
{c.value}
|
||||
</span>
|
||||
{c.delta ? (
|
||||
<span
|
||||
className="mono tabular-nums"
|
||||
style={{
|
||||
color: "hsl(var(--muted-foreground))",
|
||||
fontSize: 10.5,
|
||||
}}
|
||||
>
|
||||
{c.delta}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Right rail — perforated tick pattern, decoratively marks the
|
||||
end of the tape without using a heavy border. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="shrink-0 w-3 self-stretch"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, hsl(36 14% 88% / 0.16) 1px, transparent 1.5px)",
|
||||
backgroundSize: "6px 6px",
|
||||
backgroundPosition: "0 center",
|
||||
maskImage:
|
||||
"linear-gradient(to right, transparent, black 30%, black 100%)",
|
||||
WebkitMaskImage:
|
||||
"linear-gradient(to right, transparent, black 30%, black 100%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user