Files
tracksteel_app/src/utils/pdfGenerator.ts
T

304 lines
9.5 KiB
TypeScript

import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
export const generateProfessionalPDF = async (elementId: string, filename: string) => {
let tempContainer: HTMLElement | null = null;
try {
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Elemento #${elementId} não encontrado para gerar PDF`);
}
// Criar um container temporário isolado fora de modais/dialogs para evitar bugs de tamanho 0x0
tempContainer = document.createElement('div');
tempContainer.style.position = 'fixed';
tempContainer.style.left = '0';
tempContainer.style.top = '0';
tempContainer.style.width = '850px';
tempContainer.style.backgroundColor = '#ffffff';
tempContainer.style.zIndex = '-9999';
tempContainer.style.opacity = '1';
tempContainer.style.pointerEvents = 'none';
tempContainer.style.margin = '0';
tempContainer.style.padding = '20px';
tempContainer.style.boxSizing = 'border-box';
tempContainer.style.overflow = 'visible';
// Clonar o elemento para o container isolado
const clonedElement = element.cloneNode(true) as HTMLElement;
clonedElement.style.display = 'block';
clonedElement.style.visibility = 'visible';
clonedElement.style.position = 'static';
clonedElement.style.width = '100%';
clonedElement.style.maxWidth = '100%';
clonedElement.style.height = 'auto';
clonedElement.style.maxHeight = 'none';
clonedElement.style.overflow = 'visible';
clonedElement.style.transform = 'none';
clonedElement.style.backgroundColor = '#ffffff';
clonedElement.style.color = '#000000';
tempContainer.appendChild(clonedElement);
document.body.appendChild(tempContainer);
// Aguardar renderização e computação de layout
await new Promise(resolve => setTimeout(resolve, 300));
// Elemento a ser renderizado pelo html2canvas
const targetElement = (clonedElement.offsetHeight > 0 && clonedElement.offsetWidth > 0)
? clonedElement
: element;
// Configurações otimizadas para html2canvas
const canvas = await html2canvas(targetElement, {
scale: 2,
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
logging: false,
windowWidth: 1024,
onclone: (clonedDoc) => {
const found = clonedDoc.getElementById(elementId) || clonedDoc.body;
if (found) {
(found as HTMLElement).style.display = 'block';
(found as HTMLElement).style.visibility = 'visible';
}
}
});
if (!canvas || canvas.width === 0 || canvas.height === 0) {
throw new Error('Não foi possível capturar o layout visual para o PDF');
}
const imgData = canvas.toDataURL('image/png', 1.0);
if (!imgData || !imgData.startsWith('data:image/png;base64,')) {
throw new Error('Falha ao processar os dados da imagem para o PDF');
}
// Criar PDF com configurações otimizadas
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4',
compress: true
});
// Dimensões A4 em mm
const pageWidth = 210;
const pageHeight = 297;
// Margens adequadas
const marginTop = 12;
const marginBottom = 12;
const marginLeft = 12;
const marginRight = 12;
// Área útil para conteúdo
const contentWidth = pageWidth - marginLeft - marginRight;
const contentHeight = pageHeight - marginTop - marginBottom;
// Calcular proporções mantendo aspect ratio
const imgWidth = contentWidth;
const imgHeight = (canvas.height * contentWidth) / canvas.width;
// Sistema de paginação
let currentY = 0;
let pageNumber = 1;
const totalPages = Math.max(1, Math.ceil(imgHeight / contentHeight));
// Função para adicionar rodapé com numeração
const addFooter = (pageNum: number, totalPagesCount: number) => {
pdf.setFontSize(8);
pdf.setTextColor(120, 120, 120);
const footerText = `Página ${pageNum} de ${totalPagesCount}`;
const textWidth = pdf.getTextWidth(footerText);
const footerX = (pageWidth - textWidth) / 2;
const footerY = pageHeight - 6;
pdf.text(footerText, footerX, footerY);
};
if (imgHeight <= contentHeight) {
// Conteúdo cabe em uma página
pdf.addImage(imgData, 'PNG', marginLeft, marginTop, imgWidth, imgHeight, undefined, 'FAST');
addFooter(1, 1);
} else {
// Conteúdo precisa de múltiplas páginas com corte preciso
while (currentY < imgHeight) {
if (pageNumber > 1) {
pdf.addPage();
}
const remainingHeight = imgHeight - currentY;
const currentPageHeight = Math.min(contentHeight, remainingHeight);
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
if (tempCtx && currentPageHeight > 0) {
const sliceHeight = Math.round((currentPageHeight * canvas.width) / imgWidth);
tempCanvas.width = canvas.width;
tempCanvas.height = sliceHeight;
tempCtx.fillStyle = '#ffffff';
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(
canvas,
0,
Math.round((currentY * canvas.width) / imgWidth),
canvas.width,
sliceHeight,
0,
0,
canvas.width,
sliceHeight
);
const tempImgData = tempCanvas.toDataURL('image/png', 1.0);
if (tempImgData && tempImgData.startsWith('data:image/png;base64,')) {
pdf.addImage(tempImgData, 'PNG', marginLeft, marginTop, imgWidth, currentPageHeight, undefined, 'FAST');
}
}
addFooter(pageNumber, totalPages);
currentY += contentHeight;
pageNumber++;
}
}
// Salvar arquivo PDF
pdf.save(filename);
return true;
} catch (error: any) {
console.error('Erro detalhado ao gerar PDF:', error);
throw new Error(`Erro ao gerar PDF: ${error.message || error}`);
} finally {
if (tempContainer && tempContainer.parentNode) {
tempContainer.parentNode.removeChild(tempContainer);
}
}
};
export const printProfessionalPDF = async (elementId: string) => {
try {
const element = document.getElementById(elementId);
if (!element) {
throw new Error('Elemento não encontrado');
}
// Criar nova janela para impressão
const printWindow = window.open('', '_blank');
if (!printWindow) {
throw new Error('Não foi possível abrir janela de impressão');
}
// Obter estilos do documento atual
const styleSheets = Array.from(document.styleSheets);
let allStyles = '';
try {
styleSheets.forEach(sheet => {
try {
if (sheet.cssRules) {
Array.from(sheet.cssRules).forEach(rule => {
allStyles += rule.cssText + '\n';
});
}
} catch (e) {
// Ignorar erros de CORS
}
});
} catch (e) {
console.warn('Não foi possível obter alguns estilos', e);
}
// HTML otimizado para impressão idêntica à visualização
const printContent = `
<!DOCTYPE html>
<html>
<head>
<title>Checklist de Produção</title>
<meta charset="UTF-8">
<script src="https://cdn.tailwindcss.com"></script>
<style>
${allStyles}
/* Reset e configuração básica */
* {
box-sizing: border-box;
}
@page {
size: A4 portrait;
margin: 8mm 10mm 8mm 10mm;
}
@media print {
body {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
background-color: #ffffff !important;
font-size: 9.5px !important;
}
.no-print {
display: none !important;
}
#${elementId} {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
transform: none !important;
}
.priority-group {
break-inside: auto !important;
page-break-inside: auto !important;
}
h2 {
break-after: avoid !important;
page-break-after: avoid !important;
}
.item-row {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
.item-card {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
}
</style>
</head>
<body class="bg-white">
${element.outerHTML}
<script>
window.onload = function() {
setTimeout(() => {
window.print();
window.close();
}, 800);
}
</script>
</body>
</html>
`;
printWindow.document.write(printContent);
printWindow.document.close();
return true;
} catch (error) {
console.error('Erro ao imprimir PDF:', error);
throw error;
}
};