Workflow-App/Workflow/components/ReportActions.tsx

137 lines
4.9 KiB
TypeScript
Raw Normal View History

"use client";
// components/ReportActions.tsx
// Two buttons: "Create PDF" (download) and "Mail PDF" (email).
// Used on Company and Director detail pages.
import { useState } from "react";
interface Props {
type: "company" | "director";
id: string;
/** Shown in the mail sent confirmation. */
emailAddress?: string | null;
}
export default function ReportActions({ type, id, emailAddress }: Props) {
const [pdfLoading, setPdfLoading] = useState(false);
const [mailLoading, setMailLoading] = useState(false);
const [mailResult, setMailResult] = useState<{ ok: boolean; message: string } | null>(null);
async function handleDownloadPdf() {
setPdfLoading(true);
try {
const res = await fetch(`/api/report/${type}/${id}/pdf`);
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Unknown error" }));
alert(`PDF error: ${err.error}`);
return;
}
const blob = await res.blob();
const disposition = res.headers.get("Content-Disposition") ?? "";
const nameMatch = disposition.match(/filename="?([^"]+)"?/);
const filename = nameMatch ? nameMatch[1] : `${type}_report.pdf`;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
} catch (e) {
alert(`PDF download failed: ${e instanceof Error ? e.message : String(e)}`);
} finally {
setPdfLoading(false);
}
}
async function handleMailPdf() {
if (!emailAddress) {
alert("No email address on file for this " + (type === "company" ? "company." : "director."));
return;
}
if (!confirm(`Send the ${type} report PDF to ${emailAddress}?`)) return;
setMailLoading(true);
setMailResult(null);
try {
const res = await fetch(`/api/report/${type}/${id}/email`, { method: "POST" });
const data = await res.json();
if (res.ok && data.ok) {
setMailResult({ ok: true, message: `Sent to ${data.sent_to}` });
} else {
setMailResult({ ok: false, message: data.error ?? "Send failed" });
}
} catch (e) {
setMailResult({
ok: false,
message: e instanceof Error ? e.message : String(e),
});
} finally {
setMailLoading(false);
}
}
return (
<div className="flex items-center gap-2 flex-wrap">
{/* Create PDF */}
<button
onClick={handleDownloadPdf}
disabled={pdfLoading}
className="inline-flex items-center gap-1.5 px-4 py-2.5 rounded-md border border-border bg-surface text-fg text-sm font-semibold hover:bg-surface-warm transition-colors disabled:opacity-50"
>
{pdfLoading ? (
<>
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
Generating…
</>
) : (
<>
<svg className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm5 6a1 1 0 10-2 0v3.586l-1.293-1.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V8z" clipRule="evenodd" />
</svg>
Create PDF
</>
)}
</button>
{/* Mail PDF */}
<button
onClick={handleMailPdf}
disabled={mailLoading}
className="inline-flex items-center gap-1.5 px-4 py-2.5 rounded-md border border-border bg-surface text-fg text-sm font-semibold hover:bg-surface-warm transition-colors disabled:opacity-50"
>
{mailLoading ? (
<>
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
Sending…
</>
) : (
<>
<svg className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
Mail PDF
</>
)}
</button>
{/* Result feedback */}
{mailResult && (
<span
className={`text-sm font-medium ${
mailResult.ok ? "text-green-600" : "text-red-500"
}`}
>
{mailResult.ok ? "✓ " : "✗ "}
{mailResult.message}
</span>
)}
</div>
);
}