diff --git a/apps/web/src/lib/components/ponto/ComprovantePonto.svelte b/apps/web/src/lib/components/ponto/ComprovantePonto.svelte index 2fa24a9..721b06b 100644 --- a/apps/web/src/lib/components/ponto/ComprovantePonto.svelte +++ b/apps/web/src/lib/components/ponto/ComprovantePonto.svelte @@ -2,6 +2,7 @@ import { useQuery } from 'convex-svelte'; import { api } from '@sgse-app/backend/convex/_generated/api'; import jsPDF from 'jspdf'; + import autoTable from 'jspdf-autotable'; import { Printer, X, User, Clock, CheckCircle2, XCircle, Calendar, MapPin } from 'lucide-svelte'; import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { formatarDataHoraCompleta, getTipoRegistroLabel } from '$lib/utils/ponto'; @@ -109,15 +110,16 @@ const registro = registroQuery.data; const doc = new jsPDF(); - // Logo + // Adicionar logo no canto superior esquerdo let yPosition = 20; try { - const logoImg = new Image(); - logoImg.src = logoGovPE; - await new Promise((resolve, reject) => { - logoImg.onload = () => resolve(); - logoImg.onerror = () => reject(); - setTimeout(() => reject(), 3000); + const logoImg = await new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => resolve(img); + img.onerror = (err) => reject(err); + setTimeout(() => reject(new Error('Timeout loading logo')), 3000); + img.src = logoGovPE; }); const logoWidth = 25; @@ -125,59 +127,75 @@ const logoHeight = logoWidth * aspectRatio; doc.addImage(logoImg, 'PNG', 15, 10, logoWidth, logoHeight); - yPosition = Math.max(20, 10 + logoHeight / 2); + yPosition = 10 + logoHeight + 10; } catch (err) { - console.warn('Não foi possível carregar a logo:', err); + console.warn('Erro ao carregar logo:', err); + yPosition = 20; } - // Cabeçalho + // Cabeçalho padrão do sistema (centralizado) + doc.setFontSize(14); + doc.setFont('helvetica', 'bold'); + doc.setTextColor(0, 0, 0); + doc.text('GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(yPosition - 10, 20), { align: 'center' }); + doc.setFontSize(12); + doc.text('SECRETARIA DE ESPORTES', 105, Math.max(yPosition - 2, 28), { align: 'center' }); + + yPosition = Math.max(yPosition, 40); + yPosition += 10; + + // Título do comprovante doc.setFontSize(16); - doc.setTextColor(41, 128, 185); + doc.setTextColor(102, 126, 234); // Cor primária padrão do sistema + doc.setFont('helvetica', 'bold'); doc.text('COMPROVANTE DE REGISTRO DE PONTO', 105, yPosition, { align: 'center' }); yPosition += 15; - // Informações do Funcionário - doc.setFontSize(12); - doc.setTextColor(0, 0, 0); - doc.setFont('helvetica', 'bold'); - doc.text('DADOS DO FUNCIONÁRIO', 15, yPosition); - doc.setFont('helvetica', 'normal'); - - yPosition += 8; - doc.setFontSize(10); - + // Informações do Funcionário em tabela + const funcionarioData: string[][] = []; + if (registro.funcionario) { if (registro.funcionario.matricula) { - doc.text(`Matrícula: ${registro.funcionario.matricula}`, 15, yPosition); - yPosition += 6; + funcionarioData.push(['Matrícula', registro.funcionario.matricula]); } - doc.text(`Nome: ${registro.funcionario.nome}`, 15, yPosition); - yPosition += 6; + funcionarioData.push(['Nome', registro.funcionario.nome || '-']); if (registro.funcionario.descricaoCargo) { - doc.text(`Cargo/Função: ${registro.funcionario.descricaoCargo}`, 15, yPosition); - yPosition += 6; + funcionarioData.push(['Cargo/Função', registro.funcionario.descricaoCargo]); } if (registro.funcionario.simbolo) { - doc.text( - `Símbolo: ${registro.funcionario.simbolo.nome} (${registro.funcionario.simbolo.tipo === 'cargo_comissionado' ? 'Cargo Comissionado' : 'Função Gratificada'})`, - 15, - yPosition - ); - yPosition += 6; + const simboloTipo = registro.funcionario.simbolo.tipo === 'cargo_comissionado' + ? 'Cargo Comissionado' + : 'Função Gratificada'; + funcionarioData.push(['Símbolo', `${registro.funcionario.simbolo.nome} (${simboloTipo})`]); } } - yPosition += 5; + if (funcionarioData.length > 0) { + doc.setFontSize(12); + doc.setFont('helvetica', 'bold'); + doc.setTextColor(0, 0, 0); + doc.text('DADOS DO FUNCIONÁRIO', 15, yPosition); + yPosition += 8; - // Informações do Registro - doc.setFont('helvetica', 'bold'); - doc.text('DADOS DO REGISTRO', 15, yPosition); - doc.setFont('helvetica', 'normal'); + autoTable(doc, { + startY: yPosition, + head: [['Campo', 'Informação']], + body: funcionarioData, + theme: 'striped', + headStyles: { fillColor: [102, 126, 234] }, + styles: { fontSize: 10 }, + margin: { left: 15, right: 15 } + }); - yPosition += 8; - doc.setFontSize(10); + type JsPDFWithAutoTable = jsPDF & { + lastAutoTable?: { finalY: number }; + }; + const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY ?? yPosition + 10; + yPosition = finalY + 10; + } + // Informações do Registro em tabela const config = configQuery?.data; const tipoLabel = config ? getTipoRegistroLabel(registro.tipo, { @@ -187,25 +205,38 @@ nomeSaida: config.nomeSaida, }) : getTipoRegistroLabel(registro.tipo); - doc.text(`Tipo: ${tipoLabel}`, 15, yPosition); - yPosition += 6; - + const dataHora = formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo); - doc.text(`Data e Hora: ${dataHora}`, 15, yPosition); - yPosition += 6; + + const registroData: string[][] = [ + ['Tipo', tipoLabel], + ['Data e Hora', dataHora], + ['Status', registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo'], + ['Tolerância', `${registro.toleranciaMinutos} minutos`], + ['Sincronizado', registro.sincronizadoComServidor ? 'Sim (Servidor)' : 'Não (PC Local)'] + ]; - doc.text(`Status: ${registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo'}`, 15, yPosition); - yPosition += 6; + doc.setFontSize(12); + doc.setFont('helvetica', 'bold'); + doc.setTextColor(0, 0, 0); + doc.text('DADOS DO REGISTRO', 15, yPosition); + yPosition += 8; - doc.text(`Tolerância: ${registro.toleranciaMinutos} minutos`, 15, yPosition); - yPosition += 6; + autoTable(doc, { + startY: yPosition, + head: [['Campo', 'Informação']], + body: registroData, + theme: 'striped', + headStyles: { fillColor: [102, 126, 234] }, + styles: { fontSize: 10 }, + margin: { left: 15, right: 15 } + }); - doc.text( - `Sincronizado: ${registro.sincronizadoComServidor ? 'Sim (Servidor)' : 'Não (PC Local)'}`, - 15, - yPosition - ); - yPosition += 10; + type JsPDFWithAutoTable2 = jsPDF & { + lastAutoTable?: { finalY: number }; + }; + const finalY2 = (doc as JsPDFWithAutoTable2).lastAutoTable?.finalY ?? yPosition + 10; + yPosition = finalY2 + 10; // Imagem capturada (se disponível) if (registro.imagemUrl) { @@ -216,8 +247,10 @@ yPosition = 20; } + doc.setFontSize(12); doc.setFont('helvetica', 'bold'); - doc.text('FOTO CAPTURADA', 105, yPosition, { align: 'center' }); + doc.setTextColor(0, 0, 0); + doc.text('FOTO CAPTURADA', 15, yPosition); doc.setFont('helvetica', 'normal'); yPosition += 10; diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index a93655c..5c162ca 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -2849,7 +2849,7 @@ {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoAusenciaAprovar } ) then detalhes} {#if detalhes} -