Datavision / frontend /src /utils /pdfExport.ts
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
6.29 kB
/**
* PDF Export Utility
* Uses html2pdf.js to export HTML elements to PDF
*/
import html2pdf from 'html2pdf.js';
export interface PDFExportOptions {
filename?: string;
margin?: number;
pageFormat?: 'a4' | 'letter' | 'legal';
orientation?: 'portrait' | 'landscape';
quality?: number;
scale?: number;
}
const defaultOptions: PDFExportOptions = {
filename: 'report',
margin: 10,
pageFormat: 'a4',
orientation: 'portrait',
quality: 0.98,
scale: 2,
};
/**
* Export a DOM element to PDF
* @param elementId - ID of the element to export
* @param options - PDF export options
*/
export async function exportToPDF(
elementId: string,
options: PDFExportOptions = {}
): Promise<void> {
const element = document.getElementById(elementId);
if (!element) {
console.error(`Element with ID "${elementId}" not found`);
throw new Error(`Element with ID "${elementId}" not found`);
}
const finalOptions = { ...defaultOptions, ...options };
const timestamp = new Date().toISOString().split('T')[0];
// Show PDF-only header before export
const pdfHeader = element.querySelector('.pdf-only-header') as HTMLElement;
if (pdfHeader) {
pdfHeader.style.display = 'block';
}
// Store original styles to restore later
const originalBg = element.style.backgroundColor;
const originalColor = element.style.color;
// Apply light theme for PDF (dark text on white background)
element.style.backgroundColor = '#ffffff';
element.style.color = '#1a1a2e';
// Apply dark text to all text elements for PDF visibility
const textElements = element.querySelectorAll('*');
const originalStyles: Map<Element, { color: string; backgroundColor: string }> = new Map();
textElements.forEach((el) => {
const htmlEl = el as HTMLElement;
originalStyles.set(el, {
color: htmlEl.style.color,
backgroundColor: htmlEl.style.backgroundColor
});
// Force dark text colors for PDF readability
const computedColor = window.getComputedStyle(htmlEl).color;
if (computedColor && (computedColor.includes('255') || computedColor.includes('rgb(255') || computedColor.includes('rgba(255'))) {
htmlEl.style.color = '#1a1a2e';
}
// Handle specific element types
if (htmlEl.tagName === 'H1' || htmlEl.tagName === 'H2' || htmlEl.tagName === 'H3') {
htmlEl.style.color = '#1a1a2e';
}
if (htmlEl.tagName === 'P' || htmlEl.tagName === 'SPAN' || htmlEl.tagName === 'DIV') {
if (!htmlEl.style.color || htmlEl.style.color.includes('var(')) {
htmlEl.style.color = '#2d2d3a';
}
}
// Make backgrounds white/transparent
const computedBg = window.getComputedStyle(htmlEl).backgroundColor;
if (computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent') {
if (computedBg.includes('rgb(') && !computedBg.includes('255, 255, 255')) {
htmlEl.style.backgroundColor = 'rgba(240, 240, 245, 0.5)';
}
}
});
const pdfOptions = {
margin: finalOptions.margin,
filename: `${finalOptions.filename}_${timestamp}.pdf`,
image: { type: 'jpeg' as const, quality: finalOptions.quality },
html2canvas: {
scale: finalOptions.scale,
useCORS: true,
logging: false,
backgroundColor: '#ffffff'
},
jsPDF: {
unit: 'mm' as const,
format: finalOptions.pageFormat,
orientation: finalOptions.orientation
},
pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
};
try {
await html2pdf().set(pdfOptions).from(element).save();
} catch (error) {
console.error('PDF export failed:', error);
throw error;
} finally {
// Restore original styles
element.style.backgroundColor = originalBg;
element.style.color = originalColor;
textElements.forEach((el) => {
const htmlEl = el as HTMLElement;
const original = originalStyles.get(el);
if (original) {
htmlEl.style.color = original.color;
htmlEl.style.backgroundColor = original.backgroundColor;
}
});
// Hide PDF-only header after export
if (pdfHeader) {
pdfHeader.style.display = 'none';
}
}
}
/**
* Export element to PDF with print-friendly styling
* Temporarily applies print styles before exporting
*/
export async function exportWithPrintStyles(
elementId: string,
options: PDFExportOptions = {}
): Promise<void> {
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with ID "${elementId}" not found`);
}
// Add print class for styling
element.classList.add('pdf-export-mode');
// Wait for styles to apply
await new Promise(resolve => setTimeout(resolve, 100));
try {
await exportToPDF(elementId, options);
} finally {
// Remove print class
element.classList.remove('pdf-export-mode');
}
}
/**
* Generate a formatted date range string for reports
*/
export function getReportDateRange(period: string): string {
const now = new Date();
let startDate: Date;
switch (period) {
case '7d':
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
break;
case '30d':
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
break;
case '90d':
startDate = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
break;
case '1y':
startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
break;
default:
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
}
const formatDate = (d: Date) => d.toLocaleDateString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
return `${formatDate(startDate)} - ${formatDate(now)}`;
}