Implemented data export

This commit is contained in:
2026-09-26 13:45:42 +02:00
parent 15a8d8f449
commit 2949d6218b
12 changed files with 10939 additions and 359 deletions
@@ -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`);
}