feat: add functionality to manage employee status during point registration, preventing point logging for employees on vacation or leave; enhance UI alerts to inform users of their current status

This commit is contained in:
2025-12-09 15:06:36 -03:00
parent 248d7cd623
commit 73da995109
4 changed files with 119 additions and 20 deletions

View File

@@ -62,6 +62,12 @@
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
);
// Query para obter status atual do funcionário (férias/licença)
const funcionarioStatusQuery = useQuery(
api.funcionarios.getCurrent,
currentUser?.data ? {} : 'skip'
);
// Estados
let mostrandoWebcam = $state(false);
let registrando = $state(false);
@@ -86,6 +92,12 @@
const registrosHoje = $derived(registrosHojeQuery?.data || []);
const config = $derived(configQuery?.data);
// Status de férias/licença do funcionário
const funcionarioStatus = $derived(funcionarioStatusQuery?.data);
const statusFerias = $derived(funcionarioStatus?.statusFerias ?? 'ativo');
const emFerias = $derived(statusFerias === 'em_ferias');
const emLicenca = $derived(statusFerias === 'em_licenca');
const proximoTipo = $derived.by(() => {
if (registrosHoje.length === 0) {
return 'entrada';
@@ -209,6 +221,16 @@
return;
}
// Verificar se funcionário está em férias ou licença
if (emFerias || emLicenca) {
mensagemErroModal = 'Registro de ponto não permitido';
detalhesErroModal = emFerias
? 'Seu status atual é "Em Férias". Durante o período de férias não é permitido registrar ponto.'
: 'Seu status atual é "Em Licença". Durante o período de licença não é permitido registrar ponto.';
mostrarModalErro = true;
return;
}
// Verificar permissões antes de registrar
const permissoes = await verificarPermissoes();
if (!permissoes.localizacao || !permissoes.webcam) {
@@ -824,6 +846,8 @@
!coletandoInfo &&
config !== undefined &&
!estaDispensado &&
!emFerias &&
!emLicenca &&
temFuncionarioAssociado
);
});
@@ -1051,6 +1075,25 @@
</div>
{/if}
<!-- Alerta de Status de Férias/Licença -->
{#if temFuncionarioAssociado && (emFerias || emLicenca)}
<div class="alert alert-info shadow-lg">
<Info class="h-6 w-6 shrink-0 stroke-current" />
<div>
<h3 class="font-bold">Status do Funcionário</h3>
<div class="text-sm">
{#if emFerias}
Seu status atual é <strong>Em Férias</strong>. Durante o período de férias não é permitido
registrar ponto.
{:else}
Seu status atual é <strong>Em Licença</strong>. Durante o período de licença não é permitido
registrar ponto.
{/if}
</div>
</div>
</div>
{/if}
<!-- Card de Registro de Ponto Modernizado -->
<div
class="card from-base-100 via-base-100 to-primary/5 border-base-300 mx-auto max-w-2xl border bg-gradient-to-br shadow-2xl"
@@ -1083,7 +1126,11 @@
? 'Você não possui funcionário associado à sua conta'
: estaDispensado
? 'Você está dispensado de registrar ponto no momento'
: ''}
: emFerias
? 'Você está em férias. Durante o período de férias não é permitido registrar ponto.'
: emLicenca
? 'Você está em licença. Durante o período de licença não é permitido registrar ponto.'
: ''}
>
{#if registrando}
<span class="loading loading-spinner loading-sm"></span>
@@ -1098,6 +1145,12 @@
{:else if estaDispensado}
<XCircle class="h-5 w-5" />
Registro Indisponível
{:else if emFerias}
<XCircle class="h-5 w-5" />
Em Férias
{:else if emLicenca}
<XCircle class="h-5 w-5" />
Em Licença
{:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'}
<LogIn class="h-5 w-5" />
Registrar Entrada

View File

@@ -132,13 +132,37 @@
});
// Funções auxiliares
/**
* Converte string de data no formato "YYYY-MM-DD" (com ou sem parte de hora)
* para um objeto Date no fuso local, evitando o problema de "um dia a menos"
* causado por `new Date('YYYY-MM-DD')` em timezones como UTC-3.
*/
function parseDataLocal(data: string): Date {
if (!data) return new Date(NaN);
// Garante que só a parte da data seja usada (descarta "T...").
const apenasData = data.substring(0, 10);
const [anoStr, mesStr, diaStr] = apenasData.split('-');
const ano = Number(anoStr);
const mes = Number(mesStr);
const dia = Number(diaStr);
if (!ano || !mes || !dia) {
return new Date(NaN);
}
return new Date(ano, mes - 1, dia);
}
function formatarData(data: string) {
return new Date(data).toLocaleDateString('pt-BR');
const date = parseDataLocal(data);
if (isNaN(date.getTime())) return '';
return date.toLocaleDateString('pt-BR');
}
function calcularDias(dataInicio: string, dataFim: string): number {
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
const inicio = parseDataLocal(dataInicio);
const fim = parseDataLocal(dataFim);
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
}
@@ -508,7 +532,7 @@
!licencaMaternidade.ehProrrogacao &&
!licencaMaternidade.dataFim
) {
const inicio = new Date(licencaMaternidade.dataInicio);
const inicio = parseDataLocal(licencaMaternidade.dataInicio);
if (!isNaN(inicio.getTime())) {
inicio.setDate(inicio.getDate() + 120); // 120 dias
licencaMaternidade.dataFim = inicio.toISOString().split('T')[0];
@@ -522,7 +546,7 @@
licencaPaternidade.dataInicio &&
!licencaPaternidade.dataFim
) {
const inicio = new Date(licencaPaternidade.dataInicio);
const inicio = parseDataLocal(licencaPaternidade.dataInicio);
if (!isNaN(inicio.getTime())) {
inicio.setDate(inicio.getDate() + 20); // 20 dias
licencaPaternidade.dataFim = inicio.toISOString().split('T')[0];
@@ -574,11 +598,11 @@
// Filtro por período
if (filtroDataInicio && filtroDataFim) {
const inicio = new Date(filtroDataInicio);
const fim = new Date(filtroDataFim);
const inicio = parseDataLocal(filtroDataInicio);
const fim = parseDataLocal(filtroDataFim);
atestados = atestados.filter((a) => {
const aInicio = new Date(a.dataInicio);
const aFim = new Date(a.dataFim);
const aInicio = parseDataLocal(a.dataInicio);
const aFim = parseDataLocal(a.dataFim);
return (
(aInicio >= inicio && aInicio <= fim) ||
(aFim >= inicio && aFim <= fim) ||
@@ -586,8 +610,8 @@
);
});
licencas = licencas.filter((l) => {
const lInicio = new Date(l.dataInicio);
const lFim = new Date(l.dataFim);
const lInicio = parseDataLocal(l.dataInicio);
const lFim = parseDataLocal(l.dataFim);
return (
(lInicio >= inicio && lInicio <= fim) ||
(lFim >= inicio && lFim <= fim) ||
@@ -645,11 +669,11 @@
// Filtro por período
if (relatorioPeriodoInicio && relatorioPeriodoFim) {
const inicio = new Date(relatorioPeriodoInicio);
const fim = new Date(relatorioPeriodoFim);
const inicio = parseDataLocal(relatorioPeriodoInicio);
const fim = parseDataLocal(relatorioPeriodoFim);
atestados = atestados.filter((a) => {
const aInicio = new Date(a.dataInicio);
const aFim = new Date(a.dataFim);
const aInicio = parseDataLocal(a.dataInicio);
const aFim = parseDataLocal(a.dataFim);
return (
(aInicio >= inicio && aInicio <= fim) ||
(aFim >= inicio && aFim <= fim) ||
@@ -657,8 +681,8 @@
);
});
licencas = licencas.filter((l) => {
const lInicio = new Date(l.dataInicio);
const lFim = new Date(l.dataFim);
const lInicio = parseDataLocal(l.dataInicio);
const lFim = parseDataLocal(l.dataFim);
return (
(lInicio >= inicio && lInicio <= fim) ||
(lFim >= inicio && lFim <= fim) ||
@@ -759,7 +783,7 @@
doc.setFontSize(11);
doc.setTextColor(0, 0, 0);
doc.text(
`Período: ${format(new Date(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(new Date(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`,
`Período: ${format(parseDataLocal(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(parseDataLocal(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`,
105,
yPosition,
{ align: 'center' }