diff --git a/src/components/prioridades/PrioridadesPDF.tsx b/src/components/prioridades/PrioridadesPDF.tsx index 8d0c7e7..51e4c0f 100644 --- a/src/components/prioridades/PrioridadesPDF.tsx +++ b/src/components/prioridades/PrioridadesPDF.tsx @@ -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 = ({ 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 = ({ - - Visualizar Checklist de Produção - + + Visualizar Checklist de Produção +
+ + +
diff --git a/src/components/prioridades/PrioridadesPDFTemplate.tsx b/src/components/prioridades/PrioridadesPDFTemplate.tsx index 30d4f64..8f77a3e 100644 --- a/src/components/prioridades/PrioridadesPDFTemplate.tsx +++ b/src/components/prioridades/PrioridadesPDFTemplate.tsx @@ -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 = ({ 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'); diff --git a/src/pages/PrioridadesFabricacao.tsx b/src/pages/PrioridadesFabricacao.tsx index d574350..973b8cb 100644 --- a/src/pages/PrioridadesFabricacao.tsx +++ b/src/pages/PrioridadesFabricacao.tsx @@ -420,168 +420,10 @@ const PrioridadesFabricacao = () => { isOpen={showPrioridadesPDF} onClose={() => setShowPrioridadesPDF(false)} itensPorPrioridade={itensPorPrioridade} + ofSelecionada={ofSelecionada} + faseSelecionada={faseSelecionada} versaoAtual={versaoAtual} /> - - {/* Componente oculto para impressão */} -
-
- {(ofSelecionada && faseSelecionada) && ( -
- {/* Usar o mesmo template do PDF */} -
-
-

Checklist de Produção

-

Formulário para apontamento da fabricação.

-
-
-

- Data de Emissão: {new Date().toLocaleDateString('pt-BR')} - {versaoAtual && ( - Rev. {versaoAtual.revisao} - )} -

-
-
- -
-
-
-

Ordem de Fabricação (OF)

-

{ofSelecionada}

-
-
-

Fase

-

{faseSelecionada}

-
-
-

PROCESSO

-
- {['Corte', 'Solda', 'Pintura', 'Expedição'].map((processo) => ( -
-
- {processo} -
- ))} -
-
-
-
- -
- Legenda: - Marca (Qtd) - (S/M) - = Sem Montagem, - (C/M) - = Com Montagem. Os quadrados indicam o controle de peças fabricadas. -
- - {/* Renderizar os itens por prioridade */} -
- {['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 ( -
-

- {getPrioridadeNome(codigo)} -

- -
- {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 ( -
- {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( -
- 5 -
- ); - } - - for (let i = 0; i < numSmallBoxes; i++) { - boxes.push(
); - } - } else { - for (let i = 0; i < quantity; i++) { - boxes.push(
); - } - } - - return
{boxes}
; - }; - - return ( -
-
- - {marca} ({quantidade}) - - {infoType} - {generateTickBoxes(quantidade)} -
-
-
Data/Operador:
-
-
- ); - })} - - {/* Preencher células vazias se necessário */} - {Array.from({ length: 3 - rowItems.length }, (_, emptyIndex) => ( -
- ))} -
- ); - })} -
-
- ); - })} -
-
- )} -
-
); diff --git a/src/utils/pdfGenerator.ts b/src/utils/pdfGenerator.ts index 170aee2..8cd652b 100644 --- a/src/utils/pdfGenerator.ts +++ b/src/utils/pdfGenerator.ts @@ -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); + } } };