613 lines
17 KiB
TypeScript
613 lines
17 KiB
TypeScript
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);
|
|
}
|
|
|
|
// SEÇÃO: TABELA PRINCIPAL DE REGISTROS (PRIMEIRO)
|
|
if (sections.registrosPonto) {
|
|
yPosition = gerarTabelaRegistrosPDF(doc, yPosition, dias, configPonto, sections);
|
|
}
|
|
|
|
// 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: 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;
|
|
}
|
|
|