Files
germantax-e2e-mrfz66zi/packages/functions/src/templates/shared.ts
2026-07-11 08:20:01 +02:00

65 lines
2.0 KiB
TypeScript

export const escapeHtml = (s: unknown): string =>
String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
export const formatEur = (n: unknown): string => {
const num = typeof n === 'number' ? n : Number(n ?? 0)
if (!Number.isFinite(num)) return '—'
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(num)
}
export const formatDateDe = (d: string | Date): string => {
const date = typeof d === 'string' ? new Date(d) : d
return new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: 'long',
year: 'numeric',
}).format(date)
}
export const baseDocument = (title: string, body: string): string => `<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>${escapeHtml(title)}</title>
<style>
@page { size: A4; margin: 24mm 20mm; }
body { font-family: 'Helvetica Neue', Arial, sans-serif; font-size: 11pt; color: #111; line-height: 1.5; }
h1 { font-size: 16pt; margin: 0 0 4mm 0; }
h2 { font-size: 13pt; margin: 8mm 0 2mm 0; }
.meta { color: #555; font-size: 10pt; margin-bottom: 8mm; }
.section { margin: 6mm 0; }
ul { margin: 2mm 0; padding-left: 6mm; }
table { border-collapse: collapse; width: 100%; }
td, th { border: 1px solid #ddd; padding: 2mm 3mm; text-align: left; vertical-align: top; }
.signature { margin-top: 16mm; display: flex; gap: 12mm; }
.signature .line { flex: 1; border-top: 1px solid #333; padding-top: 2mm; font-size: 10pt; }
</style>
</head>
<body>
${body}
</body>
</html>`
export const renderMembers = (
members: Array<{ displayName: string; role: string; shares: number | null }>,
): string => {
const rows = members
.map(
(m) =>
`<tr><td>${escapeHtml(m.displayName)}</td><td>${escapeHtml(m.role)}</td><td>${m.shares ?? '—'}</td></tr>`,
)
.join('')
return `<table>
<thead><tr><th>Name</th><th>Rolle</th><th>Anteile</th></tr></thead>
<tbody>${rows}</tbody>
</table>`
}