safeer-pdf-generator
Version:
Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery
239 lines (232 loc) • 7.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.modernTemplate = void 0;
const guards_js_1 = require("../utils/guards.js");
/**
* Modern template with clean, professional design
* Features: gradient header bar, card-style info section, modern typography
*/
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} ${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 = (0, guards_js_1.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 };
};
exports.modernTemplate = modernTemplate;
//# sourceMappingURL=modernTemplate.js.map