Files
cpas/pdf/template.js

554 lines
20 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Inline base64 logo — avoids Puppeteer needing a live HTTP request to /static/
const LOGO_B64 = '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';
const TIERS = [
{ min: 0, max: 4, label: 'Tier 01 — Elite Standing', color: '#16a34a', bg: '#f0fdf4' },
{ min: 5, max: 9, label: 'Tier 1 — Realignment', color: '#854d0e', bg: '#fefce8' },
{ min: 10, max: 14, label: 'Tier 2 — Administrative Lockdown', color: '#b45309', bg: '#fff7ed' },
{ min: 15, max: 19, label: 'Tier 3 — Verification', color: '#c2410c', bg: '#fff7ed' },
{ min: 20, max: 24, label: 'Tier 4 — Risk Mitigation', color: '#b91c1c', bg: '#fef2f2' },
{ min: 25, max: 29, label: 'Tier 5 — Final Decision', color: '#991b1b', bg: '#fef2f2' },
{ min: 30, max: 999, label: 'Tier 6 — Separation', color: '#ffffff', bg: '#7f1d1d' },
];
function getTier(pts) {
return TIERS.find(t => pts >= t.min && pts <= t.max) || TIERS[0];
}
function fmt(d) {
if (!d) return '—';
return new Date(d + 'T12:00:00').toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
timeZone: 'America/Chicago',
});
}
function fmtDT(d, t) { return t ? `${fmt(d)} at ${t}` : fmt(d); }
function field(label, value) {
if (!value) return '';
return `
<div class="field">
<div class="field-label">${label}</div>
<div class="field-value">${value}</div>
</div>`;
}
function buildHtml(v, score) {
const priorPts = score.active_points || 0;
const priorTier = getTier(priorPts);
const newTotal = priorPts + v.points;
const newTier = getTier(newTotal);
const escalated = priorTier.label !== newTier.label;
const genAt = new Date().toLocaleString('en-US', {
timeZone: 'America/Chicago', dateStyle: 'full', timeStyle: 'short',
});
const docId = `CPAS-${v.id.toString().padStart(5, '0')}`;
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, 'Segoe UI', Arial, sans-serif;
font-size: 13px;
color: #1a1a2e;
background: #fff;
line-height: 1.5;
}
/* ── HEADER ── */
.header {
background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 60%, #16213e 100%);
padding: 24px 36px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 3px solid #d4af37;
}
.header-left { display: flex; align-items: center; gap: 16px; }
.logo { height: 36px; }
.header-title {
font-size: 18px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.3px;
}
.header-sub {
font-size: 11px;
color: #94a3b8;
margin-top: 3px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.header-right { text-align: right; }
.doc-id {
font-size: 13px;
font-weight: 700;
color: #d4af37;
letter-spacing: 0.5px;
}
.doc-meta {
font-size: 10px;
color: #64748b;
margin-top: 4px;
}
/* ── CONFIDENTIAL BANNER ── */
.confidential-bar {
background: #fef2f2;
border-bottom: 1px solid #fecaca;
padding: 7px 36px;
font-size: 11px;
font-weight: 700;
color: #991b1b;
letter-spacing: 0.8px;
text-transform: uppercase;
text-align: center;
}
/* ── BODY WRAPPER ── */
.body { padding: 28px 36px; }
/* ── SECTION ── */
.section { margin-bottom: 24px; }
.section-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.section-title {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: #64748b;
}
.section-rule {
flex: 1;
height: 1px;
background: #e2e8f0;
}
/* ── FIELD GRID ── */
.field-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 32px;
}
.field-grid.single { grid-template-columns: 1fr; }
.field { padding: 0; }
.field-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #94a3b8;
margin-bottom: 2px;
}
.field-value {
font-size: 13px;
color: #1e293b;
font-weight: 500;
}
.field-value.prominent {
font-size: 15px;
font-weight: 700;
color: #0f172a;
}
/* ── DETAIL BOX ── */
.detail-box {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-left: 4px solid #667eea;
border-radius: 6px;
padding: 14px 16px;
margin-top: 12px;
font-size: 12px;
color: #374151;
line-height: 1.6;
}
.detail-box-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #94a3b8;
margin-bottom: 6px;
}
/* ── SCORE CARD ── */
.score-card {
display: flex;
align-items: center;
gap: 0;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
margin-top: 4px;
}
.score-cell {
flex: 1;
padding: 18px 16px;
text-align: center;
border-right: 1px solid #e2e8f0;
}
.score-cell:last-child { border-right: none; }
.score-cell.operator {
flex: 0 0 48px;
font-size: 24px;
font-weight: 200;
color: #cbd5e1;
}
.score-num {
font-size: 32px;
font-weight: 800;
line-height: 1;
}
.score-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #94a3b8;
margin-top: 4px;
}
.tier-badge {
display: inline-block;
margin-top: 8px;
padding: 3px 10px;
border-radius: 12px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.3px;
}
/* ── POINTS PILL ── */
.points-pill {
display: inline-flex;
align-items: center;
gap: 10px;
background: #fffbeb;
border: 2px solid #d4af37;
border-radius: 8px;
padding: 12px 24px;
margin-bottom: 16px;
}
.points-pill-num {
font-size: 42px;
font-weight: 900;
color: #d4af37;
line-height: 1;
}
.points-pill-label {
font-size: 12px;
color: #92400e;
line-height: 1.4;
}
.points-pill-label strong { display: block; font-size: 14px; }
/* ── ESCALATION ALERT ── */
.escalation-alert {
background: #fef9c3;
border: 1.5px solid #eab308;
border-radius: 8px;
padding: 12px 16px;
margin-top: 14px;
font-size: 12px;
color: #713f12;
display: flex;
align-items: center;
gap: 10px;
}
.escalation-icon { font-size: 18px; }
/* ── TIER TABLE ── */
.tier-table { width: 100%; border-collapse: collapse; }
.tier-table th {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #94a3b8;
text-align: left;
padding: 6px 12px;
border-bottom: 2px solid #e2e8f0;
}
.tier-table td {
padding: 7px 12px;
font-size: 12px;
border-bottom: 1px solid #f1f5f9;
}
.tier-table tr.current-tier td {
background: #fffbeb;
font-weight: 700;
}
.tier-dot {
display: inline-block;
width: 8px; height: 8px;
border-radius: 50%;
margin-right: 6px;
vertical-align: middle;
}
/* ── NOTICE ── */
.notice {
background: #eff6ff;
border-left: 4px solid #3b82f6;
border-radius: 0 6px 6px 0;
padding: 12px 16px;
font-size: 11.5px;
color: #1e40af;
line-height: 1.6;
}
/* ── SIGNATURE ── */
.sig-intro {
font-size: 11.5px;
color: #475569;
line-height: 1.7;
margin-bottom: 28px;
}
.sig-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
.sig-block { }
.sig-line {
border-bottom: 1.5px solid #334155;
margin-bottom: 8px;
min-height: 52px;
}
.sig-line-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #64748b;
}
.sig-date-line {
border-bottom: 1.5px solid #334155;
margin-bottom: 8px;
margin-top: 20px;
min-height: 36px;
}
/* ── FOOTER BAR ── */
.footer-bar {
margin-top: 32px;
padding: 10px 0 0;
border-top: 1px solid #e2e8f0;
font-size: 10px;
color: #94a3b8;
display: flex;
justify-content: space-between;
}
</style>
</head>
<body>
<div class="header">
<div class="header-left">
<img src="data:image/png;base64,${LOGO_B64}" class="logo" />
<div>
<div class="header-title">CPAS Violation Record</div>
<div class="header-sub">Comprehensive Professional Accountability System</div>
</div>
</div>
<div class="header-right">
<div class="doc-id">${docId}</div>
<div class="doc-meta">Generated ${genAt}</div>
</div>
</div>
<div class="confidential-bar">⚑ Confidential — Authorized HR &amp; Management Use Only</div>
<div class="body">
<!-- Employee -->
<div class="section">
<div class="section-header">
<div class="section-title">Employee Information</div>
<div class="section-rule"></div>
</div>
<div class="field-grid">
<div class="field">
<div class="field-label">Employee Name</div>
<div class="field-value prominent">${v.employee_name}</div>
</div>
<div class="field">
<div class="field-label">Department</div>
<div class="field-value">${v.department || '—'}</div>
</div>
<div class="field">
<div class="field-label">Supervisor</div>
<div class="field-value">${v.supervisor || '—'}</div>
</div>
<div class="field">
<div class="field-label">Witness / Documenting Officer</div>
<div class="field-value">${v.witness_name || '—'}</div>
</div>
</div>
</div>
<!-- Violation -->
<div class="section">
<div class="section-header">
<div class="section-title">Violation Details</div>
<div class="section-rule"></div>
</div>
<div class="field-grid">
<div class="field">
<div class="field-label">Violation</div>
<div class="field-value prominent">${v.violation_name}</div>
</div>
<div class="field">
<div class="field-label">Category</div>
<div class="field-value">${v.category}</div>
</div>
<div class="field">
<div class="field-label">Incident Date / Time</div>
<div class="field-value">${fmtDT(v.incident_date, v.incident_time)}</div>
</div>
<div class="field">
<div class="field-label">Submitted By</div>
<div class="field-value">${v.submitted_by || 'System'}</div>
</div>
${v.location ? `
<div class="field" style="grid-column: 1 / -1;">
<div class="field-label">Location / Context</div>
<div class="field-value">${v.location}</div>
</div>` : ''}
</div>
${v.details ? `
<div class="detail-box">
<div class="detail-box-label">Incident Notes</div>
${v.details}
</div>` : ''}
</div>
<!-- Points -->
<div class="section">
<div class="section-header">
<div class="section-title">CPAS Point Assessment</div>
<div class="section-rule"></div>
</div>
<div class="points-pill">
<div class="points-pill-num">${v.points}</div>
<div class="points-pill-label">
<strong>Points Assessed</strong>
This violation
</div>
</div>
<div class="score-card">
<div class="score-cell">
<div class="score-num" style="color:${priorTier.color};">${priorPts}</div>
<div class="score-label">Prior Active Points</div>
<span class="tier-badge" style="background:${priorTier.bg}; color:${priorTier.color};">
${priorTier.label}
</span>
</div>
<div class="score-cell operator">+</div>
<div class="score-cell">
<div class="score-num" style="color:#d4af37;">${v.points}</div>
<div class="score-label">This Violation</div>
</div>
<div class="score-cell operator">=</div>
<div class="score-cell">
<div class="score-num" style="color:${newTier.color};">${newTotal}</div>
<div class="score-label">New Active Total</div>
<span class="tier-badge" style="background:${newTier.bg}; color:${newTier.color};">
${newTier.label}
</span>
</div>
</div>
${escalated ? `
<div class="escalation-alert">
<span class="escalation-icon">⚠</span>
<div>
<strong>Tier Escalation:</strong>
This violation advances the employee from <strong>${priorTier.label}</strong>
to <strong>${newTier.label}</strong>.
</div>
</div>` : ''}
</div>
<!-- Tier Reference -->
<div class="section">
<div class="section-header">
<div class="section-title">CPAS Tier Reference</div>
<div class="section-rule"></div>
</div>
<table class="tier-table">
<thead>
<tr>
<th>Points</th>
<th>Tier &amp; Standing</th>
</tr>
</thead>
<tbody>
${TIERS.map(t => {
const active = newTotal >= t.min && newTotal <= t.max;
const range = t.min === 30 ? '30+' : `${t.min}${t.max}`;
return `<tr class="${active ? 'current-tier' : ''}">
<td>${active ? '▶ ' : ''}${range}</td>
<td>
<span class="tier-dot" style="background:${t.color === '#ffffff' ? t.bg : t.color};"></span>
${t.label}
${active ? '<strong> ← Current</strong>' : ''}
</td>
</tr>`;
}).join('')}
</tbody>
</table>
</div>
<!-- Notice -->
<div class="notice" style="margin-bottom:24px;">
<strong>Employee Notice:</strong> CPAS points remain active for a rolling 90-day period from the date of each incident.
Accumulation of points may result in tier escalation and associated consequences as outlined in the Employee Handbook.
The employee may submit a written response within 5 business days of receiving this document.
</div>
<!-- Signatures -->
<div class="section">
<div class="section-header">
<div class="section-title">Acknowledgement &amp; Signatures</div>
<div class="section-rule"></div>
</div>
<p class="sig-intro">
By signing below, the employee acknowledges receipt of this violation record.
Acknowledgement does not imply agreement with the violation as documented.
</p>
<div class="sig-grid">
<div class="sig-block">
<div class="sig-line"></div>
<div class="sig-line-label">Employee Signature</div>
<div class="sig-date-line"></div>
<div class="sig-line-label">Date</div>
</div>
<div class="sig-block">
<div class="sig-line"></div>
<div class="sig-line-label">Supervisor / Documenting Officer Signature</div>
<div class="sig-date-line"></div>
<div class="sig-line-label">Date</div>
</div>
</div>
</div>
<div class="footer-bar">
<span>${docId} &nbsp;·&nbsp; ${v.employee_name} &nbsp;·&nbsp; Incident: ${v.incident_date}</span>
<span>Message Point Media — Internal Use Only</span>
</div>
</div>
</body>
</html>`;
}
module.exports = buildHtml;