fix: correcao na geracao de relatorio PDF em prioridades de fabricacao
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PrioridadesPDFTemplate } from './PrioridadesPDFTemplate';
|
||||
import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao';
|
||||
import { Download } from 'lucide-react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import jsPDF from 'jspdf';
|
||||
import { Download, Loader2, Printer } from 'lucide-react';
|
||||
import { generateProfessionalPDF, printProfessionalPDF } from '@/utils/pdfGenerator';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface PrioridadesPDFProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
itensPorPrioridade: { [key: string]: ItemPrioridade[] };
|
||||
ofSelecionada?: string | null;
|
||||
faseSelecionada?: string | null;
|
||||
versaoAtual?: {
|
||||
revisao: number;
|
||||
dataModificacao: string;
|
||||
@@ -23,64 +25,66 @@ export const PrioridadesPDF: React.FC<PrioridadesPDFProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
itensPorPrioridade,
|
||||
ofSelecionada,
|
||||
faseSelecionada,
|
||||
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 elemento = document.getElementById('prioridades-pdf-content');
|
||||
|
||||
if (!elemento) {
|
||||
console.error('Elemento não encontrado');
|
||||
toast.error('Elemento do relatório não encontrado');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(elemento, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff'
|
||||
});
|
||||
|
||||
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) {
|
||||
setIsGenerating(true);
|
||||
const nomeArquivo = getNomeArquivo();
|
||||
await generateProfessionalPDF('prioridades-pdf-content', `${nomeArquivo}.pdf`);
|
||||
toast.success('PDF baixado com sucesso!');
|
||||
} catch (error: any) {
|
||||
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}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center justify-between">
|
||||
Visualizar Checklist de Produção
|
||||
<Button onClick={handleGerarPDF} className="ml-4">
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Baixar PDF
|
||||
</Button>
|
||||
<DialogTitle className="flex flex-wrap items-center justify-between gap-2">
|
||||
<span>Visualizar Checklist de Produção</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
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>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-4">
|
||||
<PrioridadesPDFTemplate
|
||||
itensPorPrioridade={itensPorPrioridade}
|
||||
ofSelecionada={ofSelecionada}
|
||||
faseSelecionada={faseSelecionada}
|
||||
versaoAtual={versaoAtual}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ItemPrioridade } from '@/hooks/useItensPrioridadeFabricacao';
|
||||
|
||||
interface PrioridadesPDFTemplateProps {
|
||||
itensPorPrioridade: { [key: string]: ItemPrioridade[] };
|
||||
ofSelecionada?: string | null;
|
||||
faseSelecionada?: string | null;
|
||||
versaoAtual?: {
|
||||
revisao: number;
|
||||
dataModificacao: string;
|
||||
@@ -13,13 +15,15 @@ interface PrioridadesPDFTemplateProps {
|
||||
|
||||
export const PrioridadesPDFTemplate: React.FC<PrioridadesPDFTemplateProps> = ({
|
||||
itensPorPrioridade,
|
||||
ofSelecionada,
|
||||
faseSelecionada,
|
||||
versaoAtual
|
||||
}) => {
|
||||
const todosItens = Object.values(itensPorPrioridade).flat();
|
||||
|
||||
const primeiroItem = todosItens[0];
|
||||
const ofNumber = 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 ofNumber = ofSelecionada || primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number || 'N/A';
|
||||
const etapaFase = faseSelecionada || primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase || 'N/A';
|
||||
|
||||
const dataAtual = new Date().toLocaleDateString('pt-BR');
|
||||
|
||||
|
||||
@@ -420,168 +420,10 @@ const PrioridadesFabricacao = () => {
|
||||
isOpen={showPrioridadesPDF}
|
||||
onClose={() => setShowPrioridadesPDF(false)}
|
||||
itensPorPrioridade={itensPorPrioridade}
|
||||
ofSelecionada={ofSelecionada}
|
||||
faseSelecionada={faseSelecionada}
|
||||
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>
|
||||
</StandardPageLayout>
|
||||
);
|
||||
|
||||
+86
-103
@@ -2,75 +2,79 @@ 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 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
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
// 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';
|
||||
|
||||
// Garantir que o elemento esteja visível e com dimensões corretas
|
||||
const originalDisplay = element.style.display;
|
||||
const originalVisibility = element.style.visibility;
|
||||
const originalPosition = element.style.position;
|
||||
|
||||
element.style.display = 'block';
|
||||
element.style.visibility = 'visible';
|
||||
element.style.position = 'relative';
|
||||
|
||||
// Forçar um reflow
|
||||
element.offsetHeight;
|
||||
|
||||
// Aguardar mais um pouco após forçar o reflow
|
||||
// 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));
|
||||
|
||||
console.log('Gerando PDF para elemento:', elementId);
|
||||
console.log('Dimensões do elemento:', {
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight,
|
||||
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');
|
||||
}
|
||||
// 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(element, {
|
||||
const canvas = await html2canvas(targetElement, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: false,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff',
|
||||
width: element.scrollWidth,
|
||||
height: element.scrollHeight,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
windowWidth: Math.max(element.scrollWidth, 1200),
|
||||
windowHeight: Math.max(element.scrollHeight, 800),
|
||||
foreignObjectRendering: false,
|
||||
removeContainer: false,
|
||||
imageTimeout: 10000,
|
||||
logging: false
|
||||
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';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Canvas criado com sucesso:', {
|
||||
width: canvas.width,
|
||||
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');
|
||||
if (!canvas || canvas.width === 0 || canvas.height === 0) {
|
||||
throw new Error('Não foi possível capturar o layout visual para o PDF');
|
||||
}
|
||||
|
||||
// Restaurar estilos originais
|
||||
element.style.display = originalDisplay;
|
||||
element.style.visibility = originalVisibility;
|
||||
element.style.position = originalPosition;
|
||||
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({
|
||||
@@ -85,10 +89,10 @@ export const generateProfessionalPDF = async (elementId: string, filename: strin
|
||||
const pageHeight = 297;
|
||||
|
||||
// Margens adequadas
|
||||
const marginTop = 15;
|
||||
const marginBottom = 15;
|
||||
const marginLeft = 15;
|
||||
const marginRight = 15;
|
||||
const marginTop = 12;
|
||||
const marginBottom = 12;
|
||||
const marginLeft = 12;
|
||||
const marginRight = 12;
|
||||
|
||||
// Área útil para conteúdo
|
||||
const contentWidth = pageWidth - marginLeft - marginRight;
|
||||
@@ -98,103 +102,82 @@ export const generateProfessionalPDF = async (elementId: string, filename: strin
|
||||
const imgWidth = contentWidth;
|
||||
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
|
||||
let currentY = 0;
|
||||
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
|
||||
const addFooter = (pageNum: number, totalPages: number) => {
|
||||
const addFooter = (pageNum: number, totalPagesCount: number) => {
|
||||
pdf.setFontSize(8);
|
||||
pdf.setTextColor(100, 100, 100);
|
||||
const footerText = `Página ${pageNum} de ${totalPages}`;
|
||||
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 - 8;
|
||||
const footerY = pageHeight - 6;
|
||||
pdf.text(footerText, footerX, footerY);
|
||||
};
|
||||
|
||||
// Primeira página
|
||||
if (imgHeight <= contentHeight) {
|
||||
// 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);
|
||||
} else {
|
||||
// Conteúdo precisa de múltiplas páginas
|
||||
// Conteúdo precisa de múltiplas páginas com corte preciso
|
||||
while (currentY < imgHeight) {
|
||||
if (pageNumber > 1) {
|
||||
pdf.addPage();
|
||||
}
|
||||
|
||||
// Calcular a altura restante do conteúdo
|
||||
const remainingHeight = imgHeight - currentY;
|
||||
const currentPageHeight = Math.min(contentHeight, remainingHeight);
|
||||
|
||||
// Criar um canvas temporário para a seção atual
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
if (tempCtx) {
|
||||
if (tempCtx && currentPageHeight > 0) {
|
||||
const sliceHeight = Math.round((currentPageHeight * canvas.width) / imgWidth);
|
||||
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(
|
||||
canvas,
|
||||
0,
|
||||
(currentY * canvas.width) / imgWidth,
|
||||
Math.round((currentY * canvas.width) / imgWidth),
|
||||
canvas.width,
|
||||
tempCanvas.height,
|
||||
sliceHeight,
|
||||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
tempCanvas.height
|
||||
sliceHeight
|
||||
);
|
||||
|
||||
// Converter para dados de imagem
|
||||
const tempImgData = tempCanvas.toDataURL('image/png', 1.0);
|
||||
|
||||
// Adicionar a seção ao PDF
|
||||
pdf.addImage(tempImgData, 'PNG', marginLeft, marginTop, imgWidth, currentPageHeight);
|
||||
if (tempImgData && tempImgData.startsWith('data:image/png;base64,')) {
|
||||
pdf.addImage(tempImgData, 'PNG', marginLeft, marginTop, imgWidth, currentPageHeight, undefined, 'FAST');
|
||||
}
|
||||
}
|
||||
|
||||
// Adicionar rodapé
|
||||
addFooter(pageNumber, totalPages);
|
||||
|
||||
// Preparar para próxima página
|
||||
currentY += contentHeight;
|
||||
pageNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
// Forçar o download do PDF
|
||||
console.log('Iniciando download do PDF:', filename);
|
||||
// Salvar arquivo PDF
|
||||
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;
|
||||
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user