modcomms/frontend/components/PDFReport.tsx
2025-12-18 16:51:27 +00:00

150 lines
No EOL
9.4 KiB
TypeScript
Executable file

import React from 'react';
import type { AgentReview, SubReview, RagStatus, OverallStatus } from '../types';
import { BarclaysLogo } from './icons/BarclaysLogo';
import { OliverLogo } from './icons/OliverLogo';
import { LegalIcon } from './icons/LegalIcon';
import { BrandIcon } from './icons/BrandIcon';
import { CopyIcon } from './icons/CopyIcon';
import { ChannelIcon } from './icons/ChannelIcon';
interface PDFReportProps {
campaignName: string;
proofs: any[];
}
const RagStatusBadge: React.FC<{ status: RagStatus }> = ({ status }) => {
let bgColor = '#E5E7EB'; // Gray for Error
let textColor = '#1F2937';
switch (status) {
case 'Red': bgColor = '#FEE2E2'; textColor = '#991B1B'; break;
case 'Amber': bgColor = '#FEF3C7'; textColor = '#92400E'; break;
case 'Green': bgColor = '#D1FAE5'; textColor = '#065F46'; break;
}
return (
<span style={{ backgroundColor: bgColor, color: textColor, padding: '4px 12px', borderRadius: '9999px', fontSize: '12px', fontWeight: 'bold' }}>
{status}
</span>
);
};
export const PDFReport: React.FC<PDFReportProps> = ({ campaignName, proofs }) => {
const today = new Date().toLocaleDateString('en-GB', {
day: '2-digit',
month: 'long',
year: 'numeric',
});
const isCampaignReport = proofs.length > 1;
const singleProofName = !isCampaignReport && proofs.length > 0 ? proofs[0].proofName : '';
return (
<div style={{ width: '210mm', fontFamily: 'Arial, sans-serif', color: '#333333', background: '#FFFFFF' }}>
{/* --- Cover Page --- */}
<div style={{ width: '210mm', height: '297mm', display: 'flex', flexDirection: 'column', padding: '20mm', boxSizing: 'border-box', borderBottom: '1px solid #e5e5e5' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingBottom: '10mm', borderBottom: '1px solid #e5e5e5' }}>
<BarclaysLogo style={{ height: '50px', width: 'auto', color: '#001f5a' }} />
<OliverLogo style={{ height: '25px', width: 'auto' }} />
</div>
<div style={{ flexGrow: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
<h1 style={{ fontSize: '42px', color: '#001f5a', margin: '0 0 10px 0' }}>AI Compliance & Brand Report</h1>
<p style={{ fontSize: '18px', color: '#555', marginTop: '0' }}>
{isCampaignReport ? 'Campaign-Level Summary' : 'Single Proof Analysis'}
</p>
</div>
<div style={{ borderTop: '1px solid #e5e5e5', paddingTop: '10mm' }}>
<p style={{ margin: 0, fontSize: '14px' }}><strong>Campaign:</strong> {campaignName}</p>
{!isCampaignReport && <p style={{ margin: '5px 0 0 0', fontSize: '14px' }}><strong>Proof:</strong> {singleProofName}</p>}
<p style={{ margin: '5px 0 0 0', fontSize: '14px' }}><strong>Export Date:</strong> {today}</p>
</div>
</div>
{/* --- Table of Contents for Campaign Report --- */}
{isCampaignReport && (
<div style={{ width: '210mm', minHeight: '297mm', padding: '20mm', boxSizing: 'border-box', pageBreakBefore: 'always', borderBottom: '1px solid #e5e5e5' }}>
<h2 style={{ fontSize: '28px', color: '#001f5a', borderBottom: '2px solid #00a3e0', paddingBottom: '8px', marginBottom: '20px' }}>
Table of Contents
</h2>
<ul style={{ listStyle: 'none', padding: 0 }}>
{proofs.map((proof, index) => (
<li key={index} style={{ fontSize: '16px', padding: '10px 0', borderBottom: '1px dotted #ccc' }}>
{proof.proofName} - V{proof.versions[0]?.version || 1}
</li>
))}
</ul>
</div>
)}
{/* --- Proof Report Pages --- */}
{proofs.map((proof) => {
const version = proof.versions[0];
if (!version) return null;
const feedback: AgentReview = version.feedback;
const agentReviews = [
{ title: 'Legal Agent', review: feedback.legalAgentReview, icon: <LegalIcon style={{height: '24px', width: '24px'}} /> },
{ title: 'Brand Agent', review: feedback.brandAgentReview, icon: <BrandIcon style={{height: '24px', width: '24px'}} /> },
{ title: 'Tone Agent', review: feedback.toneAgentReview, icon: <CopyIcon style={{height: '24px', width: '24px'}} /> },
{ title: 'Channel Agent', review: feedback.channelAgentReview, icon: <ChannelIcon style={{height: '24px', width: '24px'}} /> },
];
return (
<div key={proof.proofName} style={{ width: '210mm', minHeight: '297mm', padding: '15mm', boxSizing: 'border-box', pageBreakBefore: 'always' }}>
{/* Proof Header */}
<div style={{ paddingBottom: '8px', borderBottom: '2px solid #00a3e0', marginBottom: '10mm' }}>
<h2 style={{ fontSize: '24px', color: '#001f5a', margin: 0 }}>{proof.proofName} - V{version.version}</h2>
<p style={{ fontSize: '14px', color: '#555', margin: '4px 0 0 0' }}>
{version.workfrontId} &bull; {proof.channel} / {proof.subChannel}
</p>
</div>
{/* Preview & Summary */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10mm', marginBottom: '10mm' }}>
<div style={{ border: '1px solid #ccc', padding: '4px', background: '#f8f8f8' }}>
<img src={version.proofPreviewUrl} alt="Proof Preview" style={{ width: '100%', objectFit: 'contain' }} />
</div>
<div style={{ backgroundColor: '#f4f6f8', padding: '6mm', borderRadius: '4px', borderLeft: `4px solid ${feedback.overallStatus === 'Passed' ? '#10B981' : '#EF4444'}`}}>
<h3 style={{ fontSize: '18px', margin: '0 0 8px 0', color: '#001f5a' }}>Overall Summary</h3>
<p style={{ fontSize: '14px', margin: '0 0 10px 0', fontWeight: 'bold' }}>Status: {feedback.overallStatus}</p>
{feedback.overallStatus === 'Requires Manual Legal Review' && (
<div style={{ backgroundColor: '#F3E8FF', border: '1px solid #D8B4FE', borderRadius: '4px', padding: '8px', marginBottom: '10px'}}>
<p style={{fontSize: '13px', margin: 0, fontWeight: 'bold', color: '#6B21A8'}}>Financial Promotion Detected:</p>
<p style={{fontSize: '12px', margin: '4px 0 0 0', color: '#6B21A8', fontStyle: 'italic'}}>"{feedback.financialPromotionReason}"</p>
</div>
)}
<p style={{ fontSize: '14px', lineHeight: 1.5, margin: 0 }}>{feedback.leadAgentSummary}</p>
</div>
</div>
{/* Detailed Agent Feedback */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8mm' }}>
{agentReviews.map(({ title, review, icon }) => (
<div key={title} style={{ border: '1px solid #e5e5e5', borderRadius: '4px', padding: '5mm', backgroundColor: '#fff', breakInside: 'avoid' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}>
<h4 style={{ fontSize: '16px', fontWeight: 'bold', color: '#001f5a', margin: 0, display: 'flex', alignItems: 'center', gap: '8px'}}>
<span style={{color: '#0070c0'}}>{icon}</span> {title}
</h4>
<RagStatusBadge status={review.ragStatus} />
</div>
<p style={{ fontSize: '13px', lineHeight: 1.5, margin: '0 0 10px 0', borderTop: '1px solid #eee', paddingTop: '10px' }}>{review.feedback}</p>
{review.issues && review.issues.length > 0 && (
<div>
<h5 style={{ fontSize: '13px', fontWeight: 'bold', margin: '0 0 5px 0' }}>Actionable Issues:</h5>
<ul style={{ margin: 0, paddingLeft: '18px', fontSize: '12px', lineHeight: 1.5 }}>
{review.issues.map((issue, i) => <li key={i} style={{ marginBottom: '4px' }}>{issue}</li>)}
</ul>
</div>
)}
</div>
))}
</div>
</div>
);
})}
</div>
);
};