'use client'; import { useState } from 'react'; import { FileDown, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { PDF_DASHBOARD_WIDGETS, type PdfDashboardWidgetId, } from '@/lib/services/dashboard-report-data.service'; import { triggerBlobDownload } from '@/lib/utils/download'; import { usePermissions } from '@/hooks/use-permissions'; import { resolvePortIdFromSlug } from '@/lib/api/client'; /** * Dashboard "Export as PDF" affordance. Per-export dialog lets reps * pick which sections to include + set a custom title. Saved-template * support lands in Phase C; for now, the dialog defaults all widgets * checked + the current date in the title. * * Permission-gated client-side on `reports.export`; the server route * re-checks via withPermission so a tampered client can't bypass. */ export function ExportDashboardPdfButton() { const { can } = usePermissions(); const [open, setOpen] = useState(false); const [title, setTitle] = useState( `Dashboard report — ${new Date().toLocaleDateString('en-GB')}`, ); const [selected, setSelected] = useState( PDF_DASHBOARD_WIDGETS.map((w) => w.id), ); const [loading, setLoading] = useState(false); if (!can('reports', 'export')) return null; function toggle(id: PdfDashboardWidgetId) { setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); } async function handleExport() { if (selected.length === 0) { toast.error('Pick at least one section to include.'); return; } setLoading(true); try { // FormData isn't required (this is a JSON body), but we DO need // to forward the X-Port-Id header so the server-side resolver // knows which port's data to use. apiFetch is JSON-only and // doesn't expose the raw response body; we need the buffer here // so do a raw fetch with the same header convention. const headers = new Headers({ 'Content-Type': 'application/json' }); if (typeof window !== 'undefined') { const slug = window.location.pathname.split('/').filter(Boolean)[0]; if (slug && slug !== 'login' && slug !== 'portal' && slug !== 'api') { const portId = await resolvePortIdFromSlug(slug); if (portId) headers.set('X-Port-Id', portId); } } const res = await fetch('/api/v1/reports/generate', { method: 'POST', headers, body: JSON.stringify({ title: title.trim() || 'Dashboard report', config: { kind: 'dashboard', widgetIds: selected, }, }), }); if (!res.ok) { const text = await res.text(); throw new Error(text || `Export failed (${res.status})`); } const blob = await res.blob(); const filename = title.trim().replace(/[\\/]/g, '_') + '.pdf'; triggerBlobDownload(blob, filename); toast.success('Report downloaded'); setOpen(false); } catch (err) { toast.error(err instanceof Error ? err.message : 'Export failed'); } finally { setLoading(false); } } return ( <> Export dashboard as PDF Pick which sections to include and set a title. The PDF inherits the active port's logo and primary color.
setTitle(e.target.value)} />
{PDF_DASHBOARD_WIDGETS.map((w) => ( ))}
); }