From 56e63edcd64c1867fe0186d405721479c6645333 Mon Sep 17 00:00:00 2001 From: renee-png Date: Wed, 17 Jun 2026 12:16:16 -0400 Subject: [PATCH] Violation reports: replace fining stamp with bold red header line The diagonal semi-transparent "RECOMMENDED FOR FINING" stamp looked poor over the report content. Remove it (drawRecommendedForFiningStamp + both call sites) and instead print a bold red "RECOMMENDED FOR FINING" line just beneath the navy header bar on both the Summary and Timeline reports, shifting the owner block down to make room. Non-fining reports are unchanged. Co-Authored-By: Claude Opus 4.8 --- src/lib/violationPdfGenerator.js | 72 ++++++++++---------------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/lib/violationPdfGenerator.js b/src/lib/violationPdfGenerator.js index 8fb8f3c..9cdab7b 100644 --- a/src/lib/violationPdfGenerator.js +++ b/src/lib/violationPdfGenerator.js @@ -11,50 +11,20 @@ import { formatPhotoTimestamp, getPhotoDateFromUrl } from './exifUtils'; // Safe yield helper to prevent main thread blocking and ensure it resolves const safeYieldToMain = () => new Promise(resolve => setTimeout(resolve, 0)); -// Draws a diagonal red "RECOMMENDED FOR FINING" stamp across the current page -const drawRecommendedForFiningStamp = (doc) => { +// Draws a clean, bold red "RECOMMENDED FOR FINING" line just beneath the navy +// header bar (replaces the old diagonal stamp). Returns the y of the owner +// block so callers leave room for the banner only when it's shown. +const FINING_HEADER_COLOR = '#c0392b'; +const drawRecommendedForFiningHeader = (doc, margin) => { try { - const pageWidth = doc.internal.pageSize.getWidth(); - // Place stamp near the top-right of the page so it doesn't cover - // photo evidence in the middle/bottom of the page. - const stampW = Math.min(pageWidth - 160, 300); - const stampH = 56; - const cx = pageWidth - (stampW / 2) - 40; - const cy = 110; - - doc.saveGraphicsState && doc.saveGraphicsState(); - // Lighter opacity so underlying content remains fully readable - if (doc.setGState && doc.GState) { - try { doc.setGState(new doc.GState({ opacity: 0.55 })); } catch (e) {} - } - - // Rotate around center (jsPDF rotates around the x,y origin) - const angle = -14; - const rad = (angle * Math.PI) / 180; - const cos = Math.cos(rad); - const sin = Math.sin(rad); - - // Compute rotated rect corner so the rect is centered at (cx, cy) - const rx = cx - (stampW / 2) * cos + (stampH / 2) * sin; - const ry = cy - (stampW / 2) * sin - (stampH / 2) * cos; - - doc.setDrawColor(200, 0, 0); - doc.setTextColor(200, 0, 0); - doc.setLineWidth(2.5); - doc.roundedRect(rx, ry, stampW, stampH, 10, 10, 'S', angle); - doc.setFont('helvetica', 'bold'); - doc.setFontSize(20); - // Text position: center of stamp, jsPDF text rotates around the text origin - doc.text('RECOMMENDED FOR FINING', cx, cy + 7, { align: 'center', angle: -angle }); - - doc.restoreGraphicsState && doc.restoreGraphicsState(); - // Reset state - doc.setTextColor(0, 0, 0); - doc.setDrawColor(0, 0, 0); - doc.setLineWidth(1); + doc.setFontSize(12); + doc.setTextColor(FINING_HEADER_COLOR); + doc.text('RECOMMENDED FOR FINING', margin, 75); + // Reset to the standard header text colour for the owner block that follows. + doc.setTextColor('#2c3e50'); } catch (e) { - console.warn('Failed to draw recommended-for-fining stamp', e); + console.warn('Failed to draw recommended-for-fining header', e); } }; @@ -332,6 +302,12 @@ export const generateViolationStatusPDF = async (violations, settings = {}, onPr doc.text(`Generated: ${formatDateSafe(new Date())}`, pageWidth - margin, 45, { align: 'right' }); currentY = 80; + // Bold red "RECOMMENDED FOR FINING" header line (replaces the old stamp). + if (isRecommendedForFining(vData.status)) { + drawRecommendedForFiningHeader(doc, margin); + currentY = 96; + } + // Owner/Address doc.setFont(fonts.subHeader.name, fonts.subHeader.style); doc.setFontSize(14); doc.setTextColor(colors.text); doc.text(vData.owner || 'Unknown Owner', margin, currentY); @@ -491,10 +467,6 @@ export const generateViolationStatusPDF = async (violations, settings = {}, onPr doc.setFont(fonts.footer.name, fonts.footer.style); doc.setFontSize(fonts.footer.size); doc.setTextColor(colors.subHeader); doc.text(pageStr, pageWidth / 2, pageHeight - 15, { align: 'center' }); - if (isRecommendedForFining(vData.status)) { - drawRecommendedForFiningStamp(doc); - } - } catch (err) { errorCount++; if (errorCount <= 5) { @@ -562,6 +534,12 @@ export const generateViolationTimelinePDF = async (violations, settings = {}, on doc.text(`Generated: ${formatDateSafe(new Date())}`, pageWidth - margin, 45, { align: 'right' }); currentY = 80; + // Bold red "RECOMMENDED FOR FINING" header line (replaces the old stamp). + if (isRecommendedForFining(vData.status)) { + drawRecommendedForFiningHeader(doc, margin); + currentY = 96; + } + doc.setFont('helvetica', 'bold'); doc.setFontSize(14); doc.setTextColor('#2c3e50'); @@ -590,10 +568,6 @@ export const generateViolationTimelinePDF = async (violations, settings = {}, on doc.setTextColor('#34495e'); doc.text(pageStr, pageWidth / 2, pageHeight - 15, { align: 'center' }); - if (isRecommendedForFining(vData.status)) { - drawRecommendedForFiningStamp(doc); - } - } catch (e) { errorCount++; if (errorCount <= 5) console.error(`Error processing timeline page ${i}:`, e);