fix: correcao na geracao de relatorio PDF em prioridades de fabricacao

This commit is contained in:
2026-08-21 16:26:20 +00:00
parent 4504c1ded2
commit 046639321b
4 changed files with 190 additions and 322 deletions
+96 -57
View File
@@ -1,17 +1,19 @@
import React from 'react'; import React, { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { PrioridadesPDFTemplate } from './PrioridadesPDFTemplate'; import { PrioridadesPDFTemplate } from './PrioridadesPDFTemplate';
import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao'; import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao';
import { Download } from 'lucide-react'; import { Download, Loader2, Printer } from 'lucide-react';
import html2canvas from 'html2canvas'; import { generateProfessionalPDF, printProfessionalPDF } from '@/utils/pdfGenerator';
import jsPDF from 'jspdf'; import { toast } from 'sonner';
interface PrioridadesPDFProps { interface PrioridadesPDFProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
itensPorPrioridade: { [key: string]: ItemPrioridade[] }; itensPorPrioridade: { [key: string]: ItemPrioridade[] };
ofSelecionada?: string | null;
faseSelecionada?: string | null;
versaoAtual?: { versaoAtual?: {
revisao: number; revisao: number;
dataModificacao: string; dataModificacao: string;
@@ -23,64 +25,66 @@ export const PrioridadesPDF: React.FC<PrioridadesPDFProps> = ({
isOpen, isOpen,
onClose, onClose,
itensPorPrioridade, itensPorPrioridade,
ofSelecionada,
faseSelecionada,
versaoAtual versaoAtual
}) => { }) => {
const [isGenerating, setIsGenerating] = useState(false);
const [isPrinting, setIsPrinting] = useState(false);
const getNomeArquivo = () => {
let nomeArquivo = 'checklist-producao';
const todosItens = Object.values(itensPorPrioridade).flat();
const primeiroItem = todosItens[0];
const of = ofSelecionada || primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number;
const fase = faseSelecionada || primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase;
if (of && fase) {
nomeArquivo = `checklist-producao-${of}-${fase}`;
if (versaoAtual) {
nomeArquivo += `-rev${versaoAtual.revisao}`;
}
}
return nomeArquivo;
};
const handleGerarPDF = async () => { const handleGerarPDF = async () => {
const elemento = document.getElementById('prioridades-pdf-content'); const elemento = document.getElementById('prioridades-pdf-content');
if (!elemento) { if (!elemento) {
console.error('Elemento não encontrado'); toast.error('Elemento do relatório não encontrado');
return; return;
} }
try { try {
const canvas = await html2canvas(elemento, { setIsGenerating(true);
scale: 2, const nomeArquivo = getNomeArquivo();
useCORS: true, await generateProfessionalPDF('prioridades-pdf-content', `${nomeArquivo}.pdf`);
allowTaint: true, toast.success('PDF baixado com sucesso!');
backgroundColor: '#ffffff' } catch (error: any) {
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210;
const pageHeight = 295;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
// Extrair OF e Fase para o nome do arquivo
const todosItens = Object.values(itensPorPrioridade).flat();
let nomeArquivo = 'checklist-producao';
if (todosItens.length > 0) {
const primeiroItem = todosItens[0];
const of = primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number;
const fase = primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase;
if (of && fase) {
nomeArquivo = `checklist-producao-${of}-${fase}`;
if (versaoAtual) {
nomeArquivo += `-rev${versaoAtual.revisao}`;
}
}
}
pdf.save(`${nomeArquivo}.pdf`);
} catch (error) {
console.error('Erro ao gerar PDF:', error); console.error('Erro ao gerar PDF:', error);
toast.error(error.message || 'Erro ao gerar PDF');
} finally {
setIsGenerating(false);
}
};
const handleImprimir = async () => {
const elemento = document.getElementById('prioridades-pdf-content');
if (!elemento) {
toast.error('Elemento do relatório não encontrado');
return;
}
try {
setIsPrinting(true);
await printProfessionalPDF('prioridades-pdf-content');
toast.success('Relatório enviado para impressão');
} catch (error: any) {
console.error('Erro ao imprimir:', error);
toast.error('Erro ao imprimir relatório');
} finally {
setIsPrinting(false);
} }
}; };
@@ -88,18 +92,53 @@ export const PrioridadesPDF: React.FC<PrioridadesPDFProps> = ({
<Dialog open={isOpen} onOpenChange={onClose}> <Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center justify-between"> <DialogTitle className="flex flex-wrap items-center justify-between gap-2">
Visualizar Checklist de Produção <span>Visualizar Checklist de Produção</span>
<Button onClick={handleGerarPDF} className="ml-4"> <div className="flex items-center gap-2">
<Download className="h-4 w-4 mr-2" /> <Button
Baixar PDF variant="outline"
</Button> size="sm"
onClick={handleImprimir}
disabled={isPrinting || isGenerating}
>
{isPrinting ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Imprimindo...
</>
) : (
<>
<Printer className="h-4 w-4 mr-2" />
Imprimir
</>
)}
</Button>
<Button
size="sm"
onClick={handleGerarPDF}
disabled={isGenerating || isPrinting}
>
{isGenerating ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Gerando PDF...
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Baixar PDF
</>
)}
</Button>
</div>
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="mt-4"> <div className="mt-4">
<PrioridadesPDFTemplate <PrioridadesPDFTemplate
itensPorPrioridade={itensPorPrioridade} itensPorPrioridade={itensPorPrioridade}
ofSelecionada={ofSelecionada}
faseSelecionada={faseSelecionada}
versaoAtual={versaoAtual} versaoAtual={versaoAtual}
/> />
</div> </div>
@@ -4,6 +4,8 @@ import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao';
interface PrioridadesPDFTemplateProps { interface PrioridadesPDFTemplateProps {
itensPorPrioridade: { [key: string]: ItemPrioridade[] }; itensPorPrioridade: { [key: string]: ItemPrioridade[] };
ofSelecionada?: string | null;
faseSelecionada?: string | null;
versaoAtual?: { versaoAtual?: {
revisao: number; revisao: number;
dataModificacao: string; dataModificacao: string;
@@ -13,13 +15,15 @@ interface PrioridadesPDFTemplateProps {
export const PrioridadesPDFTemplate: React.FC<PrioridadesPDFTemplateProps> = ({ export const PrioridadesPDFTemplate: React.FC<PrioridadesPDFTemplateProps> = ({
itensPorPrioridade, itensPorPrioridade,
ofSelecionada,
faseSelecionada,
versaoAtual versaoAtual
}) => { }) => {
const todosItens = Object.values(itensPorPrioridade).flat(); const todosItens = Object.values(itensPorPrioridade).flat();
const primeiroItem = todosItens[0]; const primeiroItem = todosItens[0];
const ofNumber = primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number || 'N/A'; const ofNumber = ofSelecionada || primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number || 'N/A';
const etapaFase = primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase || 'N/A'; const etapaFase = faseSelecionada || primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase || 'N/A';
const dataAtual = new Date().toLocaleDateString('pt-BR'); const dataAtual = new Date().toLocaleDateString('pt-BR');
+2 -160
View File
@@ -420,168 +420,10 @@ const PrioridadesFabricacao = () => {
isOpen={showPrioridadesPDF} isOpen={showPrioridadesPDF}
onClose={() => setShowPrioridadesPDF(false)} onClose={() => setShowPrioridadesPDF(false)}
itensPorPrioridade={itensPorPrioridade} itensPorPrioridade={itensPorPrioridade}
ofSelecionada={ofSelecionada}
faseSelecionada={faseSelecionada}
versaoAtual={versaoAtual} versaoAtual={versaoAtual}
/> />
{/* Componente oculto para impressão */}
<div className="hidden">
<div id="prioridades-pdf-content">
{(ofSelecionada && faseSelecionada) && (
<div className="bg-white text-black max-w-4xl mx-auto p-6">
{/* Usar o mesmo template do PDF */}
<div className="flex justify-between items-center border-b-2 border-gray-800 pb-4 mb-4">
<div>
<h1 className="text-2xl font-bold text-gray-900">Checklist de Produção</h1>
<p className="text-gray-600">Formulário para apontamento da fabricação.</p>
</div>
<div className="text-right">
<p className="font-semibold">
Data de Emissão: <span className="font-normal">{new Date().toLocaleDateString('pt-BR')}</span>
{versaoAtual && (
<span className="ml-2 text-gray-500">Rev. {versaoAtual.revisao}</span>
)}
</p>
</div>
</div>
<div className="border border-gray-200 bg-white p-4 rounded-lg mb-2">
<div className="grid grid-cols-1 md:grid-cols-4 gap-x-6 gap-y-4">
<div>
<p className="text-xs font-medium text-gray-500">Ordem de Fabricação (OF)</p>
<p className="text-base font-bold text-gray-800">{ofSelecionada}</p>
</div>
<div>
<p className="text-xs font-medium text-gray-500">Fase</p>
<p className="text-base font-bold text-gray-800">{faseSelecionada}</p>
</div>
<div className="md:col-span-2">
<p className="text-xs font-medium text-gray-500">PROCESSO</p>
<div className="flex items-center flex-wrap gap-x-4 gap-y-1 mt-1">
{['Corte', 'Solda', 'Pintura', 'Expedição'].map((processo) => (
<div key={processo} className="flex items-center gap-1">
<div className="w-4 h-4 border-2 border-gray-500"></div>
<span className="text-sm font-semibold text-gray-700">{processo}</span>
</div>
))}
</div>
</div>
</div>
</div>
<div className="text-xs text-gray-600 mb-6 flex items-center flex-wrap gap-x-3">
<span className="font-semibold">Legenda:</span>
<span>Marca (Qtd)</span>
<span className="font-medium text-gray-500">(S/M)</span>
<span>= Sem Montagem,</span>
<span className="font-medium text-gray-500">(C/M)</span>
<span>= Com Montagem. Os quadrados indicam o controle de peças fabricadas.</span>
</div>
{/* Renderizar os itens por prioridade */}
<div className="space-y-8">
{['P1', 'P2', 'P3', 'P4'].map((codigo, priorityIndex) => {
const itens = itensPorPrioridade[codigo] || [];
if (itens.length === 0) return null;
const getPrioridadeNome = (codigo: string) => {
switch (codigo) {
case 'P1': return 'Prioridade P1 - Urgente';
case 'P2': return 'Prioridade P2 - Alta';
case 'P3': return 'Prioridade P3 - Média';
case 'P4': return 'Prioridade P4 - Baixa';
default: return 'Desconhecida';
}
};
const getCoresPrioridade = (codigo: string) => {
switch (codigo) {
case 'P1': return 'text-red-700 bg-red-100';
case 'P2': return 'text-orange-700 bg-orange-100';
case 'P3': return 'text-blue-700 bg-blue-100';
case 'P4': return 'text-gray-700 bg-gray-200';
default: return 'text-gray-700 bg-gray-200';
}
};
return (
<div key={codigo}>
<h2 className={`text-lg font-semibold ${getCoresPrioridade(codigo)} px-3 py-1 rounded-md inline-block mb-3`}>
{getPrioridadeNome(codigo)}
</h2>
<div className="space-y-1">
{Array.from({ length: Math.ceil(itens.length / 3) }, (_, i) => {
const bgColorClass = i % 2 !== 0 ? 'bg-gray-50' : 'bg-white';
const rowItems = itens.slice(i * 3, (i + 1) * 3);
return (
<div key={i} className={`grid grid-cols-3 gap-2 p-1 rounded-md ${bgColorClass}`}>
{rowItems.map((item) => {
const quantidade = item.quantidade_priorizada;
const marca = item.peca?.marca || 'N/A';
const temComponentes = item.peca?.tem_componentes;
const infoType = temComponentes ? '(C/M)' : '(S/M)';
// Gerar checkboxes
const generateTickBoxes = (quantity: number) => {
const boxes = [];
if (quantity > 10) {
const numBigBoxes = Math.floor(quantity / 5);
const numSmallBoxes = quantity % 5;
for (let i = 0; i < numBigBoxes; i++) {
boxes.push(
<div key={`big-${i}`} className="tick-box-large">
<span>5</span>
</div>
);
}
for (let i = 0; i < numSmallBoxes; i++) {
boxes.push(<div key={`small-${i}`} className="tick-box"></div>);
}
} else {
for (let i = 0; i < quantity; i++) {
boxes.push(<div key={i} className="tick-box"></div>);
}
}
return <div className="flex items-center flex-wrap gap-1">{boxes}</div>;
};
return (
<div key={item.id} className="item-card">
<div className="flex items-center flex-wrap gap-2 mb-2">
<span className="font-semibold text-sm whitespace-nowrap">
{marca} ({quantidade})
</span>
<span className="text-xs font-medium text-gray-500">{infoType}</span>
{generateTickBoxes(quantidade)}
</div>
<div className="mt-2 text-xs">
<div className="border-b border-gray-400 pb-1 h-5">Data/Operador:</div>
</div>
</div>
);
})}
{/* Preencher células vazias se necessário */}
{Array.from({ length: 3 - rowItems.length }, (_, emptyIndex) => (
<div key={`empty-${emptyIndex}`}></div>
))}
</div>
);
})}
</div>
</div>
);
})}
</div>
</div>
)}
</div>
</div>
</div> </div>
</StandardPageLayout> </StandardPageLayout>
); );
+86 -103
View File
@@ -2,75 +2,79 @@ import html2canvas from 'html2canvas';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
export const generateProfessionalPDF = async (elementId: string, filename: string) => { export const generateProfessionalPDF = async (elementId: string, filename: string) => {
let tempContainer: HTMLElement | null = null;
try { try {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (!element) { if (!element) {
throw new Error('Elemento não encontrado para gerar PDF'); throw new Error(`Elemento #${elementId} não encontrado para gerar PDF`);
} }
// Aguardar um pouco para garantir que o elemento esteja completamente renderizado // Criar um container temporário isolado fora de modais/dialogs para evitar bugs de tamanho 0x0
await new Promise(resolve => setTimeout(resolve, 200)); 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';
// Garantir que o elemento esteja visível e com dimensões corretas // Clonar o elemento para o container isolado
const originalDisplay = element.style.display; const clonedElement = element.cloneNode(true) as HTMLElement;
const originalVisibility = element.style.visibility; clonedElement.style.display = 'block';
const originalPosition = element.style.position; clonedElement.style.visibility = 'visible';
clonedElement.style.position = 'static';
element.style.display = 'block'; clonedElement.style.width = '100%';
element.style.visibility = 'visible'; clonedElement.style.maxWidth = '100%';
element.style.position = 'relative'; clonedElement.style.height = 'auto';
clonedElement.style.maxHeight = 'none';
// Forçar um reflow clonedElement.style.overflow = 'visible';
element.offsetHeight; clonedElement.style.transform = 'none';
clonedElement.style.backgroundColor = '#ffffff';
// Aguardar mais um pouco após forçar o reflow 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)); await new Promise(resolve => setTimeout(resolve, 300));
console.log('Gerando PDF para elemento:', elementId); // Elemento a ser renderizado pelo html2canvas
console.log('Dimensões do elemento:', { const targetElement = (clonedElement.offsetHeight > 0 && clonedElement.offsetWidth > 0)
width: element.offsetWidth, ? clonedElement
height: element.offsetHeight, : element;
scrollWidth: element.scrollWidth,
scrollHeight: element.scrollHeight
});
// Se o elemento não tem dimensões, isso pode causar PDF em branco
if (element.offsetWidth === 0 || element.offsetHeight === 0) {
throw new Error('Elemento tem dimensões zero - não é possível gerar PDF');
}
// Configurações otimizadas para html2canvas // Configurações otimizadas para html2canvas
const canvas = await html2canvas(element, { const canvas = await html2canvas(targetElement, {
scale: 2, scale: 2,
useCORS: true, useCORS: true,
allowTaint: false, allowTaint: true,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
width: element.scrollWidth, logging: false,
height: element.scrollHeight, windowWidth: 1024,
scrollX: 0, onclone: (clonedDoc) => {
scrollY: 0, const found = clonedDoc.getElementById(elementId) || clonedDoc.body;
windowWidth: Math.max(element.scrollWidth, 1200), if (found) {
windowHeight: Math.max(element.scrollHeight, 800), (found as HTMLElement).style.display = 'block';
foreignObjectRendering: false, (found as HTMLElement).style.visibility = 'visible';
removeContainer: false, }
imageTimeout: 10000, }
logging: false
}); });
console.log('Canvas criado com sucesso:', { if (!canvas || canvas.width === 0 || canvas.height === 0) {
width: canvas.width, throw new Error('Não foi possível capturar o layout visual para o PDF');
height: canvas.height
});
// Verificar se o canvas foi criado corretamente
if (canvas.width === 0 || canvas.height === 0) {
throw new Error('Canvas criado com dimensões zero');
} }
// Restaurar estilos originais const imgData = canvas.toDataURL('image/png', 1.0);
element.style.display = originalDisplay; if (!imgData || !imgData.startsWith('data:image/png;base64,')) {
element.style.visibility = originalVisibility; throw new Error('Falha ao processar os dados da imagem para o PDF');
element.style.position = originalPosition; }
// Criar PDF com configurações otimizadas // Criar PDF com configurações otimizadas
const pdf = new jsPDF({ const pdf = new jsPDF({
@@ -85,10 +89,10 @@ export const generateProfessionalPDF = async (elementId: string, filename: strin
const pageHeight = 297; const pageHeight = 297;
// Margens adequadas // Margens adequadas
const marginTop = 15; const marginTop = 12;
const marginBottom = 15; const marginBottom = 12;
const marginLeft = 15; const marginLeft = 12;
const marginRight = 15; const marginRight = 12;
// Área útil para conteúdo // Área útil para conteúdo
const contentWidth = pageWidth - marginLeft - marginRight; const contentWidth = pageWidth - marginLeft - marginRight;
@@ -98,103 +102,82 @@ export const generateProfessionalPDF = async (elementId: string, filename: strin
const imgWidth = contentWidth; const imgWidth = contentWidth;
const imgHeight = (canvas.height * contentWidth) / canvas.width; const imgHeight = (canvas.height * contentWidth) / canvas.width;
// Converter canvas para imagem
const imgData = canvas.toDataURL('image/png', 1.0);
console.log('Adicionando imagem ao PDF:', {
imgWidth,
imgHeight,
contentHeight,
totalPages: Math.ceil(imgHeight / contentHeight)
});
// Verificar se os dados da imagem foram gerados
if (!imgData || imgData === 'data:,') {
throw new Error('Falha ao gerar dados da imagem do canvas');
}
// Sistema de paginação // Sistema de paginação
let currentY = 0; let currentY = 0;
let pageNumber = 1; let pageNumber = 1;
const totalPages = Math.ceil(imgHeight / contentHeight); const totalPages = Math.max(1, Math.ceil(imgHeight / contentHeight));
// Função para adicionar rodapé com numeração // Função para adicionar rodapé com numeração
const addFooter = (pageNum: number, totalPages: number) => { const addFooter = (pageNum: number, totalPagesCount: number) => {
pdf.setFontSize(8); pdf.setFontSize(8);
pdf.setTextColor(100, 100, 100); pdf.setTextColor(120, 120, 120);
const footerText = `Página ${pageNum} de ${totalPages}`; const footerText = `Página ${pageNum} de ${totalPagesCount}`;
const textWidth = pdf.getTextWidth(footerText); const textWidth = pdf.getTextWidth(footerText);
const footerX = (pageWidth - textWidth) / 2; const footerX = (pageWidth - textWidth) / 2;
const footerY = pageHeight - 8; const footerY = pageHeight - 6;
pdf.text(footerText, footerX, footerY); pdf.text(footerText, footerX, footerY);
}; };
// Primeira página
if (imgHeight <= contentHeight) { if (imgHeight <= contentHeight) {
// Conteúdo cabe em uma página // Conteúdo cabe em uma página
pdf.addImage(imgData, 'PNG', marginLeft, marginTop, imgWidth, imgHeight); pdf.addImage(imgData, 'PNG', marginLeft, marginTop, imgWidth, imgHeight, undefined, 'FAST');
addFooter(1, 1); addFooter(1, 1);
} else { } else {
// Conteúdo precisa de múltiplas páginas // Conteúdo precisa de múltiplas páginas com corte preciso
while (currentY < imgHeight) { while (currentY < imgHeight) {
if (pageNumber > 1) { if (pageNumber > 1) {
pdf.addPage(); pdf.addPage();
} }
// Calcular a altura restante do conteúdo
const remainingHeight = imgHeight - currentY; const remainingHeight = imgHeight - currentY;
const currentPageHeight = Math.min(contentHeight, remainingHeight); const currentPageHeight = Math.min(contentHeight, remainingHeight);
// Criar um canvas temporário para a seção atual
const tempCanvas = document.createElement('canvas'); const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d'); const tempCtx = tempCanvas.getContext('2d');
if (tempCtx) { if (tempCtx && currentPageHeight > 0) {
const sliceHeight = Math.round((currentPageHeight * canvas.width) / imgWidth);
tempCanvas.width = canvas.width; tempCanvas.width = canvas.width;
tempCanvas.height = (currentPageHeight * canvas.width) / imgWidth; tempCanvas.height = sliceHeight;
tempCtx.fillStyle = '#ffffff';
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
// Desenhar a seção atual do canvas original
tempCtx.drawImage( tempCtx.drawImage(
canvas, canvas,
0, 0,
(currentY * canvas.width) / imgWidth, Math.round((currentY * canvas.width) / imgWidth),
canvas.width, canvas.width,
tempCanvas.height, sliceHeight,
0, 0,
0, 0,
canvas.width, canvas.width,
tempCanvas.height sliceHeight
); );
// Converter para dados de imagem
const tempImgData = tempCanvas.toDataURL('image/png', 1.0); const tempImgData = tempCanvas.toDataURL('image/png', 1.0);
if (tempImgData && tempImgData.startsWith('data:image/png;base64,')) {
// Adicionar a seção ao PDF pdf.addImage(tempImgData, 'PNG', marginLeft, marginTop, imgWidth, currentPageHeight, undefined, 'FAST');
pdf.addImage(tempImgData, 'PNG', marginLeft, marginTop, imgWidth, currentPageHeight); }
} }
// Adicionar rodapé
addFooter(pageNumber, totalPages); addFooter(pageNumber, totalPages);
// Preparar para próxima página
currentY += contentHeight; currentY += contentHeight;
pageNumber++; pageNumber++;
} }
} }
// Forçar o download do PDF // Salvar arquivo PDF
console.log('Iniciando download do PDF:', filename);
pdf.save(filename); pdf.save(filename);
// Aguardar um momento para garantir que o download seja iniciado
await new Promise(resolve => setTimeout(resolve, 500));
console.log('PDF salvo com sucesso:', filename);
return true; return true;
} catch (error) { } catch (error: any) {
console.error('Erro detalhado ao gerar PDF:', error); console.error('Erro detalhado ao gerar PDF:', error);
throw new Error(`Erro ao gerar PDF: ${error.message}`); throw new Error(`Erro ao gerar PDF: ${error.message || error}`);
} finally {
if (tempContainer && tempContainer.parentNode) {
tempContainer.parentNode.removeChild(tempContainer);
}
} }
}; };