From 5369a2ecc9ac5674e2d28e43c69d741203a474e6 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Tue, 23 Dec 2025 19:39:10 -0300 Subject: [PATCH] feat: update PDF generation to use symbols for day types and implement confirmation modal for record deletion in absence and license management, enhancing user experience and data integrity --- .../web/src/lib/utils/ponto/pdf/geradorPDF.ts | 20 ++--- .../atestados-licencas/+page.svelte | 62 ++++++++++++++-- packages/backend/convex/atestadosLicencas.ts | 74 ++++++++++++++++++- 3 files changed, 135 insertions(+), 21 deletions(-) diff --git a/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts b/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts index 13868f3..a87a0f0 100644 --- a/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts +++ b/apps/web/src/lib/utils/ponto/pdf/geradorPDF.ts @@ -216,14 +216,14 @@ function gerarTabelaRegistrosPDF( } }; - // 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 '⚠'; + // Função auxiliar para obter símbolo do tipo de dia + const obterSimboloTipoDia = (dia: DiaFichaPonto): string => { + if (dia.atestado) return 'AT'; + if (dia.ausencia) return 'AUS'; + if (dia.licenca) return 'LIC'; + if (dia.tipoDia === 'abonado') return 'ABO'; + if (dia.tipoDia === 'nao_computado') return 'NC'; + if (dia.inconsistencias.length > 0) return 'INC'; return ''; }; @@ -258,8 +258,10 @@ function gerarTabelaRegistrosPDF( // Coluna Data (apenas na primeira linha) if (i === 0) { + const simbolo = obterSimboloTipoDia(dia); + const dataComSimbolo = simbolo ? `${dataFormatada} [${simbolo}]` : dataFormatada; linha.push({ - content: `${dataFormatada} ${obterIconeTipoDia(dia)}`, + content: dataComSimbolo, styles: { fillColor: obterCorFundoTipoDia(dia.tipoDia), fontStyle: 'bold' diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte index e102747..f0b4dce 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte @@ -6,6 +6,7 @@ import FuncionarioMatriculaAutocomplete from '$lib/components/FuncionarioMatriculaAutocomplete.svelte'; import FileUpload from '$lib/components/FileUpload.svelte'; import ErrorModal from '$lib/components/ErrorModal.svelte'; + import ConfirmModal from '$lib/components/ConfirmModal.svelte'; import CalendarioAfastamentos from '$lib/components/CalendarioAfastamentos.svelte'; import AreaChart from '$lib/components/ti/charts/AreaChart.svelte'; import UserAvatar from '$lib/components/chat/UserAvatar.svelte'; @@ -132,6 +133,14 @@ titulo: '' }); + // Modal de exclusão + let exclusaoModal = $state({ + aberto: false, + tipo: null as 'atestado' | 'licenca' | null, + id: null as string | null, + nome: '' + }); + // Licenças maternidade para prorrogação (derivar dos dados já carregados) let licencasMaternidade = $derived.by(() => { const dados = dadosQuery?.data; @@ -651,26 +660,42 @@ } }); - // Excluir registro - async function excluirRegistro(tipo: 'atestado' | 'licenca', id: string) { - if (!confirm(`Tem certeza que deseja excluir este ${tipo}?`)) return; + // Abrir modal de exclusão + function abrirModalExclusao(tipo: 'atestado' | 'licenca', id: string, nome: string) { + exclusaoModal = { + aberto: true, + tipo, + id, + nome + }; + } + + // Confirmar exclusão + async function confirmarExclusao() { + if (!exclusaoModal.tipo || !exclusaoModal.id) return; try { - if (tipo === 'atestado') { + if (exclusaoModal.tipo === 'atestado') { await client.mutation(api.atestadosLicencas.excluirAtestado, { - id: id as Id<'atestados'> + id: exclusaoModal.id as Id<'atestados'> }); } else { await client.mutation(api.atestadosLicencas.excluirLicenca, { - id: id as Id<'licencas'> + id: exclusaoModal.id as Id<'licencas'> }); } toast.success('Registro excluído com sucesso!'); + exclusaoModal.aberto = false; } catch (error: unknown) { toast.error(getErrorMessage(error, 'Erro ao excluir registro')); } } + // Cancelar exclusão + function cancelarExclusao() { + exclusaoModal.aberto = false; + } + // Filtrar registros let registrosFiltrados = $derived.by(() => { const dados = dadosQuery?.data; @@ -1677,7 +1702,12 @@ {/if} @@ -1739,7 +1769,12 @@ {/if} @@ -2438,6 +2473,17 @@ }} /> + + + {#if documentoModal.aberto} diff --git a/packages/backend/convex/atestadosLicencas.ts b/packages/backend/convex/atestadosLicencas.ts index 767dda3..469f0bd 100644 --- a/packages/backend/convex/atestadosLicencas.ts +++ b/packages/backend/convex/atestadosLicencas.ts @@ -182,7 +182,7 @@ export const listarTodos = query({ fotoPerfilUrl, criadoPorNome: criadoPor?.nome || 'Sistema', dias: calcularDias(a.dataInicio, a.dataFim), - status: new Date(a.dataFim) >= new Date() ? 'ativo' : 'finalizado' + status: new Date() > new Date(a.dataFim) ? 'finalizado' : 'ativo' }; } catch (error) { console.error('Erro ao buscar detalhes do atestado:', error); @@ -192,7 +192,7 @@ export const listarTodos = query({ fotoPerfilUrl: null, criadoPorNome: 'Sistema', dias: calcularDias(a.dataInicio, a.dataFim), - status: new Date(a.dataFim) >= new Date() ? 'ativo' : 'finalizado' + status: new Date() > new Date(a.dataFim) ? 'finalizado' : 'ativo' }; } }) @@ -226,7 +226,7 @@ export const listarTodos = query({ criadoPorNome: criadoPor?.nome || 'Sistema', licencaOriginal, dias: calcularDias(l.dataInicio, l.dataFim), - status: new Date(l.dataFim) >= new Date() ? 'ativo' : 'finalizado' + status: new Date() > new Date(l.dataFim) ? 'finalizado' : 'ativo' }; } catch (error) { console.error('Erro ao buscar detalhes da licença:', error); @@ -237,7 +237,7 @@ export const listarTodos = query({ criadoPorNome: 'Sistema', licencaOriginal: null, dias: calcularDias(l.dataInicio, l.dataFim), - status: new Date(l.dataFim) >= new Date() ? 'ativo' : 'finalizado' + status: new Date() > new Date(l.dataFim) ? 'finalizado' : 'ativo' }; } }) @@ -1255,6 +1255,32 @@ export const excluirAtestado = mutation({ const dataInicio = atestado.dataInicio; // Data início do atestado const dataFim = atestado.dataFim; // Data fim do atestado const atestadoId = args.id.toString(); // ID do atestado para remover ajustes + const documentoId = atestado.documentoId; // ID do documento para remover do storage + + // Remover logs de atividades relacionados ao atestado + try { + const logs = await ctx.db + .query('logsAtividades') + .withIndex('by_recurso_id', (q) => + q.eq('recurso', 'atestados').eq('recursoId', atestadoId) + ) + .collect(); + for (const log of logs) { + await ctx.db.delete(log._id); + } + } catch (error) { + console.error('[excluirAtestado] Erro ao remover logs de atividades:', error); + } + + // Remover documento do storage se existir + if (documentoId) { + try { + await ctx.storage.delete(documentoId); + } catch (error) { + console.error('[excluirAtestado] Erro ao remover documento do storage:', error); + // Não falhar a exclusão se o documento não existir mais + } + } // Excluir o registro do banco de dados await ctx.db.delete(args.id); @@ -1319,6 +1345,33 @@ export const excluirLicenca = mutation({ const funcionarioId = licenca.funcionarioId; const dataInicio = licenca.dataInicio; // Data início da licença const dataFim = licenca.dataFim; // Data fim da licença + const licencaId = args.id.toString(); // ID da licença para remover logs + const documentoId = licenca.documentoId; // ID do documento para remover do storage + + // Remover logs de atividades relacionados à licença + try { + const logs = await ctx.db + .query('logsAtividades') + .withIndex('by_recurso_id', (q) => + q.eq('recurso', 'licencas').eq('recursoId', licencaId) + ) + .collect(); + for (const log of logs) { + await ctx.db.delete(log._id); + } + } catch (error) { + console.error('[excluirLicenca] Erro ao remover logs de atividades:', error); + } + + // Remover documento do storage se existir + if (documentoId) { + try { + await ctx.storage.delete(documentoId); + } catch (error) { + console.error('[excluirLicenca] Erro ao remover documento do storage:', error); + // Não falhar a exclusão se o documento não existir mais + } + } // Excluir o registro do banco de dados await ctx.db.delete(args.id); @@ -1332,6 +1385,19 @@ export const excluirLicenca = mutation({ args.id ); + // Remover ajustes automáticos relacionados à licença excluída + try { + await ctx.runMutation(internal.pontos.removerAjustesAutomaticosInternal, { + funcionarioId, + motivoTipo: 'licenca', + motivoId: licencaId, + dataInicio, + dataFim + }); + } catch (error) { + console.error('[excluirLicenca] Erro ao remover ajustes automáticos:', error); + } + // Recalcular banco de horas APENAS para o período específico da licença excluída // Isso garante que os dias da licença sejam removidos corretamente dos registros de ponto await recalcularBancoHorasPeriodo(ctx, funcionarioId, dataInicio, dataFim);