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:
@@ -141,12 +141,14 @@ export function ClaimDrawer({
|
||||
// Right-anchored side panel. We override the Dialog primitive's
|
||||
// default centered positioning (`left-1/2 top-1/2 -translate-x-1/2
|
||||
// -translate-y-1/2`) by re-asserting `right-0 top-0 translate-x-0
|
||||
// translate-y-0`. `h-full` + `rounded-none` + the left border
|
||||
// translate-y-0`. `h-full` + `rounded-none` + the left hairline
|
||||
// give it the visual identity of a drawer rather than a modal.
|
||||
// `bg-card` matches the rest of the dark instrument chrome; the
|
||||
// outer shadow is a soft directional falloff to the left.
|
||||
// `aria-describedby={undefined}` suppresses the Radix warning
|
||||
// about a missing description — the drawer content is its own
|
||||
// description and there's nothing useful to point at.
|
||||
className="fixed right-0 top-0 h-full w-full max-w-2xl translate-x-0 translate-y-0 rounded-none border-l border-[color:var(--m-border-heavy)]/60 bg-[color:var(--m-surface)] p-0 shadow-[-24px_0_60px_-12px_rgba(0,0,0,0.6)]"
|
||||
className="fixed right-0 top-0 h-full w-full max-w-2xl translate-x-0 translate-y-0 rounded-none border-l border-border/60 bg-card p-0 shadow-[-24px_0_60px_-12px_rgba(0,0,0,0.6)]"
|
||||
data-testid="claim-drawer"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
@@ -167,7 +169,7 @@ export function ClaimDrawer({
|
||||
>
|
||||
<ClaimDrawerHeader claim={data} onClose={onClose} />
|
||||
<div
|
||||
className="flex gap-0 px-6 border-b border-[color:var(--surface-line)]/40"
|
||||
className="flex gap-0 px-6 border-b border-border/40"
|
||||
data-testid="claim-drawer-tabs"
|
||||
>
|
||||
<button
|
||||
@@ -176,15 +178,15 @@ export function ClaimDrawer({
|
||||
className={cn(
|
||||
"relative px-3 py-2.5 text-[10.5px] font-semibold uppercase tracking-[0.18em] transition-colors",
|
||||
activeTab === "details"
|
||||
? "text-[color:var(--surface-ink)]"
|
||||
: "text-[color:var(--surface-ink-3)] hover:text-[color:var(--surface-ink-2)]"
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
data-testid="tab-button-details"
|
||||
data-active={activeTab === "details" ? "true" : "false"}
|
||||
>
|
||||
Details
|
||||
{activeTab === "details" ? (
|
||||
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-[color:var(--accent)]" />
|
||||
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-accent shadow-[0_0_8px_hsl(var(--accent)/0.6)]" />
|
||||
) : null}
|
||||
</button>
|
||||
<button
|
||||
@@ -193,29 +195,29 @@ export function ClaimDrawer({
|
||||
className={cn(
|
||||
"relative px-3 py-2.5 text-[10.5px] font-semibold uppercase tracking-[0.18em] transition-colors",
|
||||
activeTab === "line-reconciliation"
|
||||
? "text-[color:var(--surface-ink)]"
|
||||
: "text-[color:var(--surface-ink-3)] hover:text-[color:var(--surface-ink-2)]"
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
data-testid="tab-button-line-reconciliation"
|
||||
data-active={activeTab === "line-reconciliation" ? "true" : "false"}
|
||||
>
|
||||
Line Reconciliation
|
||||
{activeTab === "line-reconciliation" ? (
|
||||
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-[color:var(--accent)]" />
|
||||
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-accent shadow-[0_0_8px_hsl(var(--accent)/0.6)]" />
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
{activeTab === "line-reconciliation" ? (
|
||||
lr.loading ? (
|
||||
<p
|
||||
className="px-6 py-4 text-sm text-[color:var(--m-ink-tertiary)]"
|
||||
className="px-6 py-4 text-sm text-muted-foreground"
|
||||
data-testid="line-reconciliation-loading"
|
||||
>
|
||||
Loading line reconciliation…
|
||||
</p>
|
||||
) : lr.error ? (
|
||||
<p
|
||||
className="px-6 py-4 text-sm text-[color:var(--m-ink-tertiary)]"
|
||||
className="px-6 py-4 text-sm text-muted-foreground"
|
||||
data-testid="line-reconciliation-error"
|
||||
>
|
||||
Failed to load line reconciliation.
|
||||
@@ -224,7 +226,7 @@ export function ClaimDrawer({
|
||||
<LineReconciliationTab data={lr.data} />
|
||||
) : null
|
||||
) : (
|
||||
<div className="flex flex-col divide-y divide-[color:var(--surface-line)]/12">
|
||||
<div className="flex flex-col divide-y divide-border/40">
|
||||
<ValidationPanel validation={data.validation} />
|
||||
<ServiceLinesTable
|
||||
serviceLines={data.serviceLines}
|
||||
|
||||
Reference in New Issue
Block a user