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 { 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,52 +25,19 @@ export const PrioridadesPDF: React.FC<PrioridadesPDFProps> = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
itensPorPrioridade,
|
itensPorPrioridade,
|
||||||
|
ofSelecionada,
|
||||||
|
faseSelecionada,
|
||||||
versaoAtual
|
versaoAtual
|
||||||
}) => {
|
}) => {
|
||||||
const handleGerarPDF = async () => {
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
const elemento = document.getElementById('prioridades-pdf-content');
|
const [isPrinting, setIsPrinting] = useState(false);
|
||||||
|
|
||||||
if (!elemento) {
|
const getNomeArquivo = () => {
|
||||||
console.error('Elemento 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';
|
let nomeArquivo = 'checklist-producao';
|
||||||
|
const todosItens = Object.values(itensPorPrioridade).flat();
|
||||||
if (todosItens.length > 0) {
|
|
||||||
const primeiroItem = todosItens[0];
|
const primeiroItem = todosItens[0];
|
||||||
const of = primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number;
|
const of = ofSelecionada || primeiroItem?.peca?.of_number || primeiroItem?.prioridade_fabricacao?.of_number;
|
||||||
const fase = primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase;
|
const fase = faseSelecionada || primeiroItem?.peca?.etapa_fase || primeiroItem?.prioridade_fabricacao?.etapa_fase;
|
||||||
|
|
||||||
if (of && fase) {
|
if (of && fase) {
|
||||||
nomeArquivo = `checklist-producao-${of}-${fase}`;
|
nomeArquivo = `checklist-producao-${of}-${fase}`;
|
||||||
@@ -76,11 +45,46 @@ export const PrioridadesPDF: React.FC<PrioridadesPDFProps> = ({
|
|||||||
nomeArquivo += `-rev${versaoAtual.revisao}`;
|
nomeArquivo += `-rev${versaoAtual.revisao}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nomeArquivo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGerarPDF = async () => {
|
||||||
|
const elemento = document.getElementById('prioridades-pdf-content');
|
||||||
|
|
||||||
|
if (!elemento) {
|
||||||
|
toast.error('Elemento do relatório não encontrado');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pdf.save(`${nomeArquivo}.pdf`);
|
try {
|
||||||
} 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);
|
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">
|
||||||
|
<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" />
|
<Download className="h-4 w-4 mr-2" />
|
||||||
Baixar PDF
|
Baixar PDF
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</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');
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
+84
-101
@@ -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';
|
||||||
|
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';
|
||||||
|
|
||||||
element.style.display = 'block';
|
tempContainer.appendChild(clonedElement);
|
||||||
element.style.visibility = 'visible';
|
document.body.appendChild(tempContainer);
|
||||||
element.style.position = 'relative';
|
|
||||||
|
|
||||||
// Forçar um reflow
|
// Aguardar renderização e computação de layout
|
||||||
element.offsetHeight;
|
|
||||||
|
|
||||||
// Aguardar mais um pouco após forçar o reflow
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user