import { useEffect, useRef, useState } from "react"; import { Button, InlineLoading, Modal, RadioButton, RadioButtonGroup, Tag } from "@carbon/react"; import { api } from "../lib/api"; import type { ErpnextPushResult } from "../lib/erpnext"; import { describePush, failedPush, summarizeBatch } from "../lib/erpnextUi"; import { useReturnFocus } from "../hooks/useReturnFocus"; export interface SendTarget { id: number; number: string; } interface Props { open: boolean; invoices: SendTarget[]; /** Cancelled or draft invoices that were left out of the selection. */ skipped?: number; /** The "submit on push" setting; the user still chooses explicitly. */ defaultSubmit: boolean; onClose: () => void; /** Called once when a send has finished, so the list can refresh its statuses. */ onFinished: (results: ErpnextPushResult[]) => void; } type Phase = "confirm" | "running" | "done"; const TAG = { success: "green", info: "blue", warning: "warm-gray", error: "red" } as const; /** Confirm step (draft or submit), then a sequential send with progress, then per-invoice results. A failure never stops the rest. */ export default function SendToErpnextModal({ open, invoices, skipped = 0, defaultSubmit, onClose, onFinished }: Props) { const [submit, setSubmit] = useState(defaultSubmit); const [phase, setPhase] = useState("confirm"); const [results, setResults] = useState([]); const alive = useRef(true); useReturnFocus(open); useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); useEffect(() => { if (!open) return; setSubmit(defaultSubmit); setPhase("confirm"); setResults([]); }, [open, defaultSubmit]); const run = async () => { setPhase("running"); const out: ErpnextPushResult[] = []; for (const inv of invoices) { let result: ErpnextPushResult; try { result = await api.erpnextPushInvoice(inv.id, submit); } catch (e) { // Not set up, or the command itself failed: this row fails, the others still go. result = failedPush(inv.id, inv.number, e); } out.push(result); if (alive.current) setResults([...out]); } if (alive.current) setPhase("done"); onFinished(out); }; const count = invoices.length; const noun = count === 1 ? "invoice" : "invoices"; if (phase === "confirm") { return ( void run()} >
setSubmit(v === "submit")}>

An invoice that is already in ERPNext is never overwritten or created twice. {skipped > 0 ? ` ${skipped} cancelled ${skipped === 1 ? "invoice is" : "invoices are"} left out: cancelled invoices are never sent.` : ""}

); } const summary = phase === "done" ? summarizeBatch(results) : null; return ( { if (phase === "done") onClose(); }} >
{phase === "running" ? ( ) : (

{summary?.title}

)}
    {results.map((r) => { const note = describePush(r); return (
  • {r.ok ? (r.noOp ? "Up to date" : r.remoteDocstatus === 1 ? "Submitted" : "Draft") : r.status === "conflict" ? "Conflict" : r.status === "refused" ? "Not sent" : "Error"} {" "} {r.number || `#${r.invoiceId}`} {r.remoteName && r.ok ? {r.remoteName} : null} {!r.ok && r.error ?
    {r.error}
    : null} {r.ok && r.warnings.length > 0 ?
    {r.warnings.join(" ")}
    : null}
  • ); })}
{phase === "done" ? ( ) : null}
); }