export interface ExportColumn { header: string; key: string; format?: (value: any, row: any) => string; } export interface ExportDataOptions { title: string; subtitle?: string; filename: string; columns: ExportColumn[]; data: any[]; } /** * Converts dataset into standard CSV string and triggers browser download */ export function exportToSpreadsheet({ filename, columns, data }: ExportDataOptions): void { if (!data || data.length === 0) return; const headers = columns.map((col) => `"${(col.header || '').replace(/"/g, '""')}"`).join(','); const rows = data.map((row) => columns .map((col) => { let val: any; if (col.format) { val = col.format(row[col.key], row); } else { val = row[col.key]; } if (val === null || val === undefined) val = ''; const strVal = String(val).replace(/"/g, '""'); return `"${strVal}"`; }) .join(',') ); const csvContent = '\uFEFF' + [headers, ...rows].join('\r\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', `${filename.replace(/\.csv$/i, '')}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } /** * Draws the high-visibility official PFM vector brand insignia onto the jsPDF document. */ function drawPfmLogo(doc: any, x: number, y: number, size: number): void { const scale = size / 64; doc.saveGraphicsState(); // Dark foundation disc doc.setFillColor(9, 13, 22); doc.circle(x + 32 * scale, y + 32 * scale, 31 * scale, 'F'); doc.setDrawColor(30, 41, 59); doc.setLineWidth(1 * scale); doc.circle(x + 32 * scale, y + 32 * scale, 30 * scale, 'S'); // Cyan/Blue Outer hexagonal shield ring doc.setDrawColor(59, 130, 246); doc.setLineWidth(1.2 * scale); (doc as any).polygon( [ { x: x + 32 * scale, y: y + 6.5 * scale }, { x: x + 53.5 * scale, y: y + 19 * scale }, { x: x + 56 * scale, y: y + 23.2 * scale }, { x: x + 56 * scale, y: y + 40.8 * scale }, { x: x + 53.5 * scale, y: y + 45 * scale }, { x: x + 32 * scale, y: y + 57.5 * scale }, { x: x + 27 * scale, y: y + 57.5 * scale }, { x: x + 5.5 * scale, y: y + 45 * scale }, { x: x + 3 * scale, y: y + 40.8 * scale }, { x: x + 3 * scale, y: y + 23.2 * scale }, { x: x + 5.5 * scale, y: y + 19 * scale }, { x: x + 27 * scale, y: y + 6.5 * scale }, ], 'S' ); // Top Facet - Indigo/Violet Governance Canopy doc.setFillColor(99, 102, 241); doc.triangle( x + 32 * scale, y + 9 * scale, x + 51.5 * scale, y + 20.2 * scale, x + 43 * scale, y + 25.2 * scale, 'F' ); doc.triangle( x + 32 * scale, y + 9 * scale, x + 43 * scale, y + 25.2 * scale, x + 21 * scale, y + 25.2 * scale, 'F' ); doc.triangle( x + 32 * scale, y + 9 * scale, x + 21 * scale, y + 25.2 * scale, x + 12.5 * scale, y + 20.2 * scale, 'F' ); // Left Facet - Electric Sapphire Capital Vault doc.setFillColor(37, 99, 235); doc.triangle( x + 10 * scale, y + 23 * scale, x + 19.2 * scale, y + 28.4 * scale, x + 19.2 * scale, y + 44.5 * scale, 'F' ); doc.triangle( x + 10 * scale, y + 23 * scale, x + 19.2 * scale, y + 44.5 * scale, x + 30 * scale, y + 50.8 * scale, 'F' ); // Right Facet - Cyan/Emerald Performance Wing doc.setFillColor(6, 182, 212); doc.triangle( x + 54 * scale, y + 23 * scale, x + 44.8 * scale, y + 28.4 * scale, x + 44.8 * scale, y + 44.5 * scale, 'F' ); doc.triangle( x + 54 * scale, y + 23 * scale, x + 44.8 * scale, y + 44.5 * scale, x + 34 * scale, y + 50.8 * scale, 'F' ); // Center Plate doc.setFillColor(15, 23, 42); doc.setDrawColor(51, 65, 85); (doc as any).polygon( [ { x: x + 22 * scale, y: y + 28.5 * scale }, { x: x + 32 * scale, y: y + 22.8 * scale }, { x: x + 42 * scale, y: y + 28.5 * scale }, { x: x + 42 * scale, y: y + 35.5 * scale }, { x: x + 32 * scale, y: y + 41.2 * scale }, { x: x + 22 * scale, y: y + 35.5 * scale }, ], 'FD' ); // Center Diamond Spark doc.setFillColor(255, 255, 255); doc.circle(x + 32 * scale, y + 32 * scale, 2.5 * scale, 'F'); doc.restoreGraphicsState(); } /** * Generates an executive-grade PDF document in LANDSCAPE mode with clear PFM logo visibility, * metadata header, and structured autoTable data. */ export async function exportToPdf({ title, subtitle, filename, columns, data }: ExportDataOptions): Promise { if (!data || data.length === 0) return; const [{ jsPDF }, { default: autoTable }] = await Promise.all([ import('jspdf'), import('jspdf-autotable'), ]); // Enforce Landscape orientation const doc = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4', }); const pageWidth = doc.internal.pageSize.getWidth(); const pageHeight = doc.internal.pageSize.getHeight(); const margin = 40; // Header background banner doc.setFillColor(13, 18, 31); doc.rect(0, 0, pageWidth, 72, 'F'); doc.setDrawColor(30, 41, 59); doc.setLineWidth(1); doc.line(0, 72, pageWidth, 72); // Draw PFM Logo with clear visibility const logoSize = 44; drawPfmLogo(doc, margin, 14, logoSize); // PFM Branding Text next to Logo doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'bold'); doc.setFontSize(14); doc.text('PFM Console', margin + logoSize + 12, 33); doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.setTextColor(56, 189, 248); // Cyan doc.text('POST-FUND MANAGEMENT SYSTEM • OFFICIAL REPORT', margin + logoSize + 12, 46); // Document Title & Subtitle in Header (Right aligned or center) doc.setFont('helvetica', 'bold'); doc.setFontSize(13); doc.setTextColor(248, 250, 252); doc.text(title, pageWidth - margin, 32, { align: 'right' }); doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.setTextColor(148, 163, 184); const nowStr = new Date().toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short', }); doc.text( `${subtitle || 'Authoritative Register Export'} • Generated: ${nowStr}`, pageWidth - margin, 46, { align: 'right' } ); // Prepare table headers and rows const tableHeaders = [columns.map((c) => c.header)]; const tableRows = data.map((row) => columns.map((col) => { let val: any; if (col.format) { val = col.format(row[col.key], row); } else { val = row[col.key]; } if (val === null || val === undefined) return '—'; return String(val); }) ); // Render autoTable in landscape (autoTable as any)(doc, { head: tableHeaders, body: tableRows, startY: 86, margin: { left: margin, right: margin, bottom: 45 }, theme: 'grid', styles: { fontSize: 8, cellPadding: 6, textColor: [30, 41, 59], lineColor: [226, 232, 240], lineWidth: 0.5, overflow: 'linebreak', }, headStyles: { fillColor: [15, 23, 42], textColor: [255, 255, 255], fontStyle: 'bold', fontSize: 8, }, alternateRowStyles: { fillColor: [248, 250, 252], }, didDrawPage: (hookData: any) => { // Footer on every page const currentPg = hookData.pageNumber; doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.setTextColor(100, 116, 139); doc.text( `Post Fund Management (PFM) • Confidential & Authoritative • Page ${currentPg}`, margin, pageHeight - 20 ); doc.text( `Total Records: ${data.length} • Landscape Master Report`, pageWidth - margin, pageHeight - 20, { align: 'right' } ); }, }); doc.save(`${filename.replace(/\.pdf$/i, '')}.pdf`); }