Feat controle ponto #35
@@ -34,6 +34,12 @@
|
|||||||
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
|
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Query para verificar dispensa ativa
|
||||||
|
const dispensaQuery = useQuery(
|
||||||
|
api.pontos.verificarDispensaAtiva,
|
||||||
|
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
|
||||||
|
);
|
||||||
|
|
||||||
// Estados
|
// Estados
|
||||||
let mostrandoWebcam = $state(false);
|
let mostrandoWebcam = $state(false);
|
||||||
let registrando = $state(false);
|
let registrando = $state(false);
|
||||||
@@ -150,6 +156,22 @@
|
|||||||
async function registrarPonto() {
|
async function registrarPonto() {
|
||||||
if (registrando) return;
|
if (registrando) return;
|
||||||
|
|
||||||
|
// Verificar se tem funcionário associado
|
||||||
|
if (!temFuncionarioAssociado) {
|
||||||
|
mensagemErroModal = 'Usuário não possui funcionário associado';
|
||||||
|
detalhesErroModal = 'Você não possui um funcionário associado à sua conta. Entre em contato com o administrador do sistema.';
|
||||||
|
mostrarModalErro = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se está dispensado antes de registrar
|
||||||
|
if (estaDispensado) {
|
||||||
|
mensagemErroModal = 'Registro dispensado pelo gestor';
|
||||||
|
detalhesErroModal = motivoDispensa || 'Você está dispensado de registrar ponto no momento.';
|
||||||
|
mostrarModalErro = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar permissões antes de registrar
|
// Verificar permissões antes de registrar
|
||||||
const permissoes = await verificarPermissoes();
|
const permissoes = await verificarPermissoes();
|
||||||
if (!permissoes.localizacao || !permissoes.webcam) {
|
if (!permissoes.localizacao || !permissoes.webcam) {
|
||||||
@@ -296,6 +318,22 @@
|
|||||||
async function iniciarRegistroComFoto() {
|
async function iniciarRegistroComFoto() {
|
||||||
if (registrando || coletandoInfo) return;
|
if (registrando || coletandoInfo) return;
|
||||||
|
|
||||||
|
// Verificar se tem funcionário associado
|
||||||
|
if (!temFuncionarioAssociado) {
|
||||||
|
mensagemErroModal = 'Usuário não possui funcionário associado';
|
||||||
|
detalhesErroModal = 'Você não possui um funcionário associado à sua conta. Entre em contato com o administrador do sistema.';
|
||||||
|
mostrarModalErro = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se está dispensado antes de abrir webcam
|
||||||
|
if (estaDispensado) {
|
||||||
|
mensagemErroModal = 'Registro dispensado pelo gestor';
|
||||||
|
detalhesErroModal = motivoDispensa || 'Você está dispensado de registrar ponto no momento.';
|
||||||
|
mostrarModalErro = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar permissões antes de abrir webcam
|
// Verificar permissões antes de abrir webcam
|
||||||
const permissoes = await verificarPermissoes();
|
const permissoes = await verificarPermissoes();
|
||||||
if (!permissoes.localizacao || !permissoes.webcam) {
|
if (!permissoes.localizacao || !permissoes.webcam) {
|
||||||
@@ -542,8 +580,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dispensaAtiva = $derived(dispensaQuery?.data);
|
||||||
|
const estaDispensado = $derived(dispensaAtiva?.dispensado ?? false);
|
||||||
|
const motivoDispensa = $derived(dispensaAtiva?.motivo ?? null);
|
||||||
|
const temFuncionarioAssociado = $derived(funcionarioId !== null);
|
||||||
|
|
||||||
const podeRegistrar = $derived.by(() => {
|
const podeRegistrar = $derived.by(() => {
|
||||||
return !registrando && !coletandoInfo && config !== undefined;
|
return !registrando && !coletandoInfo && config !== undefined && !estaDispensado && temFuncionarioAssociado;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Referência para o modal
|
// Referência para o modal
|
||||||
@@ -650,6 +693,60 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
<!-- Alerta de Funcionário Não Associado -->
|
||||||
|
{#if !temFuncionarioAssociado}
|
||||||
|
<div class="alert alert-error shadow-lg">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold">Funcionário Não Associado</h3>
|
||||||
|
<div class="text-sm">
|
||||||
|
Você não possui um funcionário associado à sua conta.
|
||||||
|
<br />
|
||||||
|
Entre em contato com o administrador do sistema para associar um funcionário à sua conta.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Alerta de Dispensa -->
|
||||||
|
{#if estaDispensado && motivoDispensa && temFuncionarioAssociado}
|
||||||
|
<div class="alert alert-warning shadow-lg">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold">Registro de Ponto Dispensado</h3>
|
||||||
|
<div class="text-sm">
|
||||||
|
Você está dispensado de registrar ponto no momento.
|
||||||
|
<br />
|
||||||
|
<strong>Motivo:</strong> {motivoDispensa}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Relógio Sincronizado -->
|
<!-- Relógio Sincronizado -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 shadow-xl">
|
||||||
<div class="card-body items-center">
|
<div class="card-body items-center">
|
||||||
@@ -730,6 +827,11 @@
|
|||||||
class="btn btn-primary btn-lg"
|
class="btn btn-primary btn-lg"
|
||||||
onclick={iniciarRegistroComFoto}
|
onclick={iniciarRegistroComFoto}
|
||||||
disabled={!podeRegistrar}
|
disabled={!podeRegistrar}
|
||||||
|
title={!temFuncionarioAssociado
|
||||||
|
? 'Você não possui funcionário associado à sua conta'
|
||||||
|
: estaDispensado
|
||||||
|
? 'Você está dispensado de registrar ponto no momento'
|
||||||
|
: ''}
|
||||||
>
|
>
|
||||||
{#if registrando}
|
{#if registrando}
|
||||||
<span class="loading loading-spinner loading-sm"></span>
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
@@ -738,6 +840,12 @@
|
|||||||
{:else}
|
{:else}
|
||||||
Registrando...
|
Registrando...
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if !temFuncionarioAssociado}
|
||||||
|
<XCircle class="h-5 w-5" />
|
||||||
|
Funcionário Não Associado
|
||||||
|
{:else if estaDispensado}
|
||||||
|
<XCircle class="h-5 w-5" />
|
||||||
|
Registro Indisponível
|
||||||
{:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'}
|
{:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'}
|
||||||
<LogIn class="h-5 w-5" />
|
<LogIn class="h-5 w-5" />
|
||||||
Registrar Entrada
|
Registrar Entrada
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
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 type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||||
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
|
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
@@ -14,6 +14,10 @@
|
|||||||
let registroSelecionado = $state<Id<'registrosPonto'> | ''>('');
|
let registroSelecionado = $state<Id<'registrosPonto'> | ''>('');
|
||||||
let modoEdicao = $state(false);
|
let modoEdicao = $state(false);
|
||||||
let abaAtiva = $state<'editar' | 'ajustar'>('editar');
|
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
|
// Formulário de edição
|
||||||
let horaNova = $state(8);
|
let horaNova = $state(8);
|
||||||
@@ -112,6 +116,9 @@
|
|||||||
const homologacoes = $derived(homologacoesQuery?.data || []);
|
const homologacoes = $derived(homologacoesQuery?.data || []);
|
||||||
const registros = $derived(registrosQuery?.data || []);
|
const registros = $derived(registrosQuery?.data || []);
|
||||||
|
|
||||||
|
// Verificar se é gestor (tem subordinados)
|
||||||
|
const isGestor = $derived(subordinados.length > 0);
|
||||||
|
|
||||||
// Lista de funcionários do time
|
// Lista de funcionários do time
|
||||||
const funcionarios = $derived.by(() => {
|
const funcionarios = $derived.by(() => {
|
||||||
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string }> = [];
|
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string }> = [];
|
||||||
@@ -142,7 +149,7 @@
|
|||||||
motivoDescricao = '';
|
motivoDescricao = '';
|
||||||
observacoes = '';
|
observacoes = '';
|
||||||
modoEdicao = true;
|
modoEdicao = true;
|
||||||
modoAjuste = false;
|
abaAtiva = 'editar';
|
||||||
}
|
}
|
||||||
|
|
||||||
function abrirEdicaoComAjuste(registroId: Id<'registrosPonto'>) {
|
function abrirEdicaoComAjuste(registroId: Id<'registrosPonto'>) {
|
||||||
@@ -253,6 +260,61 @@
|
|||||||
toast.error(`Erro ao ajustar banco de horas: ${errorMessage}`);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="container mx-auto px-4 py-6">
|
<div class="container mx-auto px-4 py-6">
|
||||||
@@ -667,6 +729,9 @@
|
|||||||
<th>Detalhes</th>
|
<th>Detalhes</th>
|
||||||
<th>Motivo</th>
|
<th>Motivo</th>
|
||||||
<th>Observações</th>
|
<th>Observações</th>
|
||||||
|
{#if isGestor}
|
||||||
|
<th>Ações</th>
|
||||||
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -723,6 +788,35 @@
|
|||||||
{homologacao.observacoes || '-'}
|
{homologacao.observacoes || '-'}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -732,5 +826,167 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1052,6 +1052,48 @@ export const listarHomologacoes = query({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclui uma homologação (apenas para gestores)
|
||||||
|
*/
|
||||||
|
export const excluirHomologacao = mutation({
|
||||||
|
args: {
|
||||||
|
homologacaoId: v.id('homologacoesPonto'),
|
||||||
|
},
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const usuario = await getCurrentUserFunction(ctx);
|
||||||
|
if (!usuario) {
|
||||||
|
throw new Error('Usuário não autenticado');
|
||||||
|
}
|
||||||
|
|
||||||
|
const homologacao = await ctx.db.get(args.homologacaoId);
|
||||||
|
if (!homologacao) {
|
||||||
|
throw new Error('Homologação não encontrada');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se é gestor do funcionário
|
||||||
|
const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, homologacao.funcionarioId);
|
||||||
|
if (!isGestor && homologacao.gestorId !== usuario._id) {
|
||||||
|
throw new Error('Você não tem permissão para excluir esta homologação');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se a homologação estiver vinculada a um registro, remover a referência
|
||||||
|
if (homologacao.registroId) {
|
||||||
|
const registro = await ctx.db.get(homologacao.registroId);
|
||||||
|
if (registro && registro.homologacaoId === args.homologacaoId) {
|
||||||
|
await ctx.db.patch(homologacao.registroId, {
|
||||||
|
homologacaoId: undefined,
|
||||||
|
editadoPorGestor: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excluir homologação
|
||||||
|
await ctx.db.delete(args.homologacaoId);
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtém opções de motivos de atestados/declarações
|
* Obtém opções de motivos de atestados/declarações
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user