Severian commited on
Commit
02b914b
·
verified ·
1 Parent(s): 159f9c9

Update components/ResultsTable.tsx

Browse files
Files changed (1) hide show
  1. components/ResultsTable.tsx +234 -0
components/ResultsTable.tsx CHANGED
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown';
4
  import { ResultRow, DetailedQaReport, QaSectionResult } from '../types';
5
  import { DownloadIcon, CheckCircleIcon, XCircleIcon, EyeIcon } from './Icons';
6
  import jsPDF from 'jspdf';
 
7
 
8
  // This tells TypeScript that `Papa` is available on the global window object.
9
  declare const Papa: any;
@@ -390,6 +391,233 @@ const ResultsTable: React.FC<ResultsTableProps> = ({ results }) => {
390
  setShowDownloadMenu(false);
391
  };
392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  const sortedResults = useMemo(() => {
394
  let sortableItems = [...results];
395
  if (sortConfig !== null) {
@@ -477,6 +705,12 @@ const ResultsTable: React.FC<ResultsTableProps> = ({ results }) => {
477
  >
478
  💾 Download JSON
479
  </button>
 
 
 
 
 
 
480
  </div>
481
  </div>
482
  )}
 
4
  import { ResultRow, DetailedQaReport, QaSectionResult } from '../types';
5
  import { DownloadIcon, CheckCircleIcon, XCircleIcon, EyeIcon } from './Icons';
6
  import jsPDF from 'jspdf';
7
+ import { Document, Packer, Paragraph, TextRun, HeadingLevel, Table, TableRow, TableCell, WidthType, AlignmentType, BorderStyle } from 'docx';
8
 
9
  // This tells TypeScript that `Papa` is available on the global window object.
10
  declare const Papa: any;
 
391
  setShowDownloadMenu(false);
392
  };
393
 
394
+ const handleDownloadDOCX = async () => {
395
+ if (results.length === 0) return;
396
+
397
+ // Helper function to clean HTML tags and format text
398
+ const cleanText = (text: string): string => {
399
+ if (!text) return '';
400
+ return text
401
+ .replace(/<[^>]*>/g, '') // Remove HTML tags
402
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // Remove markdown bold
403
+ .replace(/\*([^*]+)\*/g, '$1') // Remove markdown italic
404
+ .replace(/---/g, '') // Remove markdown separators
405
+ .replace(/\n\s*\n/g, '\n') // Remove extra line breaks
406
+ .trim();
407
+ };
408
+
409
+ // Helper function to split text into paragraphs
410
+ const splitIntoParagraphs = (text: string): string[] => {
411
+ const cleaned = cleanText(text);
412
+ return cleaned
413
+ .split(/\n+/)
414
+ .map(p => p.trim())
415
+ .filter(p => p.length > 0);
416
+ };
417
+
418
+ const children: any[] = [];
419
+
420
+ // Title
421
+ children.push(
422
+ new Paragraph({
423
+ text: "ACE Copywriting Pipeline Results",
424
+ heading: HeadingLevel.HEADING_1,
425
+ alignment: AlignmentType.CENTER,
426
+ spacing: { after: 400 }
427
+ })
428
+ );
429
+
430
+ // Summary
431
+ const totalResults = results.length;
432
+ const passedResults = results.filter(r => r.overallPass).length;
433
+ const summaryText = `Generated ${totalResults} results | ${passedResults} passed QA | ${totalResults - passedResults} failed QA`;
434
+
435
+ children.push(
436
+ new Paragraph({
437
+ text: summaryText,
438
+ spacing: { after: 400 }
439
+ })
440
+ );
441
+
442
+ // Results
443
+ results.forEach((row, index) => {
444
+ // Page/URL Header
445
+ children.push(
446
+ new Paragraph({
447
+ text: `${index + 1}. ${row.Page || 'Page'} (${row.URL})`,
448
+ heading: HeadingLevel.HEADING_2,
449
+ spacing: { before: 400, after: 200 }
450
+ })
451
+ );
452
+
453
+ // Keywords
454
+ children.push(
455
+ new Paragraph({
456
+ text: `Keywords: ${row.Keywords}`,
457
+ spacing: { after: 200 }
458
+ })
459
+ );
460
+
461
+ // Overall QA Status
462
+ children.push(
463
+ new Paragraph({
464
+ children: [
465
+ new TextRun({
466
+ text: `Overall QA: ${row.overallPass ? 'PASS' : 'FAIL'} (${row.overallGrade})`,
467
+ color: row.overallPass ? '008000' : 'FF0000',
468
+ bold: true
469
+ })
470
+ ],
471
+ spacing: { after: 200 }
472
+ })
473
+ );
474
+
475
+ // Generated Content
476
+ children.push(
477
+ new Paragraph({
478
+ text: "Generated Title:",
479
+ heading: HeadingLevel.HEADING_3,
480
+ spacing: { before: 300, after: 100 }
481
+ }),
482
+ new Paragraph({
483
+ text: cleanText(row.generatedTitle || ''),
484
+ spacing: { after: 200 }
485
+ }),
486
+ new Paragraph({
487
+ text: "Generated H1:",
488
+ heading: HeadingLevel.HEADING_3,
489
+ spacing: { before: 300, after: 100 }
490
+ }),
491
+ new Paragraph({
492
+ text: cleanText(row.generatedH1 || ''),
493
+ spacing: { after: 200 }
494
+ }),
495
+ new Paragraph({
496
+ text: "Generated Meta:",
497
+ heading: HeadingLevel.HEADING_3,
498
+ spacing: { before: 300, after: 100 }
499
+ }),
500
+ new Paragraph({
501
+ text: cleanText(row.generatedMeta || ''),
502
+ spacing: { after: 200 }
503
+ }),
504
+ new Paragraph({
505
+ text: "Generated Copy:",
506
+ heading: HeadingLevel.HEADING_3,
507
+ spacing: { before: 300, after: 100 }
508
+ })
509
+ );
510
+
511
+ // Handle generated copy with proper paragraph breaks
512
+ if (row.generatedCopy) {
513
+ const copyParagraphs = splitIntoParagraphs(row.generatedCopy);
514
+ copyParagraphs.forEach(paragraph => {
515
+ children.push(
516
+ new Paragraph({
517
+ text: paragraph,
518
+ spacing: { after: 150 }
519
+ })
520
+ );
521
+ });
522
+ }
523
+
524
+ // QA Details if available
525
+ if (row.detailedQaReport) {
526
+ children.push(
527
+ new Paragraph({
528
+ text: "QA Report Details:",
529
+ heading: HeadingLevel.HEADING_3,
530
+ spacing: { before: 400, after: 200 }
531
+ })
532
+ );
533
+
534
+ const addQaSection = (title: string, section: QaSectionResult) => {
535
+ children.push(
536
+ new Paragraph({
537
+ children: [
538
+ new TextRun({
539
+ text: `${title}: ${section.pass ? 'PASS' : 'FAIL'} (Grade: ${section.grade})`,
540
+ color: section.pass ? '008000' : 'FF0000',
541
+ bold: true
542
+ })
543
+ ],
544
+ heading: HeadingLevel.HEADING_4,
545
+ spacing: { before: 300, after: 100 }
546
+ }),
547
+ new Paragraph({
548
+ text: `Errors: ${section.errors.join(', ')}`,
549
+ spacing: { after: 100 }
550
+ })
551
+ );
552
+
553
+ // Handle correction/analysis with proper formatting
554
+ if (section.corrected && section.corrected.trim()) {
555
+ const correctedText = cleanText(section.corrected);
556
+ if (correctedText && correctedText !== 'Content analysis not available.') {
557
+ children.push(
558
+ new Paragraph({
559
+ children: [
560
+ new TextRun({
561
+ text: `Correction/Analysis: ${correctedText}`,
562
+ italics: true
563
+ })
564
+ ],
565
+ spacing: { after: 200 }
566
+ })
567
+ );
568
+ }
569
+ }
570
+ };
571
+
572
+ addQaSection('Title', row.detailedQaReport.title);
573
+ addQaSection('Meta', row.detailedQaReport.meta);
574
+ addQaSection('H1', row.detailedQaReport.h1);
575
+ addQaSection('Copy', row.detailedQaReport.copy);
576
+ }
577
+
578
+ // Add spacing between entries
579
+ children.push(
580
+ new Paragraph({
581
+ text: "",
582
+ spacing: { after: 400 }
583
+ })
584
+ );
585
+ });
586
+
587
+ // Footer
588
+ children.push(
589
+ new Paragraph({
590
+ children: [
591
+ new TextRun({
592
+ text: "Generated by ACE Copywriting Pipeline",
593
+ color: '808080',
594
+ size: 16
595
+ })
596
+ ],
597
+ alignment: AlignmentType.CENTER,
598
+ spacing: { before: 400 }
599
+ })
600
+ );
601
+
602
+ const doc = new Document({
603
+ sections: [{
604
+ properties: {},
605
+ children: children
606
+ }]
607
+ });
608
+
609
+ const blob = await Packer.toBlob(doc);
610
+ const url = URL.createObjectURL(blob);
611
+ const link = document.createElement('a');
612
+ link.setAttribute('href', url);
613
+ link.setAttribute('download', 'ace_copywriting_results.docx');
614
+ document.body.appendChild(link);
615
+ link.click();
616
+ document.body.removeChild(link);
617
+ URL.revokeObjectURL(url);
618
+ setShowDownloadMenu(false);
619
+ };
620
+
621
  const sortedResults = useMemo(() => {
622
  let sortableItems = [...results];
623
  if (sortConfig !== null) {
 
705
  >
706
  💾 Download JSON
707
  </button>
708
+ <button
709
+ onClick={handleDownloadDOCX}
710
+ className="w-full text-left px-4 py-2 text-white hover:bg-gray-600 flex items-center gap-2"
711
+ >
712
+ 📝 Download DOCX
713
+ </button>
714
  </div>
715
  </div>
716
  )}