feat: implement homologation deletion and detail viewing features

- Added functionality to delete homologations, restricted to users with managerial permissions.
- Introduced modals for viewing details of homologations and confirming deletions, enhancing user interaction.
- Updated the backend to support homologation deletion, including necessary permission checks and data integrity management.
- Enhanced the UI to display alerts for unassociated employees and active dispensas during point registration, improving user feedback and error handling.
This commit is contained in:
2025-11-20 07:01:33 -03:00
parent 57c37fedef
commit 8ea5c0316b
3 changed files with 409 additions and 3 deletions

View File

@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import { Clock, Edit, TrendingUp, TrendingDown, Save, X } from 'lucide-svelte';
import { Clock, Edit, TrendingUp, TrendingDown, Save, X, Trash2, Eye, MoreVertical } from 'lucide-svelte';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
import { toast } from 'svelte-sonner';
@@ -14,6 +14,10 @@
let registroSelecionado = $state<Id<'registrosPonto'> | ''>('');
let modoEdicao = $state(false);
let abaAtiva = $state<'editar' | 'ajustar'>('editar');
let homologacaoDetalhada = $state<Id<'homologacoesPonto'> | null>(null);
let homologacaoParaExcluir = $state<Id<'homologacoesPonto'> | null>(null);
let mostrandoModalDetalhes = $state(false);
let mostrandoModalExcluir = $state(false);
// Formulário de edição
let horaNova = $state(8);
@@ -111,6 +115,9 @@
const motivos = $derived(motivosQuery?.data);
const homologacoes = $derived(homologacoesQuery?.data || []);
const registros = $derived(registrosQuery?.data || []);
// Verificar se é gestor (tem subordinados)
const isGestor = $derived(subordinados.length > 0);
// Lista de funcionários do time
const funcionarios = $derived.by(() => {
@@ -142,7 +149,7 @@
motivoDescricao = '';
observacoes = '';
modoEdicao = true;
modoAjuste = false;
abaAtiva = 'editar';
}
function abrirEdicaoComAjuste(registroId: Id<'registrosPonto'>) {
@@ -253,6 +260,61 @@
toast.error(`Erro ao ajustar banco de horas: ${errorMessage}`);
}
}
function abrirDetalhes(homologacaoId: Id<'homologacoesPonto'>) {
homologacaoDetalhada = homologacaoId;
mostrandoModalDetalhes = true;
}
function fecharDetalhes() {
mostrandoModalDetalhes = false;
homologacaoDetalhada = null;
}
function abrirModalExcluir(homologacaoId: Id<'homologacoesPonto'>) {
homologacaoParaExcluir = homologacaoId;
mostrandoModalExcluir = true;
}
function fecharModalExcluir() {
mostrandoModalExcluir = false;
homologacaoParaExcluir = null;
}
async function excluirHomologacao() {
if (!homologacaoParaExcluir) return;
try {
await client.mutation(api.pontos.excluirHomologacao, {
homologacaoId: homologacaoParaExcluir,
});
toast.success('Homologação excluída com sucesso');
fecharModalExcluir();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Erro ao excluir homologação: ${errorMessage}`);
}
}
function editarHomologacao(homologacaoId: Id<'homologacoesPonto'>) {
const homologacao = homologacoes.find((h) => h._id === homologacaoId);
if (!homologacao) return;
// Se for edição de registro, abrir edição do registro
if (homologacao.registroId) {
funcionarioSelecionado = homologacao.funcionarioId;
abrirEdicaoComAjuste(homologacao.registroId);
} else {
// Se for ajuste de banco de horas, não há como editar diretamente
toast.info('Ajustes de banco de horas não podem ser editados. Crie um novo ajuste para corrigir.');
}
}
const homologacaoSelecionada = $derived.by(() => {
if (!homologacaoDetalhada) return null;
return homologacoes.find((h) => h._id === homologacaoDetalhada) || null;
});
</script>
<div class="container mx-auto px-4 py-6">
@@ -667,6 +729,9 @@
<th>Detalhes</th>
<th>Motivo</th>
<th>Observações</th>
{#if isGestor}
<th>Ações</th>
{/if}
</tr>
</thead>
<tbody>
@@ -723,6 +788,35 @@
{homologacao.observacoes || '-'}
</div>
</td>
{#if isGestor}
<td>
<div class="flex gap-1">
<button
class="btn btn-sm btn-outline btn-info btn-square hover:btn-info hover:scale-110 transition-transform"
onclick={() => abrirDetalhes(homologacao._id)}
title="Ver detalhes"
>
<Eye class="h-4 w-4" />
</button>
{#if homologacao.registroId}
<button
class="btn btn-sm btn-square bg-primary/10 text-primary border-primary/20 hover:bg-primary/20 hover:border-primary/40 hover:shadow-md transition-all"
onclick={() => editarHomologacao(homologacao._id)}
title="Editar"
>
<Edit class="h-4 w-4" />
</button>
{/if}
<button
class="btn btn-sm btn-square bg-error/10 text-error border-error/20 hover:bg-error/20 hover:border-error/40 hover:shadow-md transition-all"
onclick={() => abrirModalExcluir(homologacao._id)}
title="Excluir"
>
<Trash2 class="h-4 w-4" />
</button>
</div>
</td>
{/if}
</tr>
{/each}
</tbody>
@@ -732,5 +826,167 @@
</div>
</div>
{/if}
<!-- Modal de Detalhes da Homologação -->
{#if mostrandoModalDetalhes && homologacaoSelecionada}
<div class="modal modal-open">
<div class="modal-box max-w-3xl">
<h3 class="font-bold text-lg mb-4">Detalhes da Homologação</h3>
<div class="space-y-4">
<!-- Informações Gerais -->
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Informações Gerais</h4>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<span class="font-medium">Data:</span>{' '}
{new Date(homologacaoSelecionada.criadoEm).toLocaleDateString('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</div>
<div>
<span class="font-medium">Funcionário:</span>{' '}
{homologacaoSelecionada.funcionario?.nome || '-'}
{#if homologacaoSelecionada.funcionario?.matricula}
<br />
<span class="text-xs text-base-content/70">
Mat: {homologacaoSelecionada.funcionario.matricula}
</span>
{/if}
</div>
<div>
<span class="font-medium">Gestor:</span>{' '}
{homologacaoSelecionada.gestor?.nome || '-'}
</div>
<div>
<span class="font-medium">Tipo:</span>{' '}
{#if homologacaoSelecionada.registroId}
<span class="badge badge-info">Edição de Registro</span>
{:else if homologacaoSelecionada.tipoAjuste}
<span class="badge badge-warning">
Ajuste: {homologacaoSelecionada.tipoAjuste}
</span>
{/if}
</div>
</div>
</div>
</div>
<!-- Detalhes do Registro ou Ajuste -->
{#if homologacaoSelecionada.registroId}
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Edição de Registro</h4>
<div class="text-sm space-y-2">
<div>
<span class="font-medium">Horário Anterior:</span>{' '}
<span class="line-through opacity-70">
{formatarHoraPonto(homologacaoSelecionada.horaAnterior || 0, homologacaoSelecionada.minutoAnterior || 0)}
</span>
</div>
<div>
<span class="font-medium">Horário Novo:</span>{' '}
<span>
{formatarHoraPonto(homologacaoSelecionada.horaNova || 0, homologacaoSelecionada.minutoNova || 0)}
</span>
</div>
{#if homologacaoSelecionada.registro}
<div>
<span class="font-medium">Data do Registro:</span>{' '}
{new Date(homologacaoSelecionada.registro.data).toLocaleDateString('pt-BR')}
</div>
<div>
<span class="font-medium">Tipo de Registro:</span>{' '}
{getTipoRegistroLabel(homologacaoSelecionada.registro.tipo)}
</div>
{/if}
</div>
</div>
</div>
{:else if homologacaoSelecionada.tipoAjuste}
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Ajuste de Banco de Horas</h4>
<div class="text-sm space-y-2">
<div>
<span class="font-medium">Tipo de Ajuste:</span>{' '}
{homologacaoSelecionada.tipoAjuste}
</div>
<div>
<span class="font-medium">Período:</span>{' '}
{homologacaoSelecionada.periodoDias || 0}d {homologacaoSelecionada.periodoHoras || 0}h{' '}
{homologacaoSelecionada.periodoMinutos || 0}min
</div>
{#if homologacaoSelecionada.ajusteMinutos}
<div>
<span class="font-medium">Ajuste Total:</span>{' '}
{homologacaoSelecionada.ajusteMinutos > 0 ? '+' : ''}
{Math.floor(Math.abs(homologacaoSelecionada.ajusteMinutos) / 60)}h{' '}
{Math.abs(homologacaoSelecionada.ajusteMinutos) % 60}min
</div>
{/if}
</div>
</div>
</div>
{/if}
<!-- Motivo e Observações -->
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Motivo e Observações</h4>
<div class="text-sm space-y-2">
<div>
<span class="font-medium">Tipo de Motivo:</span>{' '}
{homologacaoSelecionada.motivoTipo || '-'}
</div>
<div>
<span class="font-medium">Descrição:</span>{' '}
{homologacaoSelecionada.motivoDescricao || '-'}
</div>
{#if homologacaoSelecionada.observacoes}
<div>
<span class="font-medium">Observações:</span>
<div class="mt-1 p-2 bg-base-100 rounded">
{homologacaoSelecionada.observacoes}
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="modal-action">
<button class="btn" onclick={fecharDetalhes}>Fechar</button>
</div>
</div>
<div class="modal-backdrop" onclick={fecharDetalhes}></div>
</div>
{/if}
<!-- Modal de Confirmação de Exclusão -->
{#if mostrandoModalExcluir && homologacaoParaExcluir}
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Confirmar Exclusão</h3>
<p class="mb-4">
Tem certeza que deseja excluir esta homologação? Esta ação não pode ser desfeita.
</p>
<div class="modal-action">
<button class="btn btn-ghost" onclick={fecharModalExcluir}>Cancelar</button>
<button class="btn btn-error" onclick={excluirHomologacao}>
<Trash2 class="h-4 w-4 mr-2" />
Excluir
</button>
</div>
</div>
<div class="modal-backdrop" onclick={fecharModalExcluir}></div>
</div>
{/if}
</div>