From 388ea6e49b387f26da82f88fab819913c2c03410 Mon Sep 17 00:00:00 2001 From: Cyclone UI Date: Sun, 21 Jun 2026 14:37:36 -0600 Subject: [PATCH] Refine Batch Diff page with hybrid dark/paper treatment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace small header with editorial hero: massive 'Compare *batches.*' (clamp 48-80px serif, italic for 'batches.') + ghost 'DIFF' watermark + GitCompareArrows pill (837P/835/first vs second) + pick-a-batch hint - Add torn-page fold with '↘ A → B ↙' label and 48-dot perforation - Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and double-bordered title block: 'SHEET 01 · COMPARE TWO BATCHES' over 'Compare them.' (clamp 48-96px) plus A→B wire indicator (blue A, amber B, paper-ink-3 placeholder) - New Folio helper renders the § N + vertical-rl italic label; reused on the picks and diff sections - § 01 The picks: BatchPicker gets a paper-toned card with side badge (A blue / B amber tinted square) and claim count, preserving the data-testid='diff-picker-{a,b}' and 'diff-picker-kind-{kind}' and 'diff-picker-option-{id}' contracts - § 02 The diff: conditional rendering (awaiting-picks dashed card, loading skeleton, not-found, error, empty diff, full diff view) with paper-toned wrappers - BatchDiffView refactored to paper-toned chrome: SideMeta cards (warm surface, surface-ink text), SummaryCards → new DiffKpiTile helper (success/destructive/amber/ink accent rails with lucide icons), SectionTables → tone='paper' tables with cream header band and surface-ink text. All section/row/indicator testids preserved. - Action footer: 'Comparing A with B.' italic note + Refresh / Clear picks buttons; paper-toned container, sits inside the paper plane - Bottom footer: 'End of sheet 01 / Cyclone · Batch diff / A vs B or awaiting picks' Round 11/11 — last page in the hybrid sweep. --- src/components/BatchDiffView.tsx | 280 ++++++++++-- src/pages/BatchDiff.tsx | 722 ++++++++++++++++++++++++++----- 2 files changed, 866 insertions(+), 136 deletions(-) diff --git a/src/components/BatchDiffView.tsx b/src/components/BatchDiffView.tsx index 234e710..a7b3311 100644 --- a/src/components/BatchDiffView.tsx +++ b/src/components/BatchDiffView.tsx @@ -1,4 +1,10 @@ -import { Plus, Minus, Equal, ArrowRight, type LucideIcon } from "lucide-react"; +import { + ArrowRight, + Equal, + Minus, + Plus, + type LucideIcon, +} from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -9,19 +15,20 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { KpiCard } from "@/components/KpiCard"; import { fmt } from "@/lib/format"; import { cn } from "@/lib/utils"; import type { + BatchClaimDiffSummary, BatchDiff, BatchDiffChangedRow, BatchDiffSideMeta, BatchDiffSummary, - BatchClaimDiffSummary, } from "@/types"; // --------------------------------------------------------------------------- -// Side meta header — small panel identifying which batch is on the left / right. +// Side meta header — small panel identifying which batch is on the +// left / right. Paper-toned to sit inside the cream "paper plane" of +// the BatchDiff page. data-testid pinned by BatchDiff.test.tsx. // --------------------------------------------------------------------------- function SideMeta({ @@ -39,10 +46,19 @@ function SideMeta({ : "text-amber-300 border-amber-400/30 bg-amber-400/10"; return (
-
+
{label}
@@ -54,12 +70,23 @@ function SideMeta({ > {side.kind} - {side.id} + + {side.id} +
-
+
{side.inputFilename}
-
+
Parsed {side.parsedAt ? fmt.dateShort(side.parsedAt) : "—"} {fmt.num(side.claimCount)} claim{side.claimCount === 1 ? "" : "s"} @@ -70,40 +97,125 @@ function SideMeta({ } // --------------------------------------------------------------------------- -// Summary cards — the four-count tile row. +// Summary cards — the four-count tile row. Paper-toned via +// DiffKpiTile so the counts sit naturally on the cream paper plane. // --------------------------------------------------------------------------- function SummaryCards({ summary }: { summary: BatchDiffSummary }) { return ( -
- + - - -
); } +// --------------------------------------------------------------------------- +// DiffKpiTile — paper-toned metric tile for the diff summary row. +// Mirrors AckKpiTile / BatchesKpiTile from the other hybrid pages. +// --------------------------------------------------------------------------- + +function DiffKpiTile({ + label, + value, + icon: Icon, + hint, + tone, +}: { + label: string; + value: string; + icon: LucideIcon; + hint: string; + tone: "success" | "destructive" | "amber" | "ink"; +}) { + const accentMap = { + success: "hsl(152 64% 38%)", + destructive: "hsl(358 70% 42%)", + amber: "hsl(36 92% 50%)", + ink: "hsl(var(--surface-ink))", + } as const; + const tintMap = { + success: "hsl(152 50% 88%)", + destructive: "hsl(358 70% 92%)", + amber: "hsl(36 82% 92%)", + ink: "hsl(36 22% 90%)", + } as const; + const accent = accentMap[tone]; + const tint = tintMap[tone]; + return ( +
+
+
+
+ {label} +
+
+ +
+
+
+ {value} +
+
+ ); +} + // --------------------------------------------------------------------------- // Row indicator — `+` / `-` / `~` gutter on the left of every row. // --------------------------------------------------------------------------- @@ -152,7 +264,10 @@ function RowIndicator({ function ClaimIdCell({ id }: { id: string }) { return ( - + {id} ); @@ -160,14 +275,31 @@ function ClaimIdCell({ id }: { id: string }) { function PatientCell({ name }: { name: string }) { if (!name) { - return ; + return ( + + — + + ); } - return {name}; + return ( + + {name} + + ); } function ChargeCell({ value }: { value: number }) { return ( - + {fmt.usdPrecise(value)} ); @@ -175,19 +307,44 @@ function ChargeCell({ value }: { value: number }) { function DateCell({ value }: { value: string | null }) { if (!value) { - return ; + return ( + + — + + ); } - return {value}; + return ( + + {value} + + ); } function StatusCell({ value }: { value: string }) { if (!value) { - return ; + return ( + + — + + ); } return ( {value} @@ -196,17 +353,28 @@ function StatusCell({ value }: { value: string }) { function CptCell({ codes }: { codes: string[] }) { if (!codes || codes.length === 0) { - return ; + return ( + + — + + ); } return ( - + {codes.join(", ")} ); } // --------------------------------------------------------------------------- -// Section tables — one per bucket (added / removed / changed). +// Section tables — one per bucket (added / removed / changed). Uses +// `tone="paper"` so the table chrome matches the cream paper plane. // --------------------------------------------------------------------------- type Side = "a" | "b"; @@ -386,25 +554,53 @@ function SectionTable({
-
+
{eyebrow}
-

{title}

+

+ {title} +

- + {fmt.num(count)}
{count === 0 ? (
{emptyMessage}
) : ( -
- +
+
@@ -425,7 +621,8 @@ function SectionTable({ } // --------------------------------------------------------------------------- -// Skeleton — used by the page while the diff is loading. +// Skeleton — used by the page while the diff is loading. Paper-toned +// (cream blocks) to match the paper plane chrome. // --------------------------------------------------------------------------- export function BatchDiffViewSkeleton() { @@ -462,11 +659,24 @@ export function BatchDiffEmpty({ data }: { data: BatchDiff }) { -
-
+
+
Diff · no deltas
-
+
These two batches are identical — no claims added, removed, or changed between A and B.
diff --git a/src/pages/BatchDiff.tsx b/src/pages/BatchDiff.tsx index 1228ce0..14607ea 100644 --- a/src/pages/BatchDiff.tsx +++ b/src/pages/BatchDiff.tsx @@ -1,6 +1,11 @@ import { useCallback, useMemo } from "react"; import { useSearchParams } from "react-router-dom"; -import { GitCompareArrows, CircleDashed, AlertCircle } from "lucide-react"; +import { + AlertCircle, + ArrowRight, + CircleDashed, + GitCompareArrows, +} from "lucide-react"; import { ApiError } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { @@ -12,7 +17,10 @@ import { } from "@/components/ui/select"; import { EmptyState } from "@/components/ui/empty-state"; import { ErrorState } from "@/components/ui/error-state"; -import { BatchDiffView, BatchDiffViewSkeleton } from "@/components/BatchDiffView"; +import { + BatchDiffView, + BatchDiffViewSkeleton, +} from "@/components/BatchDiffView"; import { useBatches } from "@/hooks/useBatches"; import { useBatchDiff } from "@/hooks/useBatchDiff"; import type { BatchSummary as ApiBatchSummary } from "@/lib/api"; @@ -38,9 +46,11 @@ function readIdsFromParams(params: URLSearchParams): { } // --------------------------------------------------------------------------- -// Batch picker — one Select dropdown for each side. Keeps the same -// kind-colored badge inline so the operator can spot which batch is -// which at a glance. +// Kind badge — small inline label so the operator can spot which batch +// is which at a glance. Identical contract to the one in +// `BatchesList.tsx`; duplicated here because each lives inside a +// different page surface (the picker is part of this page, the row +// badge belongs to Batches). // --------------------------------------------------------------------------- function KindBadge({ kind }: { kind: ApiBatchSummary["kind"] }) { @@ -63,6 +73,7 @@ function KindBadge({ kind }: { kind: ApiBatchSummary["kind"] }) { function BatchPicker({ label, + side, value, onChange, items, @@ -70,6 +81,7 @@ function BatchPicker({ testid, }: { label: string; + side: "A" | "B"; value: string | null; onChange: (id: string) => void; items: ApiBatchSummary[]; @@ -85,10 +97,54 @@ function BatchPicker({ [items, excludeId], ); const selected = items.find((it) => it.id === value); + const accent = side === "A" ? "hsl(212 100% 45%)" : "hsl(36 92% 50%)"; + const tint = side === "A" ? "hsl(212 85% 95%)" : "hsl(36 82% 92%)"; return ( -
-
- {label} +
+
+
+
+
+ {side} +
+ + {label} + +
+ {selected ? ( + + {selected.claimCount}{" "} + + claims + + + ) : null}