Files
jorgecuadros-platform/apps/web/src/components/DiscardBatchCard.tsx
T
rmancinas 3125b52057 feat(ocr): discard abandoned capture batches
A bad scan, the wrong PDFs or a duplicate upload used to leave a batch
sitting in READY_FOR_REVIEW forever, because the only exits were confirm
(posts to the books) or rejecting every page one at a time. Add a
DISCARDED terminal status to both OCR domains and a single endpoint per
domain that rejects every page still pending in one shot.

Discarding is refused once anything has landed: statements once a page is
POSTED, policies once a page is APPLIED. Those batches did real work and
have to be settled page by page.

- POST /statements/batches/:id/discard
- POST /policy-ocr/batches/:id/discard
- shared DiscardBatchCard on both review screens, gated the same way
2026-08-02 02:00:02 -07:00

81 lines
2.4 KiB
TypeScript

"use client";
import { useState } from "react";
/**
* "Throw this batch away" control, shared by both OCR review queues
* (recibos and pólizas).
*
* Confirmation is a two-step inline swap rather than `window.confirm`: the
* dialog would block the page, and an accidental discard is not undoable from
* the UI — the reviewer should read what they are about to lose, not dismiss
* a modal reflexively.
*
* The card is only rendered when the batch is still discardable; the API
* refuses again on its own (a page posted between render and click).
*/
export function DiscardBatchCard({
busy,
onDiscard,
pageCount,
what,
}: {
busy: boolean;
onDiscard: () => void;
pageCount: number;
/** Singular noun for what a page becomes — "recibo" / "póliza". */
what: string;
}) {
const [armed, setArmed] = useState(false);
return (
<section className="card" style={{ padding: 16 }}>
<h2 className="section-title" style={{ marginTop: 0 }}>
Descartar lote
</h2>
{armed ? (
<>
<p className="page-sub" style={{ marginBottom: 12 }}>
Se descartarán las {pageCount} página(s) de este lote y no se
creará ninguna {what}. Esto no se puede deshacer desde aquí; para
volver a intentarlo hay que subir los PDFs otra vez.
</p>
<div className="inline-form" style={{ gap: 8 }}>
<button
type="button"
className="btn btn-danger"
disabled={busy}
onClick={onDiscard}
>
{busy ? "Descartando…" : "Sí, descartar el lote"}
</button>
<button
type="button"
className="btn btn-ghost"
disabled={busy}
onClick={() => setArmed(false)}
>
Cancelar
</button>
</div>
</>
) : (
<>
<p className="page-sub" style={{ marginBottom: 12 }}>
Si el lote quedó mal (escaneo ilegible, PDFs equivocados, subida
duplicada), descártelo para sacarlo de la cola de revisión.
</p>
<button
type="button"
className="btn btn-ghost"
disabled={busy}
onClick={() => setArmed(true)}
>
Descartar lote
</button>
</>
)}
</section>
);
}