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 }) =>
    {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.recommendations && data.recommendations.length > 0 && (

    Recommendations:

    )} {data.explanations && (

    Explanations:

    {children}

    , ul: ({ children }) =>
      {children}
    , ol: ({ children }) =>
      {children}
    , li: ({ children }) =>
  • {children}
  • , code: ({ children }) => {children}, strong: ({ children }) => {children}, em: ({ children }) => {children}, }} > {data.explanations}
    )}

    Errors:

    {data.corrected && (

    Corrected Content:

    {children}

    , ul: ({ children }) =>
      {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 && (

    Raw QA Content:

    {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 }) =>
      {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.recommendations && data.recommendations.length > 0 && (

    Recommendations:

    )} {data.explanations && (

    Explanations:

    {children}

    , ul: ({ children }) =>
      {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 && (

    Raw Overall Content:

    {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 }) =>
      {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 && (
    )}
    {sortedResults .filter((row, index, arr) => arr.findIndex(r => r.id === row.id) === index) // Remove any runtime duplicates .map((row) => ( ))}
    requestSort('URL')}>URL{getSortIndicator('URL')} Generated Title Generated H1 requestSort('overallPass')}>Overall Pass{getSortIndicator('overallPass')} Details
    {row.URL} {row.generatedTitle} {row.generatedH1}
    {row.overallPass ? : }
    {selectedReport && setSelectedReport(null)} />} ); }; export default ResultsTable;