Implemented data export
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
import { jsPDF } from 'jspdf';
|
||||
import autoTable from 'jspdf-autotable';
|
||||
|
||||
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: jsPDF, 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.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.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 function exportToPdf({ title, subtitle, filename, columns, data }: ExportDataOptions): void {
|
||||
if (!data || data.length === 0) return;
|
||||
|
||||
// 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`);
|
||||
}
|
||||
Reference in New Issue
Block a user