// --------------------------------------------------------------------------- // SP30: "Recent batches" Dashboard widget. // // One row per batch the operator parsed recently. Each row tells the // operator two things at a glance: // 1. Was this batch clean or did it have a problem? (icon tint + // `topRejectionReason` line under the filename when present.) // 2. What was the billed-out outcome? ($ for 837P; payment count for // 835 ERA batches — the Remittance table has no batch-level // `total_charge` aggregate.) // // Click → onRowClick(id) so the page can navigate to /batches?batch=ID // (the BatchDrawer deep-link pattern from pages/Batches.tsx). // // Reuses the Dashboard "Recent activity" card chrome verbatim (Card + // CardHeader pb-3 + CardTitle text-[14px] + CardContent pt-0) so the // widget reads as part of the same family. // --------------------------------------------------------------------------- import { Layers, CheckCircle2, AlertTriangle } from "lucide-react"; import type { KeyboardEvent } from "react"; import { Card, CardContent, CardHeader, CardTitle, } from "@/components/ui/card"; import { fmt } from "@/lib/format"; import type { BatchSummary } from "@/lib/api"; import { cn } from "@/lib/utils"; type Props = { batches: BatchSummary[]; onRowClick: (batchId: string) => void; /** Cap shown in the header subtitle. Default 5. */ limit?: number; }; export function RecentBatchesWidget({ batches, onRowClick, limit = 5 }: Props) { return (
Recent batches

How the last {limit} batches billed out — sorted newest first.

{batches.length} latest
{batches.length === 0 ? (
No batches yet.
) : ( )}
); } // --------------------------------------------------------------------------- // BatchRow — one row in the widget. Kept as a separate component so the // parent can stay readable; matches the clickable-row + keyboard // accessibility pattern from Dashboard.tsx:300-307 and the divide-y // row markup from ActivityFeed.tsx:124-140. // --------------------------------------------------------------------------- type BatchRowProps = { batch: BatchSummary; tint: string; Icon: typeof CheckCircle2; ariaLabel: string; onActivate: () => void; }; function BatchRow({ batch, tint, Icon, ariaLabel, onActivate }: BatchRowProps) { function onKeyDown(e: KeyboardEvent) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onActivate(); } } const isErn = batch.kind === "835"; const accepted = batch.acceptedCount ?? 0; const billed = batch.billedTotal ?? 0; return (
  • {batch.inputFilename || batch.id}
    {batch.kind.toUpperCase()} · {fmt.relative(batch.parsedAt)} {batch.topRejectionReason && ( <> · {batch.topRejectionReason} )}
    {isErn ? ( <>
    {fmt.num(batch.claimCount)}
    payments
    ) : ( <>
    {fmt.usd(billed)}
    {fmt.num(accepted)}/{fmt.num(batch.claimCount)} accepted
    )}
  • ); }