"use client" import { Button } from "@conusai-kit/ui/primitives" // Download the approved lines as an Excel workbook. The procedure answers with the file as base64 // (a procedure's answer is JSON on every transport); the browser turns it into a download. With // nothing approved the button is disabled and says why, in text a screen reader reads with it. import { describeError, useWrite } from "@conusai-kit/ui/data" import { type ReactElement, useEffect, useId, useState } from "react" import type { ProjectSlugs } from "./slugs.js" export interface ExportLabels { readonly export: string readonly hint: string readonly nothing: string /** Said after the workbook was handed to the browser. */ readonly exported: string readonly failed: string } export function ExportButton({ slugs, fileId, approved, labels, }: { slugs: ProjectSlugs fileId: string /** How many lines are approved: with none, there is nothing to export. */ approved: number labels: ExportLabels }): ReactElement { const createExport = useWrite< ProjectSlugs & { fileId: string }, { filename: string; contentType: string; body: string } >("documents.export.create") const hintId = useId() const [interactive, setInteractive] = useState(false) useEffect(() => setInteractive(true), []) const [message, setMessage] = useState<{ ok: boolean; text: string } | undefined>(undefined) const [busy, setBusy] = useState(false) const run = async (): Promise => { setBusy(true) setMessage(undefined) try { const made = await createExport.mutate({ ...slugs, fileId }) const bytes = Uint8Array.from(atob(made.body), (char) => char.charCodeAt(0)) const url = URL.createObjectURL(new Blob([bytes], { type: made.contentType })) const link = document.createElement("a") link.href = url link.download = made.filename // Attached while it is clicked, and the object URL kept for a while after: a browser starts the // download after the click returns, and a URL revoked first is a download that never starts. document.body.append(link) link.click() link.remove() setTimeout(() => URL.revokeObjectURL(url), 60_000) setMessage({ ok: true, text: labels.exported }) } catch (error) { setMessage({ ok: false, text: labels.failed.replace("{detail}", describeError(error)) }) } finally { setBusy(false) } } return (

{approved === 0 ? labels.nothing : labels.hint}

{message?.text ?? ""}

) }