import React, { useState, useMemo, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import { ResultRow, DetailedQaReport, QaSectionResult } from '../types';
import { DownloadIcon, CheckCircleIcon, XCircleIcon, EyeIcon } from './Icons';
import jsPDF from 'jspdf';
import { Document, Packer, Paragraph, TextRun, HeadingLevel, Table, TableRow, TableCell, WidthType, AlignmentType, BorderStyle } from 'docx';
// This tells TypeScript that `Papa` is available on the global window object.
declare const Papa: any;
interface ResultsTableProps {
results: ResultRow[];
}
type SortConfig = {
key: keyof ResultRow;
direction: 'ascending' | 'descending';
} | null;
const QAReportModal: React.FC<{ report: DetailedQaReport; onClose: () => void }> = ({ report, onClose }) => {
const Section: React.FC<{ title: string; data: QaSectionResult }> = ({ title, data }) => (
{data.pass
?
:
}
{title}
Grade: {data.grade}
Pass: {data.pass ? 'Yes' : 'No'}
{/* Enhanced Content Display */}
{data.detailedAssessment && (
Detailed Assessment:
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
}}
>
{data.detailedAssessment}
)}
{data.keyStrengths && data.keyStrengths.length > 0 && (
Key Strengths:
{data.keyStrengths.map((strength, i) => - {strength}
)}
)}
{data.recommendations && data.recommendations.length > 0 && (
Recommendations:
{data.recommendations.map((rec, i) => - {rec}
)}
)}
{data.explanations && (
Explanations:
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
}}
>
{data.explanations}
)}
Errors:
{data.errors.map((err, i) => - {err}
)}
{data.corrected && (
Corrected Content:
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
h1: ({ children }) => {children}
,
h2: ({ children }) => {children}
,
h3: ({ children }) => {children}
,
}}
>
{data.corrected}
)}
{/* Raw Content for complete transparency */}
{data.rawContent && (
)}
);
const OverallSection: React.FC<{ data: DetailedQaReport['overall'] }> = ({ data }) => (
{data.pass
?
:
}
Overall Assessment
Grade: {data.grade}
Pass: {data.pass ? 'Yes' : 'No'}
Primary Issue:
{data.primaryIssue}
{/* Enhanced Overall Content Display */}
{data.detailedAssessment && (
Detailed Assessment:
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
}}
>
{data.detailedAssessment}
)}
{data.keyStrengths && data.keyStrengths.length > 0 && (
Key Strengths:
{data.keyStrengths.map((strength, i) => - {strength}
)}
)}
{data.recommendations && data.recommendations.length > 0 && (
Recommendations:
{data.recommendations.map((rec, i) => - {rec}
)}
)}
{data.explanations && (
Explanations:
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
}}
>
{data.explanations}
)}
{/* Raw Content for complete transparency */}
{data.rawContent && (
)}
);
const AdditionalSections: React.FC<{ sections: DetailedQaReport['additionalSections'] }> = ({ sections }) => {
if (!sections) return null;
return (
Additional QA Sections
{Object.entries(sections).map(([sectionName, sectionData]) => (
{sectionName}
{children}
,
ul: ({ children }) => ,
ol: ({ children }) => {children}
,
li: ({ children }) => {children},
code: ({ children }) => {children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
}}
>
{sectionData.content}
))}
);
};
return (
e.stopPropagation()}>
Complete QA Report Details
{/* Complete Raw Report */}
{report.completeRawReport && (
Complete Raw QA Report
{report.completeRawReport}
)}
);
};
const ResultsTable: React.FC = ({ results }) => {
const [sortConfig, setSortConfig] = useState(null);
const [selectedReport, setSelectedReport] = useState(null);
const [showDownloadMenu, setShowDownloadMenu] = useState(false);
const downloadMenuRef = useRef(null);
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (downloadMenuRef.current && !downloadMenuRef.current.contains(event.target as Node)) {
setShowDownloadMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleDownloadCSV = () => {
if (results.length === 0) return;
const csvData = results.map(row => {
const report = row.detailedQaReport;
return {
'URL': row.URL,
'Page': row.Page,
'Keywords': row.Keywords,
'Original Title': row.Recommended_Title,
'Original H1': row.Recommended_H1,
'Original Copy': row.Copy,
'Internal Links': row.Internal_Links,
'Generated Title': row.generatedTitle,
'Generated H1': row.generatedH1,
'Generated Meta': row.generatedMeta,
'Generated Copy': row.generatedCopy,
'Overall Pass': report?.overall.pass,
'Overall Grade': report?.overall.grade,
'Overall Primary Issue': report?.overall.primaryIssue,
'Overall Detailed Assessment': report?.overall.detailedAssessment,
'Overall Key Strengths': report?.overall.keyStrengths?.join('; '),
'Overall Recommendations': report?.overall.recommendations?.join('; '),
'Overall Explanations': report?.overall.explanations,
'Additional QA Sections': report?.additionalSections ?
Object.entries(report.additionalSections)
.map(([name, data]) => `${name}: ${data.content.replace(/\r?\n|\r/g, ' ')}`) // Sanitize newlines
.join(' | ') : '',
'Complete Raw QA Report': report?.completeRawReport?.replace(/\r?\n|\r/g, ' ') || row.qaReport.replace(/\r?\n|\r/g, ' '), // Sanitize newlines
};
});
const csv = Papa.unparse(csvData);
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'ace_copywriting_results.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setShowDownloadMenu(false);
};
const handleDownloadPDF = () => {
if (results.length === 0) return;
const pdf = new jsPDF();
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const margin = 20;
const maxLineWidth = pageWidth - (margin * 2);
let currentY = margin;
const lineHeight = 6;
const sectionSpacing = 10;
// Helper function to add text with word wrapping and automatic page breaks
const addWrappedText = (text: string, x: number, y: number, maxWidth: number, fontSize: number = 12): number => {
pdf.setFontSize(fontSize);
const lines = pdf.splitTextToSize(text, maxWidth);
let currentLineY = y;
for (let i = 0; i < lines.length; i++) {
// Check if we need a new page for this line
if (currentLineY + lineHeight > pageHeight - margin) {
pdf.addPage();
currentLineY = margin;
}
pdf.text(lines[i], x, currentLineY);
currentLineY += lineHeight;
}
return currentLineY;
};
// Helper function to ensure minimum space on page for section headers
const ensureMinSpace = (minHeight: number = 40): number => {
if (currentY + minHeight > pageHeight - margin) {
pdf.addPage();
return margin;
}
return currentY;
};
// Title
pdf.setFontSize(20);
pdf.setFont('helvetica', 'bold');
pdf.text('ACE Copywriting Pipeline Results', margin, currentY);
currentY += 15;
// Summary
pdf.setFontSize(12);
pdf.setFont('helvetica', 'normal');
const totalResults = results.length;
const passedResults = results.filter(r => r.overallPass).length;
const summaryText = `Generated ${totalResults} results | ${passedResults} passed QA | ${totalResults - passedResults} failed QA`;
currentY = addWrappedText(summaryText, margin, currentY, maxLineWidth);
currentY += sectionSpacing;
// Results
results.forEach((row, index) => {
// Estimate space needed for this entry based on content length
const estimatedHeight = 50 + // Base height for headers and metadata
Math.ceil((row.generatedTitle?.length || 0) / 80) * 6 + // Title
Math.ceil((row.generatedH1?.length || 0) / 80) * 6 + // H1
Math.ceil((row.generatedMeta?.length || 0) / 80) * 6 + // Meta
Math.ceil((row.generatedCopy?.length || 0) / 100) * 5; // Copy (smaller font)
// Ensure minimum space for section header
currentY = ensureMinSpace(40);
// Page/URL Header
pdf.setFontSize(14);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText(`${index + 1}. ${row.Page || 'Page'} (${row.URL})`, margin, currentY, maxLineWidth, 14);
currentY += 5;
// Keywords
pdf.setFontSize(10);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(`Keywords: ${row.Keywords}`, margin, currentY, maxLineWidth, 10);
currentY += 3;
// Overall QA Status
pdf.setFont('helvetica', 'bold');
if (row.overallPass) {
pdf.setTextColor(0, 128, 0);
} else {
pdf.setTextColor(255, 0, 0);
}
currentY = addWrappedText(`Overall QA: ${row.overallPass ? 'PASS' : 'FAIL'} (${row.overallGrade})`, margin, currentY, maxLineWidth, 10);
pdf.setTextColor(0, 0, 0);
currentY += 5;
// Generated Content
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Generated Title:', margin, currentY, maxLineWidth, 10);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(row.generatedTitle || '', margin, currentY, maxLineWidth, 9);
currentY += 3;
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Generated H1:', margin, currentY, maxLineWidth, 10);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(row.generatedH1 || '', margin, currentY, maxLineWidth, 9);
currentY += 3;
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Generated Meta:', margin, currentY, maxLineWidth, 10);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(row.generatedMeta || '', margin, currentY, maxLineWidth, 9);
currentY += 3;
// Generated Copy (full content)
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Generated Copy:', margin, currentY, maxLineWidth, 10);
pdf.setFont('helvetica', 'normal');
// Add the full copy content with proper spacing
if (row.generatedCopy) {
currentY = addWrappedText(row.generatedCopy, margin, currentY, maxLineWidth, 9);
}
// Add QA Details if available
if (row.detailedQaReport) {
const report = row.detailedQaReport;
currentY = ensureMinSpace(60); // Ensure space for QA header + at least one section
currentY += 5;
pdf.setFontSize(12);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('QA Report Details:', margin + 5, currentY, maxLineWidth - 5, 12);
currentY += 2;
const addDetailedSection = (title: string, section: QaSectionResult | DetailedQaReport['overall']) => {
const isOverall = 'primaryIssue' in section;
currentY = ensureMinSpace(30);
pdf.setFontSize(10);
pdf.setFont('helvetica', 'bold');
if (section.pass) {
pdf.setTextColor(0, 128, 0); // Green for PASS
} else {
pdf.setTextColor(255, 0, 0); // Red for FAIL
}
currentY = addWrappedText(`${title}: ${section.pass ? 'PASS' : 'FAIL'} (Grade: ${section.grade})`, margin + 5, currentY, maxLineWidth - 5, 10);
pdf.setTextColor(0, 0, 0); // Reset color
pdf.setFont('helvetica', 'normal');
if (isOverall && section.primaryIssue) {
currentY = addWrappedText(`Primary Issue: ${section.primaryIssue}`, margin + 10, currentY, maxLineWidth - 10, 8);
}
if (!isOverall && section.errors.length > 0) {
currentY = addWrappedText(`Errors: ${section.errors.join(', ')}`, margin + 10, currentY, maxLineWidth - 10, 8);
}
if (section.detailedAssessment) {
currentY = ensureMinSpace(20);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Detailed Assessment:', margin + 10, currentY, maxLineWidth - 10, 8);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(section.detailedAssessment, margin + 15, currentY, maxLineWidth - 15, 7);
}
if (section.keyStrengths && section.keyStrengths.length > 0) {
currentY = ensureMinSpace(20);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Key Strengths:', margin + 10, currentY, maxLineWidth - 10, 8);
pdf.setFont('helvetica', 'normal');
section.keyStrengths.forEach(strength => {
currentY = addWrappedText(`• ${strength}`, margin + 15, currentY, maxLineWidth - 15, 7);
});
}
if (section.recommendations && section.recommendations.length > 0) {
currentY = ensureMinSpace(20);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Recommendations:', margin + 10, currentY, maxLineWidth - 10, 8);
pdf.setFont('helvetica', 'normal');
section.recommendations.forEach(rec => {
currentY = addWrappedText(`• ${rec}`, margin + 15, currentY, maxLineWidth - 15, 7);
});
}
if (section.explanations) {
currentY = ensureMinSpace(20);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Explanations:', margin + 10, currentY, maxLineWidth - 10, 8);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(section.explanations, margin + 15, currentY, maxLineWidth - 15, 7);
}
if (!isOverall && section.corrected) {
currentY = ensureMinSpace(20);
pdf.setFont('helvetica', 'italic');
currentY = addWrappedText(`Correction/Analysis: ${section.corrected}`, margin + 10, currentY, maxLineWidth - 10, 8);
pdf.setFont('helvetica', 'normal');
}
currentY += 4;
};
addDetailedSection('Overall Assessment', report.overall);
// Add Additional Sections if available
if (report.additionalSections) {
currentY = ensureMinSpace(30);
pdf.setFontSize(10);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Additional QA Sections:', margin + 5, currentY, maxLineWidth - 5, 10);
currentY += 2;
Object.entries(report.additionalSections).forEach(([sectionName, sectionData]) => {
currentY = ensureMinSpace(25);
pdf.setFontSize(9);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText(`${sectionName}:`, margin + 10, currentY, maxLineWidth - 10, 9);
pdf.setFont('helvetica', 'normal');
currentY = addWrappedText(sectionData.content, margin + 15, currentY, maxLineWidth - 15, 7);
currentY += 2;
});
}
// Add Complete Raw Report
if (report.completeRawReport) {
currentY = ensureMinSpace(30);
pdf.setFontSize(10);
pdf.setFont('helvetica', 'bold');
currentY = addWrappedText('Complete Raw QA Report:', margin + 5, currentY, maxLineWidth - 5, 10);
currentY += 2;
pdf.setFontSize(7);
pdf.setFont('helvetica', 'normal');
pdf.setTextColor(100, 100, 100); // Lighter text for raw content
currentY = addWrappedText(report.completeRawReport, margin + 10, currentY, maxLineWidth - 10, 7);
pdf.setTextColor(0, 0, 0); // Reset color
}
}
currentY += sectionSpacing * 2; // Extra spacing between entries
});
// Footer on last page
pdf.setFontSize(8);
pdf.setTextColor(128, 128, 128);
pdf.text('Generated by ACE Copywriting Pipeline', margin, pageHeight - 10);
// Save the PDF
pdf.save('ace_copywriting_results.pdf');
setShowDownloadMenu(false);
};
const handleDownloadJSON = () => {
if (results.length === 0) return;
const jsonData = {
metadata: {
generatedAt: new Date().toISOString(),
totalResults: results.length,
passedResults: results.filter(r => r.overallPass).length,
failedResults: results.filter(r => !r.overallPass).length
},
results: results.map(row => ({
url: row.URL,
page: row.Page,
keywords: row.Keywords,
original: {
title: row.Recommended_Title,
h1: row.Recommended_H1,
copy: row.Copy,
internalLinks: row.Internal_Links
},
generated: {
title: row.generatedTitle,
h1: row.generatedH1,
meta: row.generatedMeta,
copy: row.generatedCopy
},
qa: {
overallPass: row.overallPass,
overallGrade: row.overallGrade,
sections: {
title: {
pass: row.detailedQaReport?.title.pass,
grade: row.detailedQaReport?.title.grade,
errors: row.detailedQaReport?.title.errors
},
meta: {
pass: row.detailedQaReport?.meta.pass,
grade: row.detailedQaReport?.meta.grade,
errors: row.detailedQaReport?.meta.errors
},
h1: {
pass: row.detailedQaReport?.h1.pass,
grade: row.detailedQaReport?.h1.grade,
errors: row.detailedQaReport?.h1.errors
},
copy: {
pass: row.detailedQaReport?.copy.pass,
grade: row.detailedQaReport?.copy.grade,
errors: row.detailedQaReport?.copy.errors
}
},
fullReport: row.qaReport
}
}))
};
const jsonString = JSON.stringify(jsonData, null, 2);
const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'ace_copywriting_results.json');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setShowDownloadMenu(false);
};
const handleDownloadDOCX = async () => {
if (results.length === 0) return;
// Helper function to clean HTML tags and format text
const cleanText = (text: string): string => {
if (!text) return '';
return text
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/\*\*([^*]+)\*\*/g, '$1') // Remove markdown bold
.replace(/\*([^*]+)\*/g, '$1') // Remove markdown italic
.replace(/---/g, '') // Remove markdown separators
.replace(/\n\s*\n/g, '\n') // Remove extra line breaks
.trim();
};
// Helper function to split text into paragraphs
const splitIntoParagraphs = (text: string): string[] => {
const cleaned = cleanText(text);
return cleaned
.split(/\n+/)
.map(p => p.trim())
.filter(p => p.length > 0);
};
const children: any[] = [];
// Title
children.push(
new Paragraph({
text: "ACE Copywriting Pipeline Results",
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
spacing: { after: 400 }
})
);
// Summary
const totalResults = results.length;
const passedResults = results.filter(r => r.overallPass).length;
const summaryText = `Generated ${totalResults} results | ${passedResults} passed QA | ${totalResults - passedResults} failed QA`;
children.push(
new Paragraph({
text: summaryText,
spacing: { after: 400 }
})
);
// Results
results.forEach((row, index) => {
// Page/URL Header
children.push(
new Paragraph({
text: `${index + 1}. ${row.Page || 'Page'} (${row.URL})`,
heading: HeadingLevel.HEADING_2,
spacing: { before: 400, after: 200 }
})
);
// Keywords
children.push(
new Paragraph({
text: `Keywords: ${row.Keywords}`,
spacing: { after: 200 }
})
);
// Overall QA Status
children.push(
new Paragraph({
children: [
new TextRun({
text: `Overall QA: ${row.overallPass ? 'PASS' : 'FAIL'} (${row.overallGrade})`,
color: row.overallPass ? '008000' : 'FF0000',
bold: true
})
],
spacing: { after: 200 }
})
);
// Generated Content
children.push(
new Paragraph({
text: "Generated Title:",
heading: HeadingLevel.HEADING_3,
spacing: { before: 300, after: 100 }
}),
new Paragraph({
text: cleanText(row.generatedTitle || ''),
spacing: { after: 200 }
}),
new Paragraph({
text: "Generated H1:",
heading: HeadingLevel.HEADING_3,
spacing: { before: 300, after: 100 }
}),
new Paragraph({
text: cleanText(row.generatedH1 || ''),
spacing: { after: 200 }
}),
new Paragraph({
text: "Generated Meta:",
heading: HeadingLevel.HEADING_3,
spacing: { before: 300, after: 100 }
}),
new Paragraph({
text: cleanText(row.generatedMeta || ''),
spacing: { after: 200 }
}),
new Paragraph({
text: "Generated Copy:",
heading: HeadingLevel.HEADING_3,
spacing: { before: 300, after: 100 }
})
);
// Handle generated copy with proper paragraph breaks
if (row.generatedCopy) {
const copyParagraphs = splitIntoParagraphs(row.generatedCopy);
copyParagraphs.forEach(paragraph => {
children.push(
new Paragraph({
text: paragraph,
spacing: { after: 150 }
})
);
});
}
// QA Details if available
if (row.detailedQaReport) {
const report = row.detailedQaReport;
children.push(
new Paragraph({
text: "QA Report Details:",
heading: HeadingLevel.HEADING_3,
spacing: { before: 400, after: 200 }
})
);
const addDetailedSection = (title: string, section: QaSectionResult | DetailedQaReport['overall']) => {
const isOverall = 'primaryIssue' in section;
children.push(
new Paragraph({
children: [
new TextRun({
text: `${title}: ${section.pass ? 'PASS' : 'FAIL'} (Grade: ${section.grade})`,
color: section.pass ? '008000' : 'FF0000',
bold: true
})
],
heading: HeadingLevel.HEADING_4,
spacing: { before: 300, after: 100 }
})
);
if (isOverall && section.primaryIssue) {
children.push(new Paragraph({ text: `Primary Issue: ${cleanText(section.primaryIssue)}`, spacing: { after: 100 } }));
}
if (!isOverall && section.errors && section.errors.length > 0) {
children.push(new Paragraph({ text: `Errors: ${section.errors.join(', ')}`, spacing: { after: 100 } }));
}
if (section.detailedAssessment) {
children.push(new Paragraph({ children: [new TextRun({ text: "Detailed Assessment:", bold: true })], spacing: { after: 50 } }));
splitIntoParagraphs(section.detailedAssessment).forEach(p => children.push(new Paragraph({ text: p, spacing: { after: 100 } })));
}
if (section.keyStrengths && section.keyStrengths.length > 0) {
children.push(new Paragraph({ children: [new TextRun({ text: "Key Strengths:", bold: true })], spacing: { after: 50 } }));
section.keyStrengths.forEach(s => children.push(new Paragraph({ text: `• ${cleanText(s)}`, spacing: { after: 50 }, indent: { left: 400 } })));
}
if (section.recommendations && section.recommendations.length > 0) {
children.push(new Paragraph({ children: [new TextRun({ text: "Recommendations:", bold: true })], spacing: { after: 50 } }));
section.recommendations.forEach(r => children.push(new Paragraph({ text: `• ${cleanText(r)}`, spacing: { after: 50 }, indent: { left: 400 } })));
}
if (section.explanations) {
children.push(new Paragraph({ children: [new TextRun({ text: "Explanations:", bold: true })], spacing: { after: 50 } }));
splitIntoParagraphs(section.explanations).forEach(p => children.push(new Paragraph({ text: p, spacing: { after: 100 } })));
}
if (!isOverall && section.corrected) {
children.push(new Paragraph({ children: [new TextRun({ text: "Corrected Content:", bold: true })], spacing: { after: 50 } }));
splitIntoParagraphs(section.corrected).forEach(p => children.push(new Paragraph({ text: p, spacing: { after: 100 } })));
}
};
addDetailedSection('Overall Assessment', report.overall);
// Add Additional Sections
if (report.additionalSections && Object.keys(report.additionalSections).length > 0) {
children.push(new Paragraph({ text: "Additional QA Sections", heading: HeadingLevel.HEADING_4, spacing: { before: 300, after: 100 } }));
Object.entries(report.additionalSections).forEach(([name, data]) => {
children.push(new Paragraph({ children: [new TextRun({ text: `${name}:`, bold: true })], spacing: { after: 50 } }));
splitIntoParagraphs(data.content).forEach(p => children.push(new Paragraph({ text: p, spacing: { after: 100 }, indent: { left: 400 } })));
});
}
// Add Complete Raw Report
if (report.completeRawReport) {
children.push(new Paragraph({ text: "Complete Raw QA Report", heading: HeadingLevel.HEADING_4, spacing: { before: 300, after: 100 } }));
splitIntoParagraphs(report.completeRawReport).forEach(p => {
children.push(new Paragraph({
children: [ new TextRun({ text: p, size: 16 }) ], // smaller font
spacing: { after: 100 }
}));
});
}
}
// Add spacing between entries
children.push(
new Paragraph({
text: "",
spacing: { after: 400 }
})
);
});
// Footer
children.push(
new Paragraph({
children: [
new TextRun({
text: "Generated by ACE Copywriting Pipeline",
color: '808080',
size: 16
})
],
alignment: AlignmentType.CENTER,
spacing: { before: 400 }
})
);
const doc = new Document({
sections: [{
properties: {},
children: children
}]
});
const blob = await Packer.toBlob(doc);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'ace_copywriting_results.docx');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
setShowDownloadMenu(false);
};
const sortedResults = useMemo(() => {
let sortableItems = [...results];
if (sortConfig !== null) {
sortableItems.sort((a, b) => {
const key = sortConfig.key;
const valA = a[key as keyof typeof a];
const valB = b[key as keyof typeof b];
if (typeof valA === 'boolean' && typeof valB === 'boolean') {
if (valA === valB) return 0;
return sortConfig.direction === 'ascending' ? (valA ? -1 : 1) : (valA ? 1 : -1);
}
if (valA < valB) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (valA > valB) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [results, sortConfig]);
const requestSort = (key: keyof ResultRow) => {
let direction: 'ascending' | 'descending' = 'ascending';
if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {
direction = 'descending';
}
setSortConfig({ key, direction });
};
const getSortIndicator = (key: keyof ResultRow) => {
if (!sortConfig || sortConfig.key !== key) {
return ' ↕';
}
return sortConfig.direction === 'ascending' ? ' ▲' : ' ▼';
};
if (results.length === 0) {
return (
Generated Content
No results to display yet. Process a file to see the output here.
)
}
return (
<>
Generated Content
{showDownloadMenu && (
)}
requestSort('URL')}>URL{getSortIndicator('URL')} |
Generated Title |
Generated H1 |
requestSort('overallPass')}>Overall Pass{getSortIndicator('overallPass')} |
Details |
{sortedResults
.filter((row, index, arr) => arr.findIndex(r => r.id === row.id) === index) // Remove any runtime duplicates
.map((row) => (
{row.URL} |
{row.generatedTitle} |
{row.generatedH1} |
{row.overallPass
?
:
}
|
|
))}
{selectedReport && setSelectedReport(null)} />}
>
);
};
export default ResultsTable;