Replace the repetitive "Actionable Issues" label with the more constructive "Key Actions" in the feedback report and PDF export. Remove the uppercase CSS class so the heading renders in sentence case. Update empty-state text to "No key actions identified." Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
No EOL
11 KiB
TypeScript
Executable file
202 lines
No EOL
11 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 { ChannelIcon } from './icons/ChannelIcon';
|
|
|
|
interface PDFReportProps {
|
|
campaignName: string;
|
|
proofs: any[];
|
|
}
|
|
|
|
const formatFeedbackTextForPDF = (text: string): React.ReactNode => {
|
|
if (!text) return null;
|
|
|
|
// Normalize HTML tags and bullet characters
|
|
let normalizedText = text
|
|
.replace(/<\/li>/gi, '\n')
|
|
.replace(/<\/ul>/gi, '\n')
|
|
.replace(/<ul[^>]*>/gi, '')
|
|
.replace(/<li[^>]*>/gi, '• ')
|
|
.replace(/<[^>]+>/g, '')
|
|
.replace(/\s*•\s*/g, '\n• ')
|
|
.replace(/\n{2,}/g, '\n')
|
|
.trim();
|
|
|
|
const lines = normalizedText.split('\n').filter(line => line.trim());
|
|
|
|
const bulletLines: string[] = [];
|
|
const introLines: string[] = [];
|
|
|
|
lines.forEach(line => {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith('•')) {
|
|
bulletLines.push(trimmed.replace(/^•\s*/, '').trim());
|
|
} else if (trimmed) {
|
|
if (bulletLines.length === 0) {
|
|
introLines.push(trimmed);
|
|
} else {
|
|
const lastBullet = bulletLines.pop();
|
|
if (lastBullet) {
|
|
bulletLines.push(`${lastBullet} ${trimmed}`);
|
|
} else {
|
|
bulletLines.push(trimmed);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return (
|
|
<>
|
|
{introLines.length > 0 && (
|
|
<p style={{ margin: '0 0 8px 0' }}>{introLines.join(' ')}</p>
|
|
)}
|
|
{bulletLines.length > 0 && (
|
|
<ul style={{ margin: 0, paddingLeft: '18px', listStyleType: 'disc' }}>
|
|
{bulletLines.map((item, index) => (
|
|
<li key={index} style={{ marginBottom: '4px' }}>{item}</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
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, borderRadius: '9999px', display: 'inline-block', minWidth: '50px', paddingLeft: '12px', paddingRight: '12px', paddingTop: '0', paddingBottom: '13px', textAlign: 'center', color: textColor, 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: 'Channel Best Practices Agent', review: feedback.channelBestPracticesAgentReview, icon: <ChannelIcon style={{height: '24px', width: '24px'}} /> },
|
|
{ title: 'Channel Tech Specs Agent', review: feedback.channelTechSpecsAgentReview, 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} • {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>
|
|
)}
|
|
<div style={{ fontSize: '14px', lineHeight: 1.5, margin: 0 }}>{formatFeedbackTextForPDF(feedback.leadAgentSummary)}</div>
|
|
</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>
|
|
<div style={{ fontSize: '13px', lineHeight: 1.5, margin: '0 0 10px 0', borderTop: '1px solid #eee', paddingTop: '10px' }}>{formatFeedbackTextForPDF(review.feedback)}</div>
|
|
{review.issues && review.issues.length > 0 && (
|
|
<div>
|
|
<h5 style={{ fontSize: '13px', fontWeight: 'bold', margin: '0 0 5px 0' }}>Key Actions:</h5>
|
|
<ul style={{ margin: 0, paddingLeft: '18px', fontSize: '12px', lineHeight: 1.5, listStyleType: 'disc' }}>
|
|
{review.issues.map((issue, i) => <li key={i} style={{ marginBottom: '4px' }}>{issue}</li>)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}; |