/** * 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 { 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 = 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 { 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)}`; }