Files
cyclone/src/lib/download.ts
T
Tyler 1764df0cd5 feat(sp8): Claim drawer Download 837 button
Three pieces:

- src/lib/download.ts: generic downloadTextFile(filename, mime, text)
  helper. Mirrors csv.ts:downloadCsv but takes an explicit MIME type and
  drops the BOM prepend (which would corrupt the ISA segment).

- src/lib/api.ts: serializeClaim837(id) → {text, filename}. Fetches
  GET /api/claims/{id}/serialize-837, pulls the suggested filename from
  Content-Disposition (falls back to claim-{id}.x12 if the header is
  missing). Throws ApiError on non-2xx so callers can branch on .status.

- ClaimDrawerHeader: Download icon button between the amount and the
  close button. Click → api.serializeClaim837 → downloadTextFile.
  Disabled + 'Downloading 837 file' aria-label while the fetch is in
  flight so the click feels responsive. Optional onError prop surfaces
  fetch failures; defaults to a no-op so existing callers stay clean.

Tests: 3 download.test.ts, 3 api.test.ts, 2 header.test.ts (happy
path + error path). Frontend: 350 passing (+8 from 342).
2026-06-20 20:44:39 -06:00

32 lines
1.2 KiB
TypeScript

/**
* Generic browser-side download helper for plain-text files.
*
* Mirrors `downloadCsv` in `./csv.ts` but takes an explicit MIME type so the
* same code path can serve X12 (text/x12), JSON (application/json), or any
* other text payload without growing a one-off helper per format.
*
* The implementation is identical to `downloadCsv` minus the BOM prepending
* (we want byte-faithful X12 — prepending a BOM corrupts the ISA segment).
*
* Safe to call from SSR/Node — guards against a missing `document`.
*/
export function downloadTextFile(
filename: string,
mimeType: string,
text: string,
): void {
if (typeof document === "undefined" || typeof URL === "undefined") return;
const blob = new Blob([text], { type: `${mimeType};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
// Some browsers require the link to be in the DOM before .click() works.
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Defer revoke so the browser has the URL handle when the download kicks off.
queueMicrotask(() => URL.revokeObjectURL(url));
}