feat: implement error handling and logging in server hooks to capture and notify on 404 and 500 errors, enhancing server reliability and monitoring
This commit is contained in:
612
apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts
Normal file
612
apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts
Normal file
@@ -0,0 +1,612 @@
|
||||
import jsPDF from 'jspdf';
|
||||
import autoTable from 'jspdf-autotable';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import type { ConvexClient } from 'convex-svelte';
|
||||
import { formatarHoraPonto, formatarDataDDMMAAAA, getTipoRegistroLabel } from '../../ponto';
|
||||
import type { DiaFichaPonto, ResumoPeriodo, TipoDia } from '../tipos';
|
||||
import { processarDadosFichaPonto } from '../processamento';
|
||||
import {
|
||||
adicionarLogo,
|
||||
adicionarCabecalho,
|
||||
adicionarDadosFuncionario,
|
||||
adicionarResumoPeriodo,
|
||||
adicionarSaldosPeriodo,
|
||||
adicionarLegenda,
|
||||
adicionarRodape,
|
||||
type SectionsPDF
|
||||
} from '../../fichaPontoPDF';
|
||||
import { formatarHoras, formatarMinutos } from '../formatacao';
|
||||
import { validarPeriodo } from '../validacao';
|
||||
|
||||
/**
|
||||
* Gera PDF com seleção de seções
|
||||
*/
|
||||
export async function gerarPDFComSelecao(
|
||||
client: ConvexClient,
|
||||
sections: SectionsPDF,
|
||||
funcionarioId: Id<'funcionarios'>,
|
||||
dataInicio: string,
|
||||
dataFim: string,
|
||||
funcionarios: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string }>,
|
||||
logoGovPE: string,
|
||||
onError: (message: string) => void,
|
||||
onSuccess: () => void,
|
||||
setCarregando: (value: boolean) => void
|
||||
): Promise<void> {
|
||||
console.log('[gerarPDFComSelecao] Iniciando geração de PDF', {
|
||||
funcionarioId,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
sections
|
||||
});
|
||||
|
||||
// Verificar se pelo menos uma seção foi selecionada
|
||||
if (!Object.values(sections).some((v) => v)) {
|
||||
console.error('[gerarPDFComSelecao] Nenhuma seção selecionada');
|
||||
onError('Selecione pelo menos uma seção para imprimir');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar período
|
||||
const validacaoPeriodo = validarPeriodo(dataInicio, dataFim);
|
||||
if (!validacaoPeriodo.valido) {
|
||||
console.error('[gerarPDFComSelecao] Período inválido', validacaoPeriodo);
|
||||
onError(validacaoPeriodo.erro || 'Período inválido');
|
||||
return;
|
||||
}
|
||||
|
||||
const funcionario = funcionarios.find((f) => f._id === funcionarioId);
|
||||
if (!funcionario) {
|
||||
console.error('[gerarPDFComSelecao] Funcionário não encontrado', funcionarioId);
|
||||
onError('Funcionário não encontrado');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setCarregando(true);
|
||||
console.log('[gerarPDFComSelecao] Processando dados...');
|
||||
// Processar todos os dados necessários
|
||||
const { dias, resumo, config: configPonto } = await processarDadosFichaPonto(
|
||||
client,
|
||||
funcionarioId,
|
||||
dataInicio,
|
||||
dataFim
|
||||
);
|
||||
|
||||
console.log('[gerarPDFComSelecao] Dados processados', {
|
||||
diasCount: dias.length,
|
||||
resumo,
|
||||
config: configPonto
|
||||
});
|
||||
|
||||
if (dias.length === 0) {
|
||||
console.error('[gerarPDFComSelecao] Nenhum dado encontrado');
|
||||
onError('Nenhum dado encontrado para este funcionário no período selecionado');
|
||||
setCarregando(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = new jsPDF();
|
||||
|
||||
// Logo e cabeçalho
|
||||
let yPosition = await adicionarLogo(doc, logoGovPE);
|
||||
yPosition = adicionarCabecalho(doc, yPosition);
|
||||
|
||||
// Dados do Funcionário
|
||||
if (sections.dadosFuncionario) {
|
||||
yPosition = adicionarDadosFuncionario(doc, yPosition, funcionario, dataInicio, dataFim);
|
||||
}
|
||||
|
||||
// Resumo do Período
|
||||
yPosition = adicionarResumoPeriodo(doc, yPosition, resumo, formatarHoras, formatarMinutos);
|
||||
|
||||
// Saldos do Período
|
||||
yPosition = adicionarSaldosPeriodo(doc, yPosition, resumo, formatarMinutos);
|
||||
|
||||
// Legenda
|
||||
yPosition = adicionarLegenda(doc, yPosition);
|
||||
|
||||
// SEÇÃO: TABELA PRINCIPAL DE REGISTROS
|
||||
if (sections.registrosPonto) {
|
||||
yPosition = gerarTabelaRegistrosPDF(doc, yPosition, dias, configPonto, sections);
|
||||
}
|
||||
|
||||
// SEÇÃO: BANCO DE HORAS
|
||||
if (sections.bancoHoras) {
|
||||
yPosition = await gerarSecaoBancoHorasPDF(
|
||||
doc,
|
||||
yPosition,
|
||||
client,
|
||||
funcionarioId,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
configPonto
|
||||
);
|
||||
}
|
||||
|
||||
// SEÇÃO: INCONSISTÊNCIAS (usando alteracoesGestor)
|
||||
if (sections.alteracoesGestor) {
|
||||
yPosition = gerarSecaoInconsistenciasPDF(doc, yPosition, dias);
|
||||
}
|
||||
|
||||
// SEÇÃO: AJUSTES (usando alteracoesGestor)
|
||||
if (sections.alteracoesGestor) {
|
||||
yPosition = gerarSecaoAjustesPDF(doc, yPosition, dias);
|
||||
}
|
||||
|
||||
// SEÇÃO: DISPENSAS
|
||||
if (sections.dispensasRegistro) {
|
||||
yPosition = gerarSecaoDispensasPDF(doc, yPosition, dias);
|
||||
}
|
||||
|
||||
// Rodapé
|
||||
adicionarRodape(doc);
|
||||
|
||||
// Salvar
|
||||
const nomeArquivo = `ficha-ponto-${funcionario.matricula || funcionario.nome}-${dataInicio}-${dataFim}.pdf`;
|
||||
console.log('[gerarPDFComSelecao] Salvando PDF:', nomeArquivo);
|
||||
doc.save(nomeArquivo);
|
||||
|
||||
console.log('[gerarPDFComSelecao] PDF gerado com sucesso');
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
console.error('Erro ao gerar PDF:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Mensagens de erro mais específicas
|
||||
if (errorMessage.includes('Configuração de ponto não encontrada')) {
|
||||
onError('Configuração de ponto não encontrada. Entre em contato com o administrador.');
|
||||
} else if (errorMessage.includes('Nenhum dado encontrado')) {
|
||||
onError('Nenhum dado encontrado para este funcionário no período selecionado.');
|
||||
} else if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
|
||||
onError('Tempo de geração excedido. Tente um período menor (máximo 90 dias).');
|
||||
} else {
|
||||
onError(`Erro ao gerar ficha de ponto: ${errorMessage}`);
|
||||
}
|
||||
} finally {
|
||||
setCarregando(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera tabela de registros de ponto no PDF
|
||||
*/
|
||||
function gerarTabelaRegistrosPDF(
|
||||
doc: jsPDF,
|
||||
yPosition: number,
|
||||
dias: DiaFichaPonto[],
|
||||
config: {
|
||||
horarioEntrada: string;
|
||||
horarioSaidaAlmoco: string;
|
||||
horarioRetornoAlmoco: string;
|
||||
horarioSaida: string;
|
||||
nomeEntrada?: string;
|
||||
nomeSaidaAlmoco?: string;
|
||||
nomeRetornoAlmoco?: string;
|
||||
nomeSaida?: string;
|
||||
},
|
||||
sections: SectionsPDF
|
||||
): number {
|
||||
if (yPosition > 250) {
|
||||
doc.addPage();
|
||||
yPosition = 20;
|
||||
}
|
||||
|
||||
doc.setFontSize(14);
|
||||
doc.setTextColor(41, 128, 185);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text('REGISTROS DE PONTO', 15, yPosition);
|
||||
yPosition += 10;
|
||||
|
||||
// Função auxiliar para obter cor de fundo baseada no tipo de dia
|
||||
const obterCorFundoTipoDia = (tipoDia: TipoDia): number[] => {
|
||||
switch (tipoDia) {
|
||||
case 'atestado':
|
||||
return [230, 240, 255]; // Azul claro
|
||||
case 'ausencia':
|
||||
return [255, 255, 230]; // Amarelo claro
|
||||
case 'abonado':
|
||||
return [230, 255, 230]; // Verde claro
|
||||
case 'nao_computado':
|
||||
return [240, 240, 240]; // Cinza claro
|
||||
case 'inconsistente':
|
||||
return [255, 240, 230]; // Laranja claro
|
||||
default:
|
||||
return [255, 255, 255]; // Branco
|
||||
}
|
||||
};
|
||||
|
||||
// Função auxiliar para obter ícone do tipo de dia
|
||||
const obterIconeTipoDia = (dia: DiaFichaPonto): string => {
|
||||
if (dia.atestado) return '🏥';
|
||||
if (dia.ausencia) return '🚫';
|
||||
if (dia.licenca) return '📋';
|
||||
if (dia.tipoDia === 'abonado') return '✅';
|
||||
if (dia.tipoDia === 'nao_computado') return '⏸';
|
||||
if (dia.inconsistencias.length > 0) return '⚠';
|
||||
return '';
|
||||
};
|
||||
|
||||
// Preparar dados da tabela
|
||||
const tableData: Array<
|
||||
Array<
|
||||
| string
|
||||
| {
|
||||
content: string;
|
||||
styles?: { fillColor?: number[]; textColor?: number[]; fontStyle?: string };
|
||||
}
|
||||
>
|
||||
> = [];
|
||||
|
||||
for (const dia of dias) {
|
||||
const dataFormatada = dia.dataFormatada;
|
||||
const todosRegistros = [
|
||||
...dia.registros.map((r) => ({ ...r, real: true })),
|
||||
...dia.registrosEsperados
|
||||
.filter((re) => !dia.registros.some((r) => r.tipo === re.tipo))
|
||||
.map((re) => ({ ...re, real: false }))
|
||||
].sort((a, b) => {
|
||||
if (a.hora !== b.hora) return a.hora - b.hora;
|
||||
return a.minuto - b.minuto;
|
||||
});
|
||||
|
||||
for (let i = 0; i < todosRegistros.length; i++) {
|
||||
const reg = todosRegistros[i];
|
||||
const linha: Array<
|
||||
string | { content: string; styles?: { fillColor?: number[]; textColor?: number[]; fontStyle?: string } }
|
||||
> = [];
|
||||
|
||||
// Coluna Data (apenas na primeira linha)
|
||||
if (i === 0) {
|
||||
linha.push({
|
||||
content: `${dataFormatada} ${obterIconeTipoDia(dia)}`,
|
||||
styles: {
|
||||
fillColor: obterCorFundoTipoDia(dia.tipoDia),
|
||||
fontStyle: 'bold'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
linha.push('');
|
||||
}
|
||||
|
||||
// Coluna Tipo
|
||||
const tipoLabel = config
|
||||
? getTipoRegistroLabel(reg.tipo as 'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida', {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida
|
||||
})
|
||||
: getTipoRegistroLabel(reg.tipo as 'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida');
|
||||
|
||||
if (!('real' in reg) || reg.real) {
|
||||
linha.push(tipoLabel);
|
||||
} else {
|
||||
linha.push({
|
||||
content: tipoLabel,
|
||||
styles: { textColor: [200, 0, 0] } // Vermelho para não marcado
|
||||
});
|
||||
}
|
||||
|
||||
// Coluna Horário
|
||||
const horario = formatarHoraPonto(reg.hora, reg.minuto);
|
||||
if (!('real' in reg) || reg.real) {
|
||||
linha.push(horario);
|
||||
} else {
|
||||
linha.push({
|
||||
content: horario,
|
||||
styles: { textColor: [200, 0, 0] } // Vermelho para não marcado
|
||||
});
|
||||
}
|
||||
|
||||
// Coluna Saldo Diário (se seção selecionada)
|
||||
if (sections.saldoDiario) {
|
||||
if (i === 0 && dia.saldoDiario) {
|
||||
const saldoFormatado = formatarMinutos(dia.saldoDiario.diferencaMinutos);
|
||||
const corSaldo = dia.saldoDiario.diferencaMinutos < 0 ? [200, 0, 0] : [0, 128, 0];
|
||||
linha.push({
|
||||
content: saldoFormatado,
|
||||
styles: { textColor: corSaldo, fontStyle: 'bold' }
|
||||
});
|
||||
} else {
|
||||
linha.push('');
|
||||
}
|
||||
}
|
||||
|
||||
// Coluna Observações (apenas na primeira linha)
|
||||
if (i === 0) {
|
||||
const observacoes: string[] = [];
|
||||
if (dia.atestado) {
|
||||
observacoes.push(`Atestado: ${dia.atestado.tipo}`);
|
||||
}
|
||||
if (dia.ausencia) {
|
||||
observacoes.push(`Ausência: ${dia.ausencia.motivo}`);
|
||||
}
|
||||
if (dia.licenca) {
|
||||
observacoes.push(`Licença: ${dia.licenca.tipo}`);
|
||||
}
|
||||
if (dia.dispensa) {
|
||||
observacoes.push(`Dispensa: ${dia.dispensa.motivo}`);
|
||||
}
|
||||
if (dia.inconsistencias.length > 0) {
|
||||
observacoes.push(`Inconsistências: ${dia.inconsistencias.length}`);
|
||||
}
|
||||
if (dia.ajustes.length > 0) {
|
||||
observacoes.push(
|
||||
`Ajustes: ${dia.ajustes.map((a) => `${a.tipo} ${formatarMinutos(a.valorMinutos)}`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
linha.push(observacoes.join('; ') || '-');
|
||||
} else {
|
||||
linha.push('');
|
||||
}
|
||||
|
||||
// Coluna Dentro do Prazo
|
||||
if ('real' in reg && reg.real && 'dentroDoPrazo' in reg) {
|
||||
linha.push(reg.dentroDoPrazo ? 'Sim' : 'Não');
|
||||
} else {
|
||||
linha.push('Não marcado');
|
||||
}
|
||||
|
||||
tableData.push(linha);
|
||||
}
|
||||
}
|
||||
|
||||
// Cabeçalhos da tabela
|
||||
const headers = ['Data', 'Tipo', 'Horário'];
|
||||
if (sections.saldoDiario) {
|
||||
headers.push('Saldo Diário');
|
||||
}
|
||||
headers.push('Observações', 'Dentro do Prazo');
|
||||
|
||||
// Adicionar tabela
|
||||
autoTable(doc, {
|
||||
startY: yPosition,
|
||||
head: [headers],
|
||||
body: tableData,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||
styles: { fontSize: 9 },
|
||||
didParseCell: function (data: any) {
|
||||
if (data.section === 'body' && data.cell.raw) {
|
||||
const cellData = data.cell.raw;
|
||||
if (typeof cellData === 'object' && cellData.styles) {
|
||||
if (cellData.styles.fillColor) {
|
||||
data.cell.styles.fillColor = cellData.styles.fillColor;
|
||||
}
|
||||
if (cellData.styles.textColor) {
|
||||
data.cell.styles.textColor = cellData.styles.textColor;
|
||||
}
|
||||
if (cellData.styles.fontStyle) {
|
||||
data.cell.styles.fontStyle = cellData.styles.fontStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Calcular nova posição Y
|
||||
const lastPage = doc.getNumberOfPages();
|
||||
doc.setPage(lastPage);
|
||||
type JsPDFWithAutoTable = jsPDF & {
|
||||
lastAutoTable?: { finalY: number };
|
||||
};
|
||||
const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY;
|
||||
return finalY ? finalY + 10 : yPosition + tableData.length * 7 + 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera seção de banco de horas no PDF
|
||||
*/
|
||||
async function gerarSecaoBancoHorasPDF(
|
||||
doc: jsPDF,
|
||||
yPosition: number,
|
||||
client: ConvexClient,
|
||||
funcionarioId: Id<'funcionarios'>,
|
||||
dataInicio: string,
|
||||
dataFim: string,
|
||||
config: {
|
||||
horarioEntrada: string;
|
||||
horarioSaidaAlmoco: string;
|
||||
horarioRetornoAlmoco: string;
|
||||
horarioSaida: string;
|
||||
}
|
||||
): Promise<number> {
|
||||
if (yPosition > doc.internal.pageSize.getHeight() - 60) {
|
||||
doc.addPage();
|
||||
yPosition = 20;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(41, 128, 185);
|
||||
doc.text('BANCO DE HORAS', 15, yPosition);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(0, 0, 0);
|
||||
yPosition += 10;
|
||||
|
||||
// Buscar banco de horas
|
||||
const { api } = await import('@sgse-app/backend/convex/_generated/api');
|
||||
const bancoHoras = await client.query(api.pontos.obterBancoHorasFuncionario, {
|
||||
funcionarioId
|
||||
});
|
||||
|
||||
if (bancoHoras) {
|
||||
const bancoData = [
|
||||
['Saldo Atual', formatarMinutos(bancoHoras.saldoAtualMinutos || 0)],
|
||||
['Saldo Inicial', formatarMinutos(bancoHoras.saldoInicialMinutos || 0)],
|
||||
['Saldo Final', formatarMinutos(bancoHoras.saldoFinalMinutos || 0)]
|
||||
];
|
||||
|
||||
autoTable(doc, {
|
||||
startY: yPosition,
|
||||
head: [['Campo', 'Valor']],
|
||||
body: bancoData,
|
||||
theme: 'striped',
|
||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||
styles: { fontSize: 10 }
|
||||
});
|
||||
|
||||
type JsPDFWithAutoTable = jsPDF & {
|
||||
lastAutoTable?: { finalY: number };
|
||||
};
|
||||
const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY;
|
||||
return finalY ? finalY + 10 : yPosition + bancoData.length * 7 + 10;
|
||||
}
|
||||
|
||||
return yPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera seção de inconsistências no PDF
|
||||
*/
|
||||
function gerarSecaoInconsistenciasPDF(doc: jsPDF, yPosition: number, dias: DiaFichaPonto[]): number {
|
||||
const todasInconsistencias = dias.flatMap((dia) =>
|
||||
dia.inconsistencias.map((inc) => ({
|
||||
...inc,
|
||||
data: dia.data,
|
||||
dataFormatada: dia.dataFormatada
|
||||
}))
|
||||
);
|
||||
|
||||
if (todasInconsistencias.length === 0) {
|
||||
return yPosition;
|
||||
}
|
||||
|
||||
if (yPosition > doc.internal.pageSize.getHeight() - 60) {
|
||||
doc.addPage();
|
||||
yPosition = 20;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(41, 128, 185);
|
||||
doc.text('INCONSISTÊNCIAS', 15, yPosition);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(0, 0, 0);
|
||||
yPosition += 10;
|
||||
|
||||
const inconsistenciasData = todasInconsistencias.map((inc) => [
|
||||
formatarDataDDMMAAAA(inc.data),
|
||||
inc.tipo,
|
||||
inc.descricao,
|
||||
inc.status
|
||||
]);
|
||||
|
||||
autoTable(doc, {
|
||||
startY: yPosition,
|
||||
head: [['Data', 'Tipo', 'Descrição', 'Status']],
|
||||
body: inconsistenciasData,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||
styles: { fontSize: 9 }
|
||||
});
|
||||
|
||||
type JsPDFWithAutoTable = jsPDF & {
|
||||
lastAutoTable?: { finalY: number };
|
||||
};
|
||||
const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY;
|
||||
return finalY ? finalY + 10 : yPosition + inconsistenciasData.length * 7 + 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera seção de ajustes no PDF
|
||||
*/
|
||||
function gerarSecaoAjustesPDF(doc: jsPDF, yPosition: number, dias: DiaFichaPonto[]): number {
|
||||
const todosAjustes = dias.flatMap((dia) =>
|
||||
dia.ajustes.map((ajuste) => ({
|
||||
...ajuste,
|
||||
data: dia.data,
|
||||
dataFormatada: dia.dataFormatada
|
||||
}))
|
||||
);
|
||||
|
||||
if (todosAjustes.length === 0) {
|
||||
return yPosition;
|
||||
}
|
||||
|
||||
if (yPosition > doc.internal.pageSize.getHeight() - 60) {
|
||||
doc.addPage();
|
||||
yPosition = 20;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(41, 128, 185);
|
||||
doc.text('AJUSTES DE BANCO DE HORAS', 15, yPosition);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(0, 0, 0);
|
||||
yPosition += 10;
|
||||
|
||||
const ajustesData = todosAjustes.map((ajuste) => [
|
||||
formatarDataDDMMAAAA(ajuste.data),
|
||||
ajuste.tipo === 'abonar' ? 'Abonar' : ajuste.tipo === 'descontar' ? 'Descontar' : 'Compensar',
|
||||
formatarMinutos(ajuste.valorMinutos),
|
||||
ajuste.motivoDescricao || '-'
|
||||
]);
|
||||
|
||||
autoTable(doc, {
|
||||
startY: yPosition,
|
||||
head: [['Data', 'Tipo', 'Valor', 'Motivo']],
|
||||
body: ajustesData,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||
styles: { fontSize: 9 }
|
||||
});
|
||||
|
||||
type JsPDFWithAutoTable = jsPDF & {
|
||||
lastAutoTable?: { finalY: number };
|
||||
};
|
||||
const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY;
|
||||
return finalY ? finalY + 10 : yPosition + ajustesData.length * 7 + 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera seção de dispensas no PDF
|
||||
*/
|
||||
function gerarSecaoDispensasPDF(doc: jsPDF, yPosition: number, dias: DiaFichaPonto[]): number {
|
||||
const dispensas = dias
|
||||
.map((dia) => dia.dispensa)
|
||||
.filter((d): d is NonNullable<typeof d> => d !== null)
|
||||
.filter((d, index, self) => index === self.findIndex((disp) => disp._id === d._id));
|
||||
|
||||
if (dispensas.length === 0) {
|
||||
return yPosition;
|
||||
}
|
||||
|
||||
if (yPosition > doc.internal.pageSize.getHeight() - 60) {
|
||||
doc.addPage();
|
||||
yPosition = 20;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.setTextColor(41, 128, 185);
|
||||
doc.text('DISPENSAS DE REGISTRO', 15, yPosition);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(0, 0, 0);
|
||||
yPosition += 10;
|
||||
|
||||
const dispensasData = dispensas.map((d) => [
|
||||
`${formatarDataDDMMAAAA(d.dataInicio)} a ${formatarDataDDMMAAAA(d.dataFim)}`,
|
||||
d.motivo,
|
||||
d.ativo ? 'Ativa' : 'Inativa'
|
||||
]);
|
||||
|
||||
autoTable(doc, {
|
||||
startY: yPosition,
|
||||
head: [['Período', 'Motivo', 'Status']],
|
||||
body: dispensasData,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
||||
styles: { fontSize: 9 }
|
||||
});
|
||||
|
||||
type JsPDFWithAutoTable = jsPDF & {
|
||||
lastAutoTable?: { finalY: number };
|
||||
};
|
||||
const finalY = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY;
|
||||
return finalY ? finalY + 10 : yPosition + dispensasData.length * 7 + 10;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user