import mjml2html from 'mjml' function getPath(data, path) { return String(path || '').split('.').filter(Boolean).reduce((value, key) => value?.[key], data) } export function escapeHtml(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", ''') } export function interpolate(input, data) { return String(input ?? '').replace(/{{\s*([\w.[\]-]+)\s*}}/g, (_, path) => { const normalized = path.replace(/\[(\d+)\]/g, '.$1') const value = getPath(data, normalized) return value == null ? '' : String(value) }) } function safeUrl(input, data) { const url = interpolate(input, data).trim() return /^(https?:|mailto:)/i.test(url) ? url : '#' } function renderTable(block, data) { const rows = getPath(data, block.props?.rowsPath) || [] const columns = Array.isArray(block.props?.columns) ? block.props.columns : [] if (!Array.isArray(rows)) return '' return `
${columns.map(c => ``).join('')}${rows.map(row => `${columns.map(c => ``).join('')}`).join('')}
${escapeHtml(c.label)}
${escapeHtml(getPath(row, c.key))}
` } function blockHtml(block, data) { const p = block.props || {} switch (block.type) { case 'heading': return `

${escapeHtml(interpolate(p.text, data))}

` case 'text': return `

${escapeHtml(interpolate(p.text, data))}

` case 'kpi': return `
${escapeHtml(interpolate(p.label, data))}${escapeHtml(interpolate(p.value, data))}
` case 'button': return `

${escapeHtml(interpolate(p.text, data))}

` case 'image': return `${escapeHtml(interpolate(p.alt, data))}` case 'divider': return '
' case 'table': return renderTable(block, data) default: return '' } } export function renderReportHtml(template, data = {}) { const blocks = template?.blocks || [] return ` ${blocks.map(block => blockHtml(block, data)).join('\n')}` } function emailBlock(block, data) { const p = block.props || {} switch (block.type) { case 'heading': return `${escapeHtml(interpolate(p.text, data))}` case 'text': return `${escapeHtml(interpolate(p.text, data))}` case 'kpi': return `${escapeHtml(interpolate(p.label, data))}${escapeHtml(interpolate(p.value, data))}` case 'button': return `${escapeHtml(interpolate(p.text, data))}` case 'image': return `` case 'divider': return '' default: return 'This block is available in report output only.' } } export function renderEmailHtml(template, data = {}) { const body = (template?.blocks || []).map(block => emailBlock(block, data)).join('\n') const mjml = `${body}` const result = mjml2html(mjml, { validationLevel: 'soft' }) if (result.errors?.length) console.warn('MJML warnings', result.errors) return result.html }