Em aplicações empresariais é muito comum a necessidade de criar documentos em PDF, seja para exibir dados de um relatório ou mesmo para exportar informações exibidas em tela. Neste artigo irei mostrar como criar documentos PDF utilizando React e a biblioteca PdfMake.
Iniciando o projeto
Inicie um novo projeto react utilizando o comando:
yarn create react-app app-react-pdf
Caso você não tenha o Yarn instalado pode iniciar o projeto com o seguinte comando:
npx create-react-app app-react-pdf
Por fim adicione a biblioteca PdfMake ao projeto com o comando:
yarn add pdfmake
ou caso não esteja utilizando yarn utilize o seguinte comando:
npm install pdfmake —save
Como o foco principal deste artigo é a criação de documentos em PDF vou criar uma tela inicial bem simples, apenas com um botão para gerar o relatório.
O arquivo app.js
ficou assim:
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Criando documentos PDF com ReactJS
</p>
</header>
<section className="App-body">
<button className="btn">
Visualizar documento
</button>
</section>
</div>
);
}
export default App;
Segue abaixo as regras de estilização definidas no arquivo app.css
:
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-body {
height: 15vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.btn {
padding: 10px 16px;
font-size: 14px;
background-color: transparent;
border: 1px solid #61dafb;
border-radius: 6px;
color: #61dafb;
font-weight: bold;
transition: ease-in 0.3s;
}
.btn:hover {
background-color: #61dafb;
color: #fff;
cursor: pointer;
}
Agora que já temos a base da nossa aplicação, podemos iniciar a criação do relatório. Primeiramente vamos criar um arquivo que servirá como fonte de dados.
Na pasta src
crie um arquivo chamado data.js
e cole o conteúdo abaixo dentro do arquivo:
export const data = [
{
nome: "Mousepad",
qtdEstoque: 4,
qtdVendido: 10,
},
{
nome: "Teclado",
qtdEstoque: 8,
qtdVendido: 12,
},
{
nome: "Monitor",
qtdEstoque: 2,
qtdVendido: 14,
},
{
nome: "Mouse",
qtdEstoque: 15,
qtdVendido: 32,
}
];
No início do arquivo App.js
importe a biblioteca PdfMake e o arquivo data.js
que acabamos de criar
import React from 'react';
import logo from './logo.svg';
import './App.css';
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
import { data } from './data';
pdfMake.vfs = pdfFonts.pdfMake.vfs;
Importe o arquivo Impressao.js
que será criado posteriormente contendo o layout do relatório
import { Impressao } from './impressao';
No arquivo App.js
crie a função que irá abrir o documento PDF em uma nova guia
const visualizarImpressao = () => {
const classeImpressao = new Impressao(data);
const documento = classeImpressao.gerarDocumento();
pdfMake.createPdf(documento).open({}, window.open('', '_blank'));
}
Agora chame a função no evento de clique do botão
<button className="btn" onClick={visualizarImpressao}>
Visualizar documento
</button>
Implementando o documento PDF
O PdfMake utiliza a sintaxe de object literals para construir o layout dos documentos, e sua estrutura é dividida em 4 partes, sendo elas header
, content
, footer
e styles
.
Além disso possui um conjunto de elementos como Tabelas, parágrafos e listas, sendo que é possível estilizá-los passando as propriedades inline ou definindo-as dentro da propriedade styles.
Segue abaixo o código da classe de impressão:
export class Impressao {
constructor(dadosParaImpressao) {
this.dadosParaImpressao = dadosParaImpressao;
}
async PreparaDocumento() {
const corpoDocumento = this.CriaCorpoDocumento();
const documento = this.GerarDocumento(corpoDocumento);
return documento;
}
CriaCorpoDocumento() {
const header = [
{ text: 'Nome Produto', bold: true, fontSize: 9, margin: [0, 4, 0, 0] },
{ text: 'Qtd. Estoque', bold: true, fontSize: 9, margin: [0, 4, 0, 0] },
{ text: 'Qtd. Vendido', bold: true, fontSize: 9, margin: [0, 4, 0, 0] },
];
const body = this.dadosParaImpressao.map((prod) => {
return [
{ text: prod.nome, fontSize: 8 },
{ text: prod.qtdEstoque, fontSize: 8 },
{ text: prod.qtdVendido, fontSize: 8 },
];
});
const lineHeader = [
{
text:
'__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________',
alignment: 'center',
fontSize: 5,
colSpan: 3,
},
{},
{},
];
let content = [header, lineHeader];
content = [...content, ...body];
return content;
}
GerarDocumento(corpoDocumento) {
const documento = {
pageSize: 'A4',
pageMargins: [14, 53, 14, 48],
header: function () {
return {
margin: [14, 12, 14, 0],
layout: 'noBorders',
table: {
widths: ['*'],
body: [
[
{ text: 'RELATÓRIO DE VENDAS', style: 'reportName' }
]
],
},
};
},
content: [
{
layout: 'noBorders',
table: {
headerRows: 1,
widths: [ '*', 55, 55 ],
body: corpoDocumento
}
},
],
footer(currentPage, pageCount) {
return {
layout: 'noBorders',
margin: [14, 0, 14, 22],
table: {
widths: ['auto'],
body: [
[
{
text:
'_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________',
alignment: 'center',
fontSize: 5,
},
],
[
[
{
text: `Página ${currentPage.toString()} de ${pageCount}`,
fontSize: 7,
alignment: 'right',
/* horizontal, vertical */
margin: [3, 0],
},
{
text: '© Lojinha de TI',
fontSize: 7,
alignment: 'center',
},
],
],
],
},
};
},
styles: {
reportName: {
fontSize: 9,
bold: true,
alignment: 'center',
margin: [0, 4, 0, 0],
}
},
};
return documento;
}
}
O método PreparaDocumento chama o CriaCorpoDocumento que irá iterar os dados do arquivo data.js
e devolverá o conteúdo da seção content
do documento.
No método GerarDocumento é definido o layout do relatório. Na primeira linha é definido o tamanho da página na propriedade pageSaze
. Em seguida definimos as configurações de margem do documento. A propriedade pageMargins é muito importante, pois é ela que determina o tamanho disponível para o header e o footer, já que a altura do header vai de 0 até a quantidade de margem do topo e com o footer é a mesma coisa.
A propriedade content contém uma tabela e seu conteúdo sãos os dados gerados pelo método CriaCorpoDocumento. Na propriedade footer foi declarada uma função que recebe a página atual e a quantidade de páginas. A função do footer retorna uma tabela em que a primeira linha contém um text com vários _
para criar uma linha bem sutil, e na segunda linha foram utilizados os parâmetros recebidos pela função para exibir um contador de páginas.
Se você chegou até aqui, então seu relatório em PDF deve ter ficado igual ao da imagem abaixo:
E assim concluímos este tutorial, espero que tenham gostado e até o próximo post.
Top comments (8)
boa noite, consegui gerar o pdf com meus dados da api, porém quero pegar uma constante no meu arquivo(o mesmo que puxa os dados da api), que seria um valor total, mas não consigo, poderia me dar uma luz? Ele diz que minha constante não está definida. Estou usando hooks. Em qual parte ele puxa os dados de app.js e de qual forma? A constante que quero implementar no meu pdf é -> valorTotal
Como estava dando erro, eu retirei e deixei como funciona mas sem o valorTotal
Como eu poderia implementar?
const visualizarImpressao = async () => {
console.log('report', filtro);
const classeImpressao = new Impressao(filtro);
const documento = await classeImpressao.PreparaDocumento();
pdfMake.createPdf(documento).open({}, window.open('', '_blank'));
}
E muito obrigado por ter compartilhado esse conteúdo, foi muito util para mim.
essa lib não se comunica diretamente com o state do react, você precisa montar um objeto com todas as informações que precisa e passar no construtor da classe de impressão. Ex.:
const dados = {
valorTotal: valorTotalState,
};
const classeImpressao = new Impressao(dados);
Está assim o código, quero exibir o valorTotal depois da tabela que está no body
Poderia me ajudar?
export class Impressao {
constructor(dadosParaImpressao) {
this.dadosParaImpressao = dadosParaImpressao;
}
async PreparaDocumento() {
const corpoDocumento = this.CriaCorpoDocumento();
const documento = this.GerarDocumento(corpoDocumento);
return documento;
}
CriaCorpoDocumento() {
const header = [
{ text: 'Mês Chamado', bold: true, fontSize: 12, margin: [0, 4, 0, 0] },
{ text: 'Número Chamado', bold: true, fontSize: 12, margin: [0, 4, 0, 0] },
{ text: 'Valor Boleto', bold: true, fontSize: 12, margin: [0, 4, 0, 0] },
{ text: 'Técnico Chamado', bold: true, fontSize: 12, margin: [0, 4, 0, 0] },
{ text: 'Sistema', bold: true, fontSize: 12, margin: [0, 4, 0, 0] },
}
}
Substitua seu método CriaCorpoDocumento pelo exemplo que vou deixar abaixo, aí você vai conseguir adicionar outros elementos como texto, imagem, etc... Basta fazer
page.push(elemento_que_deseja_adicionar)
`
Outra coisa, no método GerarDocumento substitua a linha da propriedade content pra ficar assim:
content: corpoDocumento
Esse codigo funciona mesmo? Sou iniciante em react e estou praticando essa parte de gerar documentos. Quando implementei seu código eu tive o seguinte erro:
TypeError: classeImpressao.gerarDocumento is not a function
Esse erro aponta para a função de visualizarImpressao nesta linha:
const documento = classeImpressao.gerarDocumento();
Poderia me dizer se eu fiz algo de errado?
Não sei se já conseguiu descobrir onde estava o erro, mas o código funciona sim. Caso ajude eu subi o projeto para o github. Segue abaixo o link do repositório:
github.com/taikio/react-pdf-example
Tive a mesma dúvida descobri o erro!
Na função visualizarImpressao troca o :
const documento = classeImpressao.gerarDocumento();
por
const documento = await classeImpressao.PreparaDocumento();
que por sinal gerarDocumento deveria ser GerarDocumento maiúsculo.
Tks!
Bom dia galera, implementei o exemplo e inclui mais alguns que encontrei na documentação da lib, se quiserem dar uma olhada: github.com/GlerystonMatos/react-pdf