Guilherme Silberfarb Costa commited on
Commit
4fea248
·
1 Parent(s): 4cfa2b3

Replace PDF iframe preview with HTML report preview

Browse files
Files changed (2) hide show
  1. frontend/src/App.jsx +73 -12
  2. frontend/src/styles.css +80 -9
frontend/src/App.jsx CHANGED
@@ -264,6 +264,19 @@ function normalizeText(value) {
264
  .replace(/[^a-z0-9]+/g, '')
265
  }
266
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  function extractFieldHints(rawDescription) {
268
  const cleaned = String(rawDescription || '')
269
  .replace(/^\s*#\s*(?=<)/gm, '')
@@ -524,7 +537,7 @@ export default function App() {
524
  [calculators, activeId]
525
  )
526
  const targetOutput = result?.text_outputs?.[0] || null
527
- const previewPdfUrl = result?.pdf_url ? buildFileUrl(`${result.pdf_url}?download=0`) : ''
528
  const downloadPdfUrl = result?.pdf_url ? buildFileUrl(`${result.pdf_url}?download=1`) : ''
529
  const dataSubtitle = useMemo(
530
  () => extractDataSubtitle(activeCalculator?.description || ''),
@@ -589,6 +602,23 @@ export default function App() {
589
  }
590
  return mapping
591
  }, [groupedFields])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
  useEffect(() => {
594
  let mounted = true
@@ -762,19 +792,50 @@ export default function App() {
762
  <p>{targetOutput.value}</p>
763
  </article>
764
  ) : null}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  {result.pdf_url ? (
766
- <>
767
- <iframe
768
- title={`Relatório ${activeCalculator?.tab_label || ''}`}
769
- src={previewPdfUrl}
770
- className="pdf-frame"
771
- />
772
- <a className="secondary-btn" href={downloadPdfUrl} target="_blank" rel="noreferrer">
773
- Download relatório PDF
774
- </a>
775
- </>
776
  ) : (
777
- <p className="muted">Não foi possível gerar o PDF.</p>
778
  )}
779
  </>
780
  ) : null}
 
264
  .replace(/[^a-z0-9]+/g, '')
265
  }
266
 
267
+ function formatReportFieldValue(field, rawValue) {
268
+ if (field?.key === 'FON' && field?.kind === 'checkbox') {
269
+ return rawValue ? 'Ofertas' : 'Transações'
270
+ }
271
+ if (field?.kind === 'checkbox') {
272
+ return rawValue ? 'Sim' : 'Não'
273
+ }
274
+ if (rawValue === null || rawValue === undefined || rawValue === '') {
275
+ return '—'
276
+ }
277
+ return String(rawValue)
278
+ }
279
+
280
  function extractFieldHints(rawDescription) {
281
  const cleaned = String(rawDescription || '')
282
  .replace(/^\s*#\s*(?=<)/gm, '')
 
537
  [calculators, activeId]
538
  )
539
  const targetOutput = result?.text_outputs?.[0] || null
540
+ const previewChartUrl = result?.image_url ? buildFileUrl(result.image_url) : ''
541
  const downloadPdfUrl = result?.pdf_url ? buildFileUrl(`${result.pdf_url}?download=1`) : ''
542
  const dataSubtitle = useMemo(
543
  () => extractDataSubtitle(activeCalculator?.description || ''),
 
602
  }
603
  return mapping
604
  }, [groupedFields])
605
+ const reportInputRows = useMemo(() => {
606
+ if (!activeCalculator) return []
607
+ return (activeCalculator.inputs || []).map((field) => ({
608
+ key: field.key,
609
+ label: field.label,
610
+ value: formatReportFieldValue(field, inputs[field.key]),
611
+ }))
612
+ }, [activeCalculator, inputs])
613
+ const reportOutputRows = useMemo(
614
+ () =>
615
+ (result?.text_outputs || []).map((entry, index) => ({
616
+ key: `${index}-${entry?.label || ''}`,
617
+ label: String(entry?.label || ''),
618
+ value: String(entry?.value || ''),
619
+ })),
620
+ [result]
621
+ )
622
 
623
  useEffect(() => {
624
  let mounted = true
 
792
  <p>{targetOutput.value}</p>
793
  </article>
794
  ) : null}
795
+ <article className="report-html-preview">
796
+ <h3 className="report-preview-title">Pré-visualização do Relatório</h3>
797
+ <div className="report-preview-grid">
798
+ <section className="report-preview-section">
799
+ <h4>Parâmetros de Entrada</h4>
800
+ <div className="report-table">
801
+ {reportInputRows.map((row) => (
802
+ <div key={`input-${row.key}`} className="report-row">
803
+ <span className="report-cell-label">{row.label}</span>
804
+ <span className="report-cell-value">{row.value}</span>
805
+ </div>
806
+ ))}
807
+ </div>
808
+ </section>
809
+
810
+ <section className="report-preview-section">
811
+ <h4>Resultados</h4>
812
+ <div className="report-table">
813
+ {reportOutputRows.map((row, index) => (
814
+ <div key={`output-${row.key}`} className="report-row">
815
+ <span className="report-cell-label">{row.label || `Resultado ${index + 1}`}</span>
816
+ <span className="report-cell-value">{row.value || '—'}</span>
817
+ </div>
818
+ ))}
819
+ </div>
820
+ </section>
821
+ </div>
822
+ {previewChartUrl ? (
823
+ <section className="report-preview-section">
824
+ <h4>Gráfico</h4>
825
+ <img
826
+ src={previewChartUrl}
827
+ alt={`Gráfico ${activeCalculator?.tab_label || ''}`}
828
+ className="report-chart-image"
829
+ />
830
+ </section>
831
+ ) : null}
832
+ </article>
833
  {result.pdf_url ? (
834
+ <a className="secondary-btn" href={downloadPdfUrl} target="_blank" rel="noreferrer">
835
+ Download relatório PDF
836
+ </a>
 
 
 
 
 
 
 
837
  ) : (
838
+ <p className="muted">Não foi possível gerar o PDF para download.</p>
839
  )}
840
  </>
841
  ) : null}
frontend/src/styles.css CHANGED
@@ -382,13 +382,82 @@ body {
382
  cursor: wait;
383
  }
384
 
385
- .pdf-frame {
386
- width: 100%;
387
- min-height: 640px;
388
  border: 1px solid #d8e2ec;
389
  border-radius: var(--radius-md);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  background: #fff;
391
- box-shadow: inset 0 0 0 1px rgba(214, 223, 233, 0.6);
392
  }
393
 
394
  .secondary-btn {
@@ -419,12 +488,14 @@ body {
419
  min-height: 560px;
420
  }
421
 
422
- .pdf-frame {
423
- min-height: 460px;
424
- }
425
-
426
  .description,
427
- .fields-section-grid {
 
 
428
  grid-template-columns: 1fr;
429
  }
 
 
 
 
430
  }
 
382
  cursor: wait;
383
  }
384
 
385
+ .report-html-preview {
 
 
386
  border: 1px solid #d8e2ec;
387
  border-radius: var(--radius-md);
388
+ background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
389
+ padding: 12px;
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 10px;
393
+ }
394
+
395
+ .report-preview-title {
396
+ margin: 0;
397
+ font-family: 'Sora', sans-serif;
398
+ font-size: 0.9rem;
399
+ color: #27496a;
400
+ }
401
+
402
+ .report-preview-grid {
403
+ display: grid;
404
+ grid-template-columns: repeat(2, minmax(0, 1fr));
405
+ gap: 10px;
406
+ }
407
+
408
+ .report-preview-section {
409
+ border: 1px solid #deebf7;
410
+ border-radius: 10px;
411
+ background: #fdfefe;
412
+ padding: 9px;
413
+ }
414
+
415
+ .report-preview-section h4 {
416
+ margin: 0 0 8px;
417
+ font-size: 0.8rem;
418
+ font-family: 'Sora', sans-serif;
419
+ text-transform: uppercase;
420
+ letter-spacing: 0.03em;
421
+ color: #315476;
422
+ }
423
+
424
+ .report-table {
425
+ display: flex;
426
+ flex-direction: column;
427
+ gap: 6px;
428
+ }
429
+
430
+ .report-row {
431
+ display: grid;
432
+ grid-template-columns: 1.2fr 0.8fr;
433
+ gap: 8px;
434
+ align-items: start;
435
+ border: 1px solid #e0eaf4;
436
+ border-radius: 8px;
437
+ background: #ffffff;
438
+ padding: 6px 8px;
439
+ }
440
+
441
+ .report-cell-label {
442
+ font-size: 0.79rem;
443
+ font-weight: 700;
444
+ color: #375776;
445
+ }
446
+
447
+ .report-cell-value {
448
+ font-size: 0.81rem;
449
+ color: #243447;
450
+ text-align: right;
451
+ word-break: break-word;
452
+ }
453
+
454
+ .report-chart-image {
455
+ width: 100%;
456
+ display: block;
457
+ border: 1px solid #d8e2ec;
458
+ border-radius: 10px;
459
  background: #fff;
460
+ box-shadow: inset 0 0 0 1px rgba(214, 223, 233, 0.35);
461
  }
462
 
463
  .secondary-btn {
 
488
  min-height: 560px;
489
  }
490
 
 
 
 
 
491
  .description,
492
+ .fields-section-grid,
493
+ .report-preview-grid,
494
+ .report-row {
495
  grid-template-columns: 1fr;
496
  }
497
+
498
+ .report-cell-value {
499
+ text-align: left;
500
+ }
501
  }