🚀 Initial commit: Versão atual do TrackSteel APP

This commit is contained in:
2026-03-18 21:17:53 +00:00
commit bde410c9ad
633 changed files with 108150 additions and 0 deletions
+293
View File
@@ -0,0 +1,293 @@
import React, { useEffect } from 'react';
import { CronogramaOf } from '@/hooks/useCronogramas';
import { useBrandSettings } from '@/hooks/useBrandSettings';
import jsPDF from 'jspdf';
import { format, differenceInDays, parseISO } from 'date-fns';
import { ptBR } from 'date-fns/locale';
interface CronogramaPDFProps {
cronograma: CronogramaOf;
onComplete?: () => void;
}
export const CronogramaPDF: React.FC<CronogramaPDFProps> = ({ cronograma, onComplete }) => {
const { brandSettings } = useBrandSettings();
const calcularDiasCorridos = (dataInicio: string, dataFim: string) => {
return differenceInDays(parseISO(dataFim), parseISO(dataInicio)) + 1;
};
const gerarPDF = async () => {
const doc = new jsPDF('portrait', 'mm', 'a4');
// Configurações de cores (tons profissionais em cinza)
const corCinzaClaro = [200, 200, 200]; // Cinza claro para cabeçalho
const corCinzaMedio = [150, 150, 150]; // Cinza médio
const corCinzaEscuro = [80, 80, 80]; // Cinza escuro para texto
const corBranco = [255, 255, 255]; // Branco
const pageWidth = doc.internal.pageSize.width;
const pageHeight = doc.internal.pageSize.height;
const margin = 15;
const usableWidth = pageWidth - (margin * 2);
let yPosition = margin;
// CABEÇALHO COMPACTO
// Fundo do cabeçalho - altura reduzida para 25px
doc.setFillColor(corCinzaClaro[0], corCinzaClaro[1], corCinzaClaro[2]);
doc.rect(0, 0, pageWidth, 25, 'F');
// Logo da empresa (tamanho reduzido)
if (brandSettings.logo_url) {
try {
const img = new Image();
img.crossOrigin = 'anonymous';
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = brandSettings.logo_url!;
});
// Logo menor - 20x15
const logoWidth = 20;
const logoHeight = 15;
doc.addImage(img, 'PNG', margin, 5, logoWidth, logoHeight);
} catch (error) {
console.log('Erro ao carregar logo:', error);
}
}
// Nome da empresa e título
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.text(brandSettings.company_name, brandSettings.logo_url ? margin + 25 : margin, 12);
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.text('CRONOGRAMA DE PRODUÇÃO', brandSettings.logo_url ? margin + 25 : margin, 18);
yPosition = 35;
// TÍTULO DO CRONOGRAMA
doc.setFillColor(240, 240, 240);
doc.rect(margin, yPosition - 3, usableWidth, 12, 'F');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
const titulo = `OF: ${cronograma.ordem_fabricacao?.num_of} - Revisão: ${cronograma.revisao}`;
doc.text(titulo, margin + 3, yPosition + 3);
doc.setFontSize(9);
doc.setFont('helvetica', 'normal');
doc.text(`Gestor: ${cronograma.gestor_profile?.full_name || 'N/A'}`, margin + 3, yPosition + 8);
yPosition += 18;
// RESUMO EXECUTIVO COMPACTO
const todasAsDatas = cronograma.processos.flatMap(p => [p.data_inicio, p.data_fim]);
const dataInicioTotal = new Date(Math.min(...todasAsDatas.map(d => new Date(d).getTime())));
const dataFimTotal = new Date(Math.max(...todasAsDatas.map(d => new Date(d).getTime())));
const duracaoTotal = differenceInDays(dataFimTotal, dataInicioTotal) + 1;
// Boxes do resumo em linha
const boxWidth = (usableWidth) / 3;
doc.setFillColor(corBranco[0], corBranco[1], corBranco[2]);
doc.setDrawColor(corCinzaMedio[0], corCinzaMedio[1], corCinzaMedio[2]);
// Box 1
doc.rect(margin, yPosition, boxWidth - 2, 15, 'FD');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text(`${cronograma.processos.length} Processos`, margin + 3, yPosition + 6);
doc.setFontSize(8);
doc.setFont('helvetica', 'normal');
doc.text('Total', margin + 3, yPosition + 11);
// Box 2
doc.rect(margin + boxWidth, yPosition, boxWidth - 2, 15, 'FD');
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text(`${duracaoTotal} dias`, margin + boxWidth + 3, yPosition + 6);
doc.setFontSize(8);
doc.setFont('helvetica', 'normal');
doc.text('Duração', margin + boxWidth + 3, yPosition + 11);
// Box 3
doc.rect(margin + boxWidth * 2, yPosition, boxWidth - 2, 15, 'FD');
doc.setFontSize(8);
doc.setFont('helvetica', 'bold');
doc.text(`${format(dataInicioTotal, 'dd/MM', { locale: ptBR })} - ${format(dataFimTotal, 'dd/MM', { locale: ptBR })}`, margin + boxWidth * 2 + 3, yPosition + 6);
doc.setFont('helvetica', 'normal');
doc.text('Período', margin + boxWidth * 2 + 3, yPosition + 11);
yPosition += 22;
// TABELA COMPACTA
doc.setFillColor(corCinzaClaro[0], corCinzaClaro[1], corCinzaClaro[2]);
doc.rect(margin, yPosition, usableWidth, 8, 'F');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text('CRONOGRAMA DETALHADO', margin + 3, yPosition + 5);
yPosition += 10;
// Cabeçalho da tabela - altura reduzida
const colWidths = [usableWidth * 0.4, usableWidth * 0.2, usableWidth * 0.2, usableWidth * 0.2];
const headers = ['Processo', 'Início', 'Fim', 'Duração'];
doc.setFillColor(245, 245, 245);
doc.rect(margin, yPosition, usableWidth, 7, 'F');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(9);
doc.setFont('helvetica', 'bold');
let xPosition = margin;
headers.forEach((header, index) => {
doc.text(header, xPosition + 2, yPosition + 5);
xPosition += colWidths[index];
});
yPosition += 7;
// Linhas da tabela - altura reduzida em 40% (de 12 para 7.2)
doc.setFont('helvetica', 'normal');
doc.setFontSize(8);
cronograma.processos
.sort((a, b) => (a.ordem || 0) - (b.ordem || 0))
.forEach((processo, index) => {
// Alternância de cores
if (index % 2 === 0) {
doc.setFillColor(250, 250, 250);
doc.rect(margin, yPosition, usableWidth, 7, 'F');
}
xPosition = margin;
const rowData = [
processo.nome_processo.length > 25 ? processo.nome_processo.substring(0, 25) + '...' : processo.nome_processo,
format(parseISO(processo.data_inicio), 'dd/MM', { locale: ptBR }),
format(parseISO(processo.data_fim), 'dd/MM', { locale: ptBR }),
`${calcularDiasCorridos(processo.data_inicio, processo.data_fim)}d`
];
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
rowData.forEach((data, colIndex) => {
doc.text(data, xPosition + 2, yPosition + 5);
xPosition += colWidths[colIndex];
});
yPosition += 7;
});
yPosition += 10;
// GRÁFICO DE GANTT VISUAL MELHORADO
doc.setFillColor(corCinzaClaro[0], corCinzaClaro[1], corCinzaClaro[2]);
doc.rect(margin, yPosition, usableWidth, 8, 'F');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.text('LINHA DO TEMPO VISUAL', margin + 3, yPosition + 5);
yPosition += 12;
// Escala de tempo
doc.setTextColor(corCinzaMedio[0], corCinzaMedio[1], corCinzaMedio[2]);
doc.setFontSize(8);
doc.text(format(dataInicioTotal, 'dd/MM/yy', { locale: ptBR }), margin, yPosition - 2);
doc.text(format(dataFimTotal, 'dd/MM/yy', { locale: ptBR }), margin + usableWidth - 20, yPosition - 2);
doc.text(`${duracaoTotal} dias`, margin + usableWidth/2 - 10, yPosition - 2);
// Linha de base
doc.setDrawColor(corCinzaMedio[0], corCinzaMedio[1], corCinzaMedio[2]);
doc.line(margin, yPosition, margin + usableWidth, yPosition);
yPosition += 3;
// Barras dos processos - área reservada para nomes maior
const cores = [
[52, 152, 219], // Azul
[46, 204, 113], // Verde
[241, 196, 15], // Amarelo
[155, 89, 182], // Roxo
[231, 76, 60], // Vermelho
[230, 126, 34], // Laranja
[26, 188, 156], // Turquesa
[127, 140, 141] // Cinza
];
const nomeAreaWidth = 60; // Área reservada para nomes dos processos
const graficoWidth = usableWidth - nomeAreaWidth - 5;
cronograma.processos
.sort((a, b) => (a.ordem || 0) - (b.ordem || 0))
.forEach((processo, index) => {
const diasDoInicio = differenceInDays(parseISO(processo.data_inicio), dataInicioTotal);
const duracaoProcesso = calcularDiasCorridos(processo.data_inicio, processo.data_fim);
const barraInicio = margin + nomeAreaWidth + (diasDoInicio / duracaoTotal) * graficoWidth;
const barraLargura = Math.max(2, (duracaoProcesso / duracaoTotal) * graficoWidth);
// Nome do processo na área reservada
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(7);
doc.setFont('helvetica', 'normal');
const nomeProcesso = processo.nome_processo.length > 20 ?
processo.nome_processo.substring(0, 20) + '...' :
processo.nome_processo;
doc.text(nomeProcesso, margin, yPosition + 2);
// Barra colorida
const cor = cores[index % cores.length];
doc.setFillColor(cor[0], cor[1], cor[2]);
doc.rect(barraInicio, yPosition - 1, barraLargura, 6, 'F');
// Duração na barra (se houver espaço)
if (barraLargura > 8) {
doc.setTextColor(255, 255, 255);
doc.setFontSize(6);
doc.setFont('helvetica', 'bold');
doc.text(`${duracaoProcesso}d`, barraInicio + barraLargura/2 - 2, yPosition + 2);
}
yPosition += 8;
});
// RODAPÉ COMPACTO
doc.setFillColor(corCinzaClaro[0], corCinzaClaro[1], corCinzaClaro[2]);
doc.rect(0, pageHeight - 15, pageWidth, 15, 'F');
doc.setTextColor(corCinzaEscuro[0], corCinzaEscuro[1], corCinzaEscuro[2]);
doc.setFontSize(7);
doc.setFont('helvetica', 'normal');
doc.text(`${brandSettings.company_name} - Sistema de Gestão`, margin, pageHeight - 8);
doc.text(`${format(new Date(), 'dd/MM/yyyy HH:mm')}`, pageWidth - 35, pageHeight - 8);
// Download do PDF
const nomeArquivo = `cronograma_${cronograma.ordem_fabricacao?.num_of}_rev${cronograma.revisao}_${format(new Date(), 'ddMMyyyy')}.pdf`;
doc.save(nomeArquivo);
if (onComplete) {
onComplete();
}
};
useEffect(() => {
const timer = setTimeout(() => {
gerarPDF();
}, 100);
return () => clearTimeout(timer);
}, [cronograma, onComplete, brandSettings]);
return null;
};