/* eslint-disable max-lines */ // 77 lines of CSS + one letter's markup; splitting it into // letterhead/body/signature/footer makes "what does the letter look like" a five-file // question for no behavioural seam. Deliberate, not deferred. import { Component, DestroyRef, ElementRef, computed, effect, inject, input, linkedSignal, output, signal, viewChild, } from '@angular/core'; import { ButtonComponent } from '@shared/ui/atoms/button/button.component'; import { formatDatumNl } from '@shared/kernel/datum'; import { Paragraph } from '@shared/kernel/rich-text'; import { Brief, LetterBlock } from '@brief/domain/brief'; import { OrgTemplate } from '@brief/domain/org-template'; import { OrgTemplateTextField } from '@brief/domain/org-template.machine'; import { Diagnostic } from '@brief/domain/placeholders'; import { BlockDiffKind } from '@brief/domain/brief-diff'; import { LetterLineComponent } from './letter-line.component'; /** A run of consecutive lines to render together: a list (bullet/number) or a single plain line. */ type PreviewSegment = { readonly list: 'bullet' | 'number' | null; readonly items: readonly Paragraph[]; }; function groupParagraphs(paras: readonly Paragraph[]): PreviewSegment[] { const out: { list: 'bullet' | 'number' | null; items: Paragraph[] }[] = []; for (const para of paras) { const kind = para.list ?? null; const last = out[out.length - 1]; if (kind && last && last.list === kind) last.items.push(para); else out.push({ list: kind, items: [para] }); } return out; } /** A4 height in CSS px (1in = 96px = 25.4mm) — for the approximate page-break marks. */ const A4_HEIGHT_PX = (297 * 96) / 25.4; /** Organism: the letter as one surface — the org template's letterhead, signature and footer around the case-type template's sections. `editableRegions` picks who edits what: `'content'` hosts the editable letter-sections in place (drafter), `'none'` renders everything read-only (approver/locked, absorbs the old letter-preview), `'template'` reserves the org-identity regions for the admin editor. Letter typography/geometry come from the shared `public/letter.css` contract — the same file the backend preview renderer inlines. Each rendered line is its own `app-letter-line` (RD-26), replacing the outlet-template indirection that stood in for it. */ @Component({ selector: 'app-letter-canvas', imports: [ButtonComponent, LetterLineComponent], styles: [ ` :host { display: block; } .toolbar { display: flex; justify-content: space-between; align-items: center; gap: var(--rhc-space-max-sm); margin-block-end: var(--rhc-space-max-sm); } .zoom { display: flex; align-items: center; gap: var(--rhc-space-max-sm); } .zoom-pct { min-width: 3.5ch; text-align: center; color: var(--rhc-color-foreground-subtle); font-variant-numeric: tabular-nums; } /* Rejection-diff badge: a small pill above a changed/added block. */ .diff-block.diff-changed { border-inline-start: 3px solid var(--rhc-color-oranje-500); padding-inline-start: var(--rhc-space-max-sm); } .diff-badge { display: inline-block; margin-block-end: 1mm; padding: 0 1.5mm; border-radius: var(--rhc-border-radius-sm); font-size: 7.5pt; /* changed = dark text on oranje-500 (4.79:1); white on oranje-500 fails (3.23:1). */ color: var(--rhc-color-foreground-default); background: var(--rhc-color-oranje-500); } .diff-badge.added { /* added = white on groen-700 (6.4:1); dark text on any green fails 4.5:1 (axe). */ color: var(--rhc-color-wit); background: var(--rhc-color-groen-700); } /* Portal-side chrome around the letter surface (not part of the contract file). */ .surface { background: var(--rhc-color-grijs-100); border: var(--rhc-border-width-sm) solid var(--rhc-color-border-default); border-radius: var(--rhc-border-radius-md); padding: var(--rhc-space-max-lg); overflow-x: auto; } .surface .letter { box-shadow: 0 1px 4px rgb(0 0 0 / 0.15); /* token-ok: paper drop-shadow, not a palette colour */ } /* Admin edit-in-place (editableRegions='template'): the org-identity fields become controls styled to sit in the letter, with a visible editable affordance. */ .tmpl-input, .tmpl-textarea { font: inherit; color: inherit; width: 100%; box-sizing: border-box; background: var(--rhc-color-geel-100); border: var(--rhc-border-width-sm) dashed var(--rhc-color-border-strong); border-radius: var(--rhc-border-radius-sm); padding: 0.5mm 1mm; } .tmpl-textarea { resize: vertical; } .org-logo { max-height: 20mm; max-width: 60mm; margin-block-end: 3mm; } `, ], template: ` @if (editableRegions() !== 'template') {
}{{ orgTemplate().orgName }}
{{ orgTemplate().returnAddress }} } {{ recipientText() }}
{{ orgTemplate().signatureClosing }}
{{ orgTemplate().signatureName }}
{{ orgTemplate().signatureRole }}
}