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:
@@ -62,6 +62,12 @@
|
|||||||
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
|
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
|
// Estados
|
||||||
let mostrandoWebcam = $state(false);
|
let mostrandoWebcam = $state(false);
|
||||||
let registrando = $state(false);
|
let registrando = $state(false);
|
||||||
@@ -86,6 +92,12 @@
|
|||||||
const registrosHoje = $derived(registrosHojeQuery?.data || []);
|
const registrosHoje = $derived(registrosHojeQuery?.data || []);
|
||||||
const config = $derived(configQuery?.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(() => {
|
const proximoTipo = $derived.by(() => {
|
||||||
if (registrosHoje.length === 0) {
|
if (registrosHoje.length === 0) {
|
||||||
return 'entrada';
|
return 'entrada';
|
||||||
@@ -209,6 +221,16 @@
|
|||||||
return;
|
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
|
// Verificar permissões antes de registrar
|
||||||
const permissoes = await verificarPermissoes();
|
const permissoes = await verificarPermissoes();
|
||||||
if (!permissoes.localizacao || !permissoes.webcam) {
|
if (!permissoes.localizacao || !permissoes.webcam) {
|
||||||
@@ -824,6 +846,8 @@
|
|||||||
!coletandoInfo &&
|
!coletandoInfo &&
|
||||||
config !== undefined &&
|
config !== undefined &&
|
||||||
!estaDispensado &&
|
!estaDispensado &&
|
||||||
|
!emFerias &&
|
||||||
|
!emLicenca &&
|
||||||
temFuncionarioAssociado
|
temFuncionarioAssociado
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1051,6 +1075,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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 -->
|
<!-- Card de Registro de Ponto Modernizado -->
|
||||||
<div
|
<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"
|
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,6 +1126,10 @@
|
|||||||
? 'Você não possui funcionário associado à sua conta'
|
? 'Você não possui funcionário associado à sua conta'
|
||||||
: estaDispensado
|
: estaDispensado
|
||||||
? 'Você está dispensado de registrar ponto no momento'
|
? '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}
|
{#if registrando}
|
||||||
@@ -1098,6 +1145,12 @@
|
|||||||
{:else if estaDispensado}
|
{:else if estaDispensado}
|
||||||
<XCircle class="h-5 w-5" />
|
<XCircle class="h-5 w-5" />
|
||||||
Registro Indisponível
|
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'}
|
{:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'}
|
||||||
<LogIn class="h-5 w-5" />
|
<LogIn class="h-5 w-5" />
|
||||||
Registrar Entrada
|
Registrar Entrada
|
||||||
|
|||||||
@@ -132,13 +132,37 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Funções auxiliares
|
// 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) {
|
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 {
|
function calcularDias(dataInicio: string, dataFim: string): number {
|
||||||
const inicio = new Date(dataInicio);
|
const inicio = parseDataLocal(dataInicio);
|
||||||
const fim = new Date(dataFim);
|
const fim = parseDataLocal(dataFim);
|
||||||
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
||||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
||||||
}
|
}
|
||||||
@@ -508,7 +532,7 @@
|
|||||||
!licencaMaternidade.ehProrrogacao &&
|
!licencaMaternidade.ehProrrogacao &&
|
||||||
!licencaMaternidade.dataFim
|
!licencaMaternidade.dataFim
|
||||||
) {
|
) {
|
||||||
const inicio = new Date(licencaMaternidade.dataInicio);
|
const inicio = parseDataLocal(licencaMaternidade.dataInicio);
|
||||||
if (!isNaN(inicio.getTime())) {
|
if (!isNaN(inicio.getTime())) {
|
||||||
inicio.setDate(inicio.getDate() + 120); // 120 dias
|
inicio.setDate(inicio.getDate() + 120); // 120 dias
|
||||||
licencaMaternidade.dataFim = inicio.toISOString().split('T')[0];
|
licencaMaternidade.dataFim = inicio.toISOString().split('T')[0];
|
||||||
@@ -522,7 +546,7 @@
|
|||||||
licencaPaternidade.dataInicio &&
|
licencaPaternidade.dataInicio &&
|
||||||
!licencaPaternidade.dataFim
|
!licencaPaternidade.dataFim
|
||||||
) {
|
) {
|
||||||
const inicio = new Date(licencaPaternidade.dataInicio);
|
const inicio = parseDataLocal(licencaPaternidade.dataInicio);
|
||||||
if (!isNaN(inicio.getTime())) {
|
if (!isNaN(inicio.getTime())) {
|
||||||
inicio.setDate(inicio.getDate() + 20); // 20 dias
|
inicio.setDate(inicio.getDate() + 20); // 20 dias
|
||||||
licencaPaternidade.dataFim = inicio.toISOString().split('T')[0];
|
licencaPaternidade.dataFim = inicio.toISOString().split('T')[0];
|
||||||
@@ -574,11 +598,11 @@
|
|||||||
|
|
||||||
// Filtro por período
|
// Filtro por período
|
||||||
if (filtroDataInicio && filtroDataFim) {
|
if (filtroDataInicio && filtroDataFim) {
|
||||||
const inicio = new Date(filtroDataInicio);
|
const inicio = parseDataLocal(filtroDataInicio);
|
||||||
const fim = new Date(filtroDataFim);
|
const fim = parseDataLocal(filtroDataFim);
|
||||||
atestados = atestados.filter((a) => {
|
atestados = atestados.filter((a) => {
|
||||||
const aInicio = new Date(a.dataInicio);
|
const aInicio = parseDataLocal(a.dataInicio);
|
||||||
const aFim = new Date(a.dataFim);
|
const aFim = parseDataLocal(a.dataFim);
|
||||||
return (
|
return (
|
||||||
(aInicio >= inicio && aInicio <= fim) ||
|
(aInicio >= inicio && aInicio <= fim) ||
|
||||||
(aFim >= inicio && aFim <= fim) ||
|
(aFim >= inicio && aFim <= fim) ||
|
||||||
@@ -586,8 +610,8 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
licencas = licencas.filter((l) => {
|
licencas = licencas.filter((l) => {
|
||||||
const lInicio = new Date(l.dataInicio);
|
const lInicio = parseDataLocal(l.dataInicio);
|
||||||
const lFim = new Date(l.dataFim);
|
const lFim = parseDataLocal(l.dataFim);
|
||||||
return (
|
return (
|
||||||
(lInicio >= inicio && lInicio <= fim) ||
|
(lInicio >= inicio && lInicio <= fim) ||
|
||||||
(lFim >= inicio && lFim <= fim) ||
|
(lFim >= inicio && lFim <= fim) ||
|
||||||
@@ -645,11 +669,11 @@
|
|||||||
|
|
||||||
// Filtro por período
|
// Filtro por período
|
||||||
if (relatorioPeriodoInicio && relatorioPeriodoFim) {
|
if (relatorioPeriodoInicio && relatorioPeriodoFim) {
|
||||||
const inicio = new Date(relatorioPeriodoInicio);
|
const inicio = parseDataLocal(relatorioPeriodoInicio);
|
||||||
const fim = new Date(relatorioPeriodoFim);
|
const fim = parseDataLocal(relatorioPeriodoFim);
|
||||||
atestados = atestados.filter((a) => {
|
atestados = atestados.filter((a) => {
|
||||||
const aInicio = new Date(a.dataInicio);
|
const aInicio = parseDataLocal(a.dataInicio);
|
||||||
const aFim = new Date(a.dataFim);
|
const aFim = parseDataLocal(a.dataFim);
|
||||||
return (
|
return (
|
||||||
(aInicio >= inicio && aInicio <= fim) ||
|
(aInicio >= inicio && aInicio <= fim) ||
|
||||||
(aFim >= inicio && aFim <= fim) ||
|
(aFim >= inicio && aFim <= fim) ||
|
||||||
@@ -657,8 +681,8 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
licencas = licencas.filter((l) => {
|
licencas = licencas.filter((l) => {
|
||||||
const lInicio = new Date(l.dataInicio);
|
const lInicio = parseDataLocal(l.dataInicio);
|
||||||
const lFim = new Date(l.dataFim);
|
const lFim = parseDataLocal(l.dataFim);
|
||||||
return (
|
return (
|
||||||
(lInicio >= inicio && lInicio <= fim) ||
|
(lInicio >= inicio && lInicio <= fim) ||
|
||||||
(lFim >= inicio && lFim <= fim) ||
|
(lFim >= inicio && lFim <= fim) ||
|
||||||
@@ -759,7 +783,7 @@
|
|||||||
doc.setFontSize(11);
|
doc.setFontSize(11);
|
||||||
doc.setTextColor(0, 0, 0);
|
doc.setTextColor(0, 0, 0);
|
||||||
doc.text(
|
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,
|
105,
|
||||||
yPosition,
|
yPosition,
|
||||||
{ align: 'center' }
|
{ align: 'center' }
|
||||||
|
|||||||
@@ -705,6 +705,19 @@ export const atualizarStatus = mutation({
|
|||||||
await ctx.db.patch(registro._id, updateData);
|
await ctx.db.patch(registro._id, updateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recalcular imediatamente o status de férias/licença do funcionário
|
||||||
|
// para refletir o cancelamento (ou outra mudança) sem depender apenas do cron diário
|
||||||
|
try {
|
||||||
|
await ctx.runMutation(internal.ferias.atualizarStatusFuncionario, {
|
||||||
|
funcionarioId: registro.funcionarioId
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'[ferias.atualizarStatus] Erro ao atualizar statusFerias do funcionário:',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -551,12 +551,21 @@ export const registrarPonto = mutation({
|
|||||||
throw new Error('Usuário não possui funcionário associado');
|
throw new Error('Usuário não possui funcionário associado');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar se funcionário está ativo
|
// Verificar se funcionário existe
|
||||||
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
if (!funcionario) {
|
if (!funcionario) {
|
||||||
throw new Error('Funcionário não encontrado');
|
throw new Error('Funcionário não encontrado');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bloquear registro de ponto para funcionários em férias ou licença
|
||||||
|
if (funcionario.statusFerias === 'em_ferias') {
|
||||||
|
throw new Error('Não é possível registrar ponto: funcionário está em férias.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (funcionario.statusFerias === 'em_licenca') {
|
||||||
|
throw new Error('Não é possível registrar ponto: funcionário está em licença.');
|
||||||
|
}
|
||||||
|
|
||||||
// Obter configuração de ponto
|
// Obter configuração de ponto
|
||||||
const config = await ctx.db
|
const config = await ctx.db
|
||||||
.query('configuracaoPonto')
|
.query('configuracaoPonto')
|
||||||
|
|||||||
Reference in New Issue
Block a user