UNPKG

safeer-pdf-generator

Version:

Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery

235 lines (228 loc) 7.78 kB
import { getNestedProperty } from '../utils/guards.js'; /** * Modern template with clean, professional design * Features: gradient header bar, card-style info section, modern typography */ export const modernTemplate = (params) => { const { title, data = [], columns = [], locale = 'en', userInfo = {}, infoSection = [], pdfFilteredColumns = [], translationFn: t = (key) => key, customCss = '', customHtml = {}, } = params; const isRTL = locale === 'ar'; const today = new Date(); const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const datePart = `${months[today.getMonth()]} ${today.getDate()}, ${today.getFullYear()}`; let hours = today.getHours(); const minutes = today.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12 || 12; const timePart = `${hours}:${minutes.toString().padStart(2, '0')} ${ampm}`; const header = ` <div style=" width: 100%; box-sizing: border-box; display: flex; flex-direction: ${isRTL ? 'row-reverse' : 'row'}; align-items: center; justify-content: space-between; background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%); padding: 8px 16px; font-family: 'Segoe UI', 'Tajawal', Arial, sans-serif; "> <div style="flex: 0 0 auto;"> <img src="${userInfo.companyLogoBase64 || ''}" style="width: 90px; height: 40px; object-fit: contain;" /> </div> <div style="flex: 1; text-align: center; padding: 0 15px;"> <div style="font-size: 14px; font-weight: 600; color: #ffffff; letter-spacing: 0.5px;">${t(title)}</div> <div style="font-size: 10px; color: rgba(255,255,255,0.8);">${datePart} &nbsp; ${timePart}</div> </div> <div style="flex: 0 0 auto; font-size: 9px; color: rgba(255,255,255,0.9); text-align: ${isRTL ? 'left' : 'right'};"> <div>${userInfo.name || ''}</div> <div>${userInfo.email || ''}</div> </div> </div> `; const footer = ``; const filteredColumns = columns.filter(col => !pdfFilteredColumns.includes(col.key)); const renderInfoSection = () => { if (!infoSection || infoSection.length === 0) return ''; return ` <div class="info-card"> ${infoSection .map(item => ` <div class="info-chip"> <span class="info-chip-label">${t(item.label)}</span> <span class="info-chip-value">${item.value}</span> </div> `) .join('')} </div> `; }; const renderCell = (col, value) => { if (['image', 'logo', 'avatar', 'flag', 'icon'].includes(col.dataIndex) && value) { return `<img src="${value}" class="cell-img" alt="${col.title}" />`; } if (['isActive', 'isBlocked', 'isMain'].includes(col.dataIndex)) { const isTrue = !!value; return `<span class="badge ${isTrue ? 'badge-yes' : 'badge-no'}">${isTrue ? t('yes') : t('no')}</span>`; } return `${value || '—'}`; }; const renderTable = () => { if (!data || data.length === 0) { return `<div class="empty-state">${t('noData')}</div>`; } const visibleData = data.slice(0, 500); return ` <table class="modern-table"> <thead> <tr> ${filteredColumns .map(col => `<th>${col.title}</th>`) .join('')} </tr> </thead> <tbody> ${visibleData .map((item, idx) => ` <tr class="${idx % 2 === 0 ? 'row-even' : 'row-odd'}"> ${filteredColumns .map(col => { const value = getNestedProperty(item, col.dataIndex); return `<td>${renderCell(col, value)}</td>`; }) .join('')} </tr> `) .join('')} </tbody> </table> `; }; const html = ` <!DOCTYPE html> <html dir="${isRTL ? 'rtl' : 'ltr'}"> <head> <meta charset="UTF-8"> <title>${title}</title> <style> * { box-sizing: border-box; } body { font-family: 'Segoe UI', 'Tajawal', Arial, sans-serif; margin: 0; padding: 0; color: #1e293b; background: #ffffff; padding-bottom: 60px; } /* Info card */ .info-card { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 12px 0; padding: 10px 12px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; } .info-chip { display: flex; align-items: center; gap: 6px; padding: 4px 10px; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 11px; } .info-chip-label { font-weight: 600; color: #475569; } .info-chip-value { color: #1e293b; } /* Modern table */ .modern-table { width: 100%; border-collapse: collapse; font-size: 11px; margin: 0; } .modern-table thead tr { background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%); } .modern-table th { padding: 10px 8px; font-weight: 600; font-size: 11px; color: #334155; text-align: center; text-transform: uppercase; letter-spacing: 0.4px; border-bottom: 2px solid #cbd5e1; } .modern-table td { padding: 8px; text-align: center; vertical-align: middle; border-bottom: 1px solid #f1f5f9; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: normal; word-wrap: break-word; } .row-even { background: #ffffff; } .row-odd { background: #fafbfc; } .modern-table tbody tr:hover { background: #f0f7ff; } /* Badges */ .badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 10px; font-weight: 600; } .badge-yes { background: #dcfce7; color: #166534; } .badge-no { background: #fee2e2; color: #991b1b; } /* Media */ .cell-img { width: 36px; height: 36px; object-fit: contain; border-radius: 4px; display: block; margin: 0 auto; } /* Empty state */ .empty-state { text-align: center; padding: 40px 20px; font-size: 14px; color: #94a3b8; background: #f8fafc; border-radius: 6px; } /* Print */ @media print { .modern-table { -webkit-print-color-adjust: exact; print-color-adjust: exact; } .modern-table thead { display: table-header-group; } .modern-table tbody { display: table-row-group; } .modern-table tbody tr { page-break-inside: avoid; } } ${customCss} </style> </head> <body> ${renderInfoSection()} ${customHtml.beforeTable || ''} ${renderTable()} ${customHtml.afterTable || ''} </body> </html> `; return { html, header, footer }; }; //# sourceMappingURL=modernTemplate.js.map