feat: enhance ComprovantePonto component by adding logo support and restructuring document layout with auto-generated tables for employee and registration data, improving PDF output clarity and presentation

This commit is contained in:
2025-11-30 15:40:58 -03:00
parent 3204440a38
commit e43f9fcf14
2 changed files with 90 additions and 57 deletions

View File

@@ -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<void>((resolve, reject) => {
logoImg.onload = () => resolve();
logoImg.onerror = () => reject();
setTimeout(() => reject(), 3000);
const logoImg = await new Promise<HTMLImageElement>((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;