Files
Voiced/src/components/FlattenProgressModal.tsx
T
xavierk b6d999b3e8 Add searchable and flattened export, history re-export and diagnostics self-test
issueAndArchive keeps an issued invoice even when rendering or archiving
fails (Issued - not archived, with a Render & archive action). The export
menu offers a searchable PDF or a 300 DPI image-only PDF whose metadata is
the invoice number alone; flatten failures offer Retry or searchable export
and never silently lower the resolution. History re-exports the original
as issued by default and warns when a re-render's layout fingerprint
differs. The render client gains a FIFO assemble lane that previews cannot
supersede. A Diagnostics panel runs fixtures in the real worker and
compares against goldens generated from Node.
2026-10-04 06:11:23 +05:30

65 lines
2.5 KiB
TypeScript

import { Button, ComposedModal, InlineNotification, ModalBody, ModalFooter, ModalHeader, ProgressBar } from "@carbon/react";
import type { ExportProgress } from "../lib/exportFlow";
export interface FlattenProgressModalProps {
open: boolean;
progress: ExportProgress | null;
/** The flatten failed: show it with Retry and "Export searchable instead". */
error: string | null;
onCancel: () => void;
onRetry: () => void;
onExportSearchable: () => void;
onClose: () => void;
}
function describe(p: ExportProgress | null): { label: string; value?: number; max: number } {
if (p?.phase === "flattening" && p.page && p.pages) {
// The page being rendered counts as in progress, so the bar shows the pages finished before it.
return { label: `Rendering page ${p.page} of ${p.pages}…`, value: p.page - 1, max: p.pages };
}
if (p?.phase === "assembling") return { label: "Building the PDF…", value: 1, max: 1 };
return { label: "Preparing the invoice…", max: 1 };
}
export default function FlattenProgressModal({ open, progress, error, onCancel, onRetry, onExportSearchable, onClose }: FlattenProgressModalProps) {
const { label, value, max } = describe(progress);
return (
<ComposedModal open={open} size="sm" preventCloseOnClickOutside onClose={() => (error ? onClose() : onCancel())} aria-label="Flattened PDF export">
<ModalHeader title="Flattened PDF" label="Export" />
<ModalBody hasForm={false}>
{error ? (
<InlineNotification
kind="error"
lowContrast
hideCloseButton
title="The flattened PDF could not be made"
subtitle={`${error} Nothing was saved, and the resolution was not lowered.`}
/>
) : (
<ProgressBar label={label} value={value} max={max} helperText="Each page is drawn at 300 DPI; this takes a few seconds per page." />
)}
</ModalBody>
{error ? (
// Three direct children: Carbon lays a three-button footer out differently from a two-button one.
<ModalFooter>
<Button kind="secondary" onClick={onClose}>
Close
</Button>
<Button kind="tertiary" onClick={onExportSearchable}>
Export searchable instead
</Button>
<Button kind="primary" onClick={onRetry}>
Retry
</Button>
</ModalFooter>
) : (
<ModalFooter>
<Button kind="secondary" onClick={onCancel}>
Cancel
</Button>
</ModalFooter>
)}
</ComposedModal>
);
}