refactor: update vacation management structure and enhance status handling
- Renamed and refactored vacation-related types and components for clarity, transitioning from 'SolicitacaoFerias' to 'PeriodoFerias'. - Improved the handling of vacation statuses, including the addition of 'EmFérias' to the status options. - Streamlined the vacation request and approval components to better reflect individual vacation periods. - Enhanced data handling in backend queries and schema to support the new structure and ensure accurate status updates. - Improved user experience by refining UI elements related to vacation periods and their statuses.
This commit is contained in:
@@ -3,13 +3,14 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
|
||||
type SolicitacaoFerias = Doc<'solicitacoesFerias'> & {
|
||||
type PeriodoFerias = Doc<'ferias'> & {
|
||||
funcionario?: Doc<'funcionarios'> | null;
|
||||
gestor?: Doc<'usuarios'> | null;
|
||||
time?: Doc<'times'> | null;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
solicitacao: SolicitacaoFerias;
|
||||
solicitacao: PeriodoFerias;
|
||||
usuarioId: Id<'usuarios'>;
|
||||
onSucesso?: () => void;
|
||||
onCancelar?: () => void;
|
||||
@@ -27,7 +28,8 @@
|
||||
aguardando_aprovacao: 'badge-warning',
|
||||
aprovado: 'badge-success',
|
||||
reprovado: 'badge-error',
|
||||
data_ajustada_aprovada: 'badge-info'
|
||||
data_ajustada_aprovada: 'badge-info',
|
||||
EmFérias: 'badge-info'
|
||||
};
|
||||
return badges[status] || 'badge-neutral';
|
||||
}
|
||||
@@ -37,7 +39,8 @@
|
||||
aguardando_aprovacao: 'Aguardando Aprovação',
|
||||
aprovado: 'Aprovado',
|
||||
reprovado: 'Reprovado',
|
||||
data_ajustada_aprovada: 'Data Ajustada e Aprovada'
|
||||
data_ajustada_aprovada: 'Data Ajustada e Aprovada',
|
||||
EmFérias: 'Em Férias'
|
||||
};
|
||||
return textos[status] || status;
|
||||
}
|
||||
@@ -48,7 +51,7 @@
|
||||
erro = '';
|
||||
|
||||
await client.mutation(api.ferias.atualizarStatus, {
|
||||
solicitacaoId: solicitacao._id,
|
||||
feriasId: solicitacao._id,
|
||||
novoStatus: 'aguardando_aprovacao',
|
||||
usuarioId: usuarioId
|
||||
});
|
||||
@@ -82,33 +85,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Períodos Solicitados -->
|
||||
<!-- Período Solicitado -->
|
||||
<div class="mt-4">
|
||||
<h3 class="mb-3 text-lg font-semibold">Períodos Solicitados</h3>
|
||||
<div class="space-y-2">
|
||||
{#each solicitacao.periodos as periodo, index (index)}
|
||||
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-3">
|
||||
<div class="badge badge-primary">{index + 1}</div>
|
||||
<div class="grid flex-1 grid-cols-3 gap-2 text-sm">
|
||||
<div>
|
||||
<span class="text-base-content/70">Início:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(periodo.dataInicio).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Fim:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(periodo.dataFim).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Dias:</span>
|
||||
<span class="text-primary ml-1 font-bold">{periodo.diasCorridos}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mb-3 text-lg font-semibold">Período Solicitado</h3>
|
||||
<div class="bg-base-200 rounded-lg p-4">
|
||||
<div class="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-base-content/70">Início:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(solicitacao.dataInicio).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
<div>
|
||||
<span class="text-base-content/70">Fim:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(solicitacao.dataFim).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Dias:</span>
|
||||
<span class="text-primary ml-1 font-bold">{solicitacao.diasFerias}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,59 +3,55 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
|
||||
interface Periodo {
|
||||
dataInicio: string;
|
||||
dataFim: string;
|
||||
diasCorridos: number;
|
||||
}
|
||||
|
||||
type SolicitacaoFerias = Doc<'solicitacoesFerias'> & {
|
||||
type PeriodoFerias = Doc<'ferias'> & {
|
||||
funcionario?: Doc<'funcionarios'> | null;
|
||||
gestor?: Doc<'usuarios'> | null;
|
||||
time?: Doc<'times'> | null;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
solicitacao: SolicitacaoFerias;
|
||||
periodo: PeriodoFerias;
|
||||
gestorId: Id<'usuarios'>;
|
||||
onSucesso?: () => void;
|
||||
onCancelar?: () => void;
|
||||
}
|
||||
|
||||
let { solicitacao, gestorId, onSucesso, onCancelar }: Props = $props();
|
||||
let { periodo, gestorId, onSucesso, onCancelar }: Props = $props();
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
let modoAjuste = $state(false);
|
||||
let periodos = $state<Periodo[]>([]);
|
||||
let novaDataInicio = $state(periodo.dataInicio);
|
||||
let novaDataFim = $state(periodo.dataFim);
|
||||
let motivoReprovacao = $state('');
|
||||
let processando = $state(false);
|
||||
let erro = $state('');
|
||||
|
||||
$effect(() => {
|
||||
if (modoAjuste && periodos.length === 0) {
|
||||
periodos = solicitacao.periodos.map((p) => ({ ...p }));
|
||||
}
|
||||
// Calcular dias do período ajustado
|
||||
const diasAjustados = $derived.by(() => {
|
||||
if (!novaDataInicio || !novaDataFim) return 0;
|
||||
const inicio = new Date(novaDataInicio);
|
||||
const fim = new Date(novaDataFim);
|
||||
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
||||
return diffDays;
|
||||
});
|
||||
|
||||
function calcularDias(periodo: Periodo) {
|
||||
if (!periodo.dataInicio || !periodo.dataFim) {
|
||||
periodo.diasCorridos = 0;
|
||||
return;
|
||||
}
|
||||
function calcularDias(dataInicio: string, dataFim: string): number {
|
||||
if (!dataInicio || !dataFim) return 0;
|
||||
|
||||
const inicio = new Date(periodo.dataInicio);
|
||||
const fim = new Date(periodo.dataFim);
|
||||
const inicio = new Date(dataInicio);
|
||||
const fim = new Date(dataFim);
|
||||
|
||||
if (fim < inicio) {
|
||||
erro = 'Data final não pode ser anterior à data inicial';
|
||||
periodo.diasCorridos = 0;
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const diff = fim.getTime() - inicio.getTime();
|
||||
const dias = Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
|
||||
periodo.diasCorridos = dias;
|
||||
erro = '';
|
||||
return dias;
|
||||
}
|
||||
|
||||
async function aprovar() {
|
||||
@@ -64,19 +60,19 @@
|
||||
erro = '';
|
||||
|
||||
// Validar se as datas e condições estão dentro do regime do funcionário
|
||||
if (!solicitacao.funcionario?._id) {
|
||||
if (!periodo.funcionario?._id) {
|
||||
erro = 'Funcionário não encontrado';
|
||||
processando = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const validacao = await client.query(api.saldoFerias.validarSolicitacao, {
|
||||
funcionarioId: solicitacao.funcionario._id,
|
||||
anoReferencia: solicitacao.anoReferencia,
|
||||
periodos: solicitacao.periodos.map((p) => ({
|
||||
dataInicio: p.dataInicio,
|
||||
dataFim: p.dataFim
|
||||
}))
|
||||
funcionarioId: periodo.funcionario._id,
|
||||
anoReferencia: periodo.anoReferencia,
|
||||
periodos: [{
|
||||
dataInicio: periodo.dataInicio,
|
||||
dataFim: periodo.dataFim
|
||||
}]
|
||||
});
|
||||
|
||||
if (!validacao.valido) {
|
||||
@@ -86,7 +82,7 @@
|
||||
}
|
||||
|
||||
await client.mutation(api.ferias.aprovar, {
|
||||
solicitacaoId: solicitacao._id,
|
||||
feriasId: periodo._id,
|
||||
gestorId: gestorId
|
||||
});
|
||||
|
||||
@@ -109,7 +105,7 @@
|
||||
erro = '';
|
||||
|
||||
await client.mutation(api.ferias.reprovar, {
|
||||
solicitacaoId: solicitacao._id,
|
||||
feriasId: periodo._id,
|
||||
gestorId: gestorId,
|
||||
motivoReprovacao
|
||||
});
|
||||
@@ -128,28 +124,27 @@
|
||||
erro = '';
|
||||
|
||||
// Validar se as datas ajustadas e condições estão dentro do regime do funcionário
|
||||
if (!solicitacao.funcionario?._id) {
|
||||
if (!periodo.funcionario?._id) {
|
||||
erro = 'Funcionário não encontrado';
|
||||
processando = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar todos os períodos ajustados
|
||||
for (const periodo of periodos) {
|
||||
if (!periodo.dataInicio || !periodo.dataFim) {
|
||||
erro = 'Todos os períodos devem ter data de início e fim';
|
||||
processando = false;
|
||||
return;
|
||||
}
|
||||
// Validar datas ajustadas
|
||||
if (!novaDataInicio || !novaDataFim) {
|
||||
erro = 'Informe as novas datas de início e fim';
|
||||
processando = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const validacao = await client.query(api.saldoFerias.validarSolicitacao, {
|
||||
funcionarioId: solicitacao.funcionario._id,
|
||||
anoReferencia: solicitacao.anoReferencia,
|
||||
periodos: periodos.map((p) => ({
|
||||
dataInicio: p.dataInicio,
|
||||
dataFim: p.dataFim
|
||||
}))
|
||||
funcionarioId: periodo.funcionario._id,
|
||||
anoReferencia: periodo.anoReferencia,
|
||||
periodos: [{
|
||||
dataInicio: novaDataInicio,
|
||||
dataFim: novaDataFim
|
||||
}],
|
||||
feriasIdExcluir: periodo._id // Excluir o período original do cálculo de saldo
|
||||
});
|
||||
|
||||
if (!validacao.valido) {
|
||||
@@ -159,9 +154,10 @@
|
||||
}
|
||||
|
||||
await client.mutation(api.ferias.ajustarEAprovar, {
|
||||
solicitacaoId: solicitacao._id,
|
||||
feriasId: periodo._id,
|
||||
gestorId: gestorId,
|
||||
novosPeriodos: periodos
|
||||
novaDataInicio,
|
||||
novaDataFim
|
||||
});
|
||||
|
||||
if (onSucesso) onSucesso();
|
||||
@@ -177,7 +173,8 @@
|
||||
aguardando_aprovacao: 'badge-warning',
|
||||
aprovado: 'badge-success',
|
||||
reprovado: 'badge-error',
|
||||
data_ajustada_aprovada: 'badge-info'
|
||||
data_ajustada_aprovada: 'badge-info',
|
||||
EmFérias: 'badge-info'
|
||||
};
|
||||
return badges[status] || 'badge-neutral';
|
||||
}
|
||||
@@ -187,7 +184,8 @@
|
||||
aguardando_aprovacao: 'Aguardando Aprovação',
|
||||
aprovado: 'Aprovado',
|
||||
reprovado: 'Reprovado',
|
||||
data_ajustada_aprovada: 'Data Ajustada e Aprovada'
|
||||
data_ajustada_aprovada: 'Data Ajustada e Aprovada',
|
||||
EmFérias: 'Em Férias'
|
||||
};
|
||||
return textos[status] || status;
|
||||
}
|
||||
@@ -195,6 +193,23 @@
|
||||
function formatarData(data: number) {
|
||||
return new Date(data).toLocaleString('pt-BR');
|
||||
}
|
||||
|
||||
// Função para formatar data sem problemas de timezone
|
||||
function formatarDataString(dataString: string): string {
|
||||
if (!dataString) return '';
|
||||
// Dividir a string da data (formato YYYY-MM-DD)
|
||||
const partes = dataString.split('-');
|
||||
if (partes.length !== 3) return dataString;
|
||||
// Retornar no formato DD/MM/YYYY
|
||||
return `${partes[2]}/${partes[1]}/${partes[0]}`;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (modoAjuste) {
|
||||
novaDataInicio = periodo.dataInicio;
|
||||
novaDataFim = periodo.dataFim;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
@@ -202,63 +217,58 @@
|
||||
<div class="mb-4 flex items-start justify-between">
|
||||
<div>
|
||||
<h2 class="card-title text-2xl">
|
||||
{solicitacao.funcionario?.nome || 'Funcionário'}
|
||||
{periodo.funcionario?.nome || 'Funcionário'}
|
||||
</h2>
|
||||
<p class="text-base-content/70 mt-1 text-sm">
|
||||
Ano de Referência: {solicitacao.anoReferencia}
|
||||
Ano de Referência: {periodo.anoReferencia}
|
||||
</p>
|
||||
</div>
|
||||
<div class={`badge ${getStatusBadge(solicitacao.status)} badge-lg`}>
|
||||
{getStatusTexto(solicitacao.status)}
|
||||
<div class={`badge ${getStatusBadge(periodo.status)} badge-lg`}>
|
||||
{getStatusTexto(periodo.status)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Períodos Solicitados -->
|
||||
<!-- Período Solicitado -->
|
||||
<div class="mt-4">
|
||||
<h3 class="mb-3 text-lg font-semibold">Períodos Solicitados</h3>
|
||||
<div class="space-y-2">
|
||||
{#each solicitacao.periodos as periodo, index}
|
||||
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-3">
|
||||
<div class="badge badge-primary">{index + 1}</div>
|
||||
<div class="grid flex-1 grid-cols-3 gap-2 text-sm">
|
||||
<div>
|
||||
<span class="text-base-content/70">Início:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(periodo.dataInicio).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Fim:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{new Date(periodo.dataFim).toLocaleDateString('pt-BR')}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Dias:</span>
|
||||
<span class="text-primary ml-1 font-bold">{periodo.diasCorridos}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mb-3 text-lg font-semibold">Período Solicitado</h3>
|
||||
<div class="bg-base-200 rounded-lg p-4">
|
||||
<div class="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-base-content/70">Início:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{formatarDataString(periodo.dataInicio)}</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
<div>
|
||||
<span class="text-base-content/70">Fim:</span>
|
||||
<span class="ml-1 font-semibold"
|
||||
>{formatarDataString(periodo.dataFim)}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-base-content/70">Dias:</span>
|
||||
<span class="text-primary ml-1 font-bold">{periodo.diasFerias}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Observações -->
|
||||
{#if solicitacao.observacao}
|
||||
{#if periodo.observacao}
|
||||
<div class="mt-4">
|
||||
<h3 class="mb-2 font-semibold">Observações</h3>
|
||||
<div class="bg-base-200 rounded-lg p-3 text-sm">
|
||||
{solicitacao.observacao}
|
||||
{periodo.observacao}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Histórico -->
|
||||
{#if solicitacao.historicoAlteracoes && solicitacao.historicoAlteracoes.length > 0}
|
||||
{#if periodo.historicoAlteracoes && periodo.historicoAlteracoes.length > 0}
|
||||
<div class="mt-4">
|
||||
<h3 class="mb-2 font-semibold">Histórico</h3>
|
||||
<div class="space-y-1">
|
||||
{#each solicitacao.historicoAlteracoes as hist}
|
||||
{#each periodo.historicoAlteracoes as hist}
|
||||
<div class="text-base-content/70 flex items-center gap-2 text-xs">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -284,7 +294,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Ações (apenas para status aguardando_aprovacao) -->
|
||||
{#if solicitacao.status === 'aguardando_aprovacao'}
|
||||
{#if periodo.status === 'aguardando_aprovacao'}
|
||||
<div class="divider mt-6"></div>
|
||||
|
||||
{#if !modoAjuste}
|
||||
@@ -341,7 +351,7 @@
|
||||
<!-- Reprovar -->
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body p-4">
|
||||
<h4 class="mb-2 text-sm font-semibold">Reprovar Solicitação</h4>
|
||||
<h4 class="mb-2 text-sm font-semibold">Reprovar Período</h4>
|
||||
<textarea
|
||||
class="textarea textarea-bordered textarea-sm mb-2"
|
||||
placeholder="Motivo da reprovação..."
|
||||
@@ -376,53 +386,49 @@
|
||||
{:else}
|
||||
<!-- Modo Ajuste -->
|
||||
<div class="space-y-4">
|
||||
<h4 class="font-semibold">Ajustar Períodos</h4>
|
||||
{#each periodos as periodo, index}
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="mb-2 font-medium">Período {index + 1}</h5>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="form-control">
|
||||
<label class="label" for={`ajuste-inicio-${index}`}>
|
||||
<span class="label-text text-xs">Início</span>
|
||||
</label>
|
||||
<input
|
||||
id={`ajuste-inicio-${index}`}
|
||||
type="date"
|
||||
class="input input-bordered input-sm"
|
||||
bind:value={periodo.dataInicio}
|
||||
onchange={() => calcularDias(periodo)}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for={`ajuste-fim-${index}`}>
|
||||
<span class="label-text text-xs">Fim</span>
|
||||
</label>
|
||||
<input
|
||||
id={`ajuste-fim-${index}`}
|
||||
type="date"
|
||||
class="input input-bordered input-sm"
|
||||
bind:value={periodo.dataFim}
|
||||
onchange={() => calcularDias(periodo)}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for={`ajuste-dias-${index}`}>
|
||||
<span class="label-text text-xs">Dias</span>
|
||||
</label>
|
||||
<div
|
||||
id={`ajuste-dias-${index}`}
|
||||
class="bg-base-300 flex h-9 items-center rounded-lg px-3"
|
||||
role="textbox"
|
||||
aria-readonly="true"
|
||||
>
|
||||
<span class="font-bold">{periodo.diasCorridos}</span>
|
||||
</div>
|
||||
<h4 class="font-semibold">Ajustar Período</h4>
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="form-control">
|
||||
<label class="label" for="ajuste-inicio">
|
||||
<span class="label-text text-xs">Início</span>
|
||||
</label>
|
||||
<input
|
||||
id="ajuste-inicio"
|
||||
type="date"
|
||||
class="input input-bordered input-sm"
|
||||
bind:value={novaDataInicio}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="ajuste-fim">
|
||||
<span class="label-text text-xs">Fim</span>
|
||||
</label>
|
||||
<input
|
||||
id="ajuste-fim"
|
||||
type="date"
|
||||
class="input input-bordered input-sm"
|
||||
bind:value={novaDataFim}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="ajuste-dias">
|
||||
<span class="label-text text-xs">Dias</span>
|
||||
</label>
|
||||
<div
|
||||
id="ajuste-dias"
|
||||
class="bg-base-300 flex h-9 items-center rounded-lg px-3"
|
||||
role="textbox"
|
||||
aria-readonly="true"
|
||||
>
|
||||
<span class="font-bold">{diasAjustados}</span>
|
||||
<span class="ml-2 text-xs opacity-70">dias</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@@ -437,7 +443,7 @@
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm gap-2"
|
||||
onclick={ajustarEAprovar}
|
||||
disabled={processando}
|
||||
disabled={processando || !novaDataInicio || !novaDataFim || diasAjustados <= 0}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -461,7 +467,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Motivo Reprovação (se reprovado) -->
|
||||
{#if solicitacao.status === 'reprovado' && solicitacao.motivoReprovacao}
|
||||
{#if periodo.status === 'reprovado' && periodo.motivoReprovacao}
|
||||
<div class="alert alert-error mt-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -478,7 +484,7 @@
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-bold">Motivo da Reprovação:</div>
|
||||
<div class="text-sm">{solicitacao.motivoReprovacao}</div>
|
||||
<div class="text-sm">{periodo.motivoReprovacao}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import CalendarioFerias from './CalendarioFerias.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
|
||||
@@ -30,7 +29,15 @@
|
||||
let observacao = $state('');
|
||||
let processando = $state(false);
|
||||
|
||||
// Estados para os selects de data
|
||||
let dataInicioPeriodo = $state('');
|
||||
let dataFimPeriodo = $state('');
|
||||
|
||||
// Queries
|
||||
const funcionarioQuery = useQuery(api.funcionarios.getById, { id: funcionarioId });
|
||||
const funcionario = $derived(funcionarioQuery?.data);
|
||||
const regimeTrabalho = $derived(funcionario?.regimeTrabalho || 'clt');
|
||||
|
||||
const saldoQuery = $derived(
|
||||
useQuery(api.saldoFerias.obterSaldo, {
|
||||
funcionarioId,
|
||||
@@ -62,9 +69,98 @@
|
||||
return [anoAtual - 1, anoAtual, anoAtual + 1];
|
||||
});
|
||||
|
||||
// Configurações do calendário (baseado no saldo/regime)
|
||||
const maxPeriodos = $derived(saldo?.regimeTrabalho?.includes('Servidor') ? 2 : 3);
|
||||
const minDiasPorPeriodo = $derived(saldo?.regimeTrabalho?.includes('Servidor') ? 10 : 5);
|
||||
// Verificar se é regime estatutário PE ou Municipal
|
||||
const ehEstatutarioPEOuMunicipal = $derived(
|
||||
regimeTrabalho === 'estatutario_pe' || regimeTrabalho === 'estatutario_municipal'
|
||||
);
|
||||
|
||||
// Função para calcular dias entre duas datas
|
||||
function calcularDias(dataInicio: string, dataFim: string): number {
|
||||
if (!dataInicio || !dataFim) return 0;
|
||||
const inicio = new Date(dataInicio);
|
||||
const fim = new Date(dataFim);
|
||||
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
||||
return diffDays;
|
||||
}
|
||||
|
||||
// Função para formatar data sem problemas de timezone
|
||||
function formatarDataString(dataString: string): string {
|
||||
if (!dataString) return '';
|
||||
// Dividir a string da data (formato YYYY-MM-DD)
|
||||
const partes = dataString.split('-');
|
||||
if (partes.length !== 3) return dataString;
|
||||
// Retornar no formato DD/MM/YYYY
|
||||
return `${partes[2]}/${partes[1]}/${partes[0]}`;
|
||||
}
|
||||
|
||||
// Função para adicionar período
|
||||
function adicionarPeriodo() {
|
||||
if (!dataInicioPeriodo || !dataFimPeriodo) {
|
||||
toast.error('Selecione as datas de início e fim');
|
||||
return;
|
||||
}
|
||||
|
||||
const dias = calcularDias(dataInicioPeriodo, dataFimPeriodo);
|
||||
|
||||
if (dias <= 0) {
|
||||
toast.error('Data de fim deve ser posterior à data de início');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validações específicas para estatutário PE e Municipal
|
||||
// Permite períodos fracionados: cada período deve ser 15 ou 30 dias
|
||||
// Total não pode exceder 30 dias, mas pode ser menos
|
||||
if (ehEstatutarioPEOuMunicipal) {
|
||||
// Verificar se o período individual é válido (15 ou 30 dias)
|
||||
if (dias !== 15 && dias !== 30) {
|
||||
toast.error('Para seu regime, cada período deve ter exatamente 15 ou 30 dias');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar se já tem 2 períodos
|
||||
if (periodosFerias.length >= 2) {
|
||||
toast.error('Máximo de 2 períodos permitidos para seu regime');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar se o total não excede 30 dias
|
||||
const novoTotal = totalDiasSelecionados + dias;
|
||||
if (novoTotal > 30) {
|
||||
toast.error(`O total não pode exceder 30 dias. Você já tem ${totalDiasSelecionados} dias, adicionando ${dias} dias totalizaria ${novoTotal} dias.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar se o total não excede o saldo disponível
|
||||
const novoTotal = totalDiasSelecionados + dias;
|
||||
if (saldo && novoTotal > saldo.diasDisponiveis) {
|
||||
toast.error(`Total de dias (${novoTotal}) excede saldo disponível (${saldo.diasDisponiveis})`);
|
||||
return;
|
||||
}
|
||||
|
||||
periodosFerias = [
|
||||
...periodosFerias,
|
||||
{
|
||||
dataInicio: dataInicioPeriodo,
|
||||
dataFim: dataFimPeriodo,
|
||||
dias
|
||||
}
|
||||
];
|
||||
|
||||
toast.success(`Período de ${dias} dias adicionado! ✅`);
|
||||
|
||||
// Limpar campos
|
||||
dataInicioPeriodo = '';
|
||||
dataFimPeriodo = '';
|
||||
}
|
||||
|
||||
// Função para remover período
|
||||
function removerPeriodo(index: number) {
|
||||
const removido = periodosFerias[index];
|
||||
periodosFerias = periodosFerias.filter((_, i) => i !== index);
|
||||
toast.info(`Período de ${removido.dias} dias removido`);
|
||||
}
|
||||
|
||||
// Funções
|
||||
function proximoPasso() {
|
||||
@@ -74,7 +170,7 @@
|
||||
}
|
||||
|
||||
if (passoAtual === 2 && periodosFerias.length === 0) {
|
||||
toast.error('Selecione pelo menos 1 período de férias');
|
||||
toast.error('Adicione pelo menos 1 período de férias');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,16 +220,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handlePeriodoAdicionado(periodo: { dataInicio: string; dataFim: string; dias: number }) {
|
||||
periodosFerias = [...periodosFerias, periodo];
|
||||
toast.success(`Período de ${periodo.dias} dias adicionado! ✅`);
|
||||
}
|
||||
|
||||
function handlePeriodoRemovido(index: number) {
|
||||
const removido = periodosFerias[index];
|
||||
periodosFerias = periodosFerias.filter((_, i) => i !== index);
|
||||
toast.info(`Período de ${removido.dias} dias removido`);
|
||||
}
|
||||
// Calcular dias do período atual
|
||||
const diasPeriodoAtual = $derived(calcularDias(dataInicioPeriodo, dataFimPeriodo));
|
||||
</script>
|
||||
|
||||
<div class="wizard-ferias-container">
|
||||
@@ -216,8 +304,12 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-lg transition-all duration-300 hover:scale-105"
|
||||
class:btn-primary={anoSelecionado === ano}
|
||||
class:btn-outline={anoSelecionado !== ano}
|
||||
style:border-color={anoSelecionado === ano ? '#f97316' : undefined}
|
||||
style:border-width={anoSelecionado === ano ? '2px' : undefined}
|
||||
style:color={anoSelecionado === ano ? '#000000' : undefined}
|
||||
style:background-color={anoSelecionado === ano ? 'transparent' : undefined}
|
||||
style:box-shadow={anoSelecionado === ano ? '0 0 10px rgba(249, 115, 22, 0.3)' : undefined}
|
||||
onclick={() => (anoSelecionado = ano)}
|
||||
>
|
||||
{ano}
|
||||
@@ -322,9 +414,14 @@
|
||||
<div>
|
||||
<h4 class="font-bold">{saldo.regimeTrabalho}</h4>
|
||||
<p class="text-sm">
|
||||
Período aquisitivo: {new Date(saldo.dataInicio).toLocaleDateString('pt-BR')}
|
||||
a {new Date(saldo.dataFim).toLocaleDateString('pt-BR')}
|
||||
Período aquisitivo: {formatarDataString(saldo.dataInicio)}
|
||||
a {formatarDataString(saldo.dataFim)}
|
||||
</p>
|
||||
{#if ehEstatutarioPEOuMunicipal}
|
||||
<p class="mt-2 text-sm font-semibold">
|
||||
⚠️ Regras: Períodos de 15 ou 30 dias. Máximo 2 períodos. Total não pode exceder 30 dias.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -401,18 +498,131 @@
|
||||
{totalDiasSelecionados} dias | <strong>Restante:</strong>
|
||||
{(saldo?.diasDisponiveis || 0) - totalDiasSelecionados} dias
|
||||
</p>
|
||||
{#if ehEstatutarioPEOuMunicipal}
|
||||
<p class="mt-2 text-sm font-semibold">
|
||||
⚠️ Regras: Períodos de 15 ou 30 dias. Máximo 2 períodos. Total não pode exceder 30 dias.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendário -->
|
||||
<CalendarioFerias
|
||||
periodosExistentes={periodosFerias}
|
||||
onPeriodoAdicionado={handlePeriodoAdicionado}
|
||||
onPeriodoRemovido={handlePeriodoRemovido}
|
||||
{maxPeriodos}
|
||||
{minDiasPorPeriodo}
|
||||
modoVisualizacao="month"
|
||||
></CalendarioFerias>
|
||||
<!-- Formulário para adicionar período -->
|
||||
<div class="card bg-base-100 shadow-lg mb-6">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title mb-4">Adicionar Período</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Data Início</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
class="input input-bordered"
|
||||
bind:value={dataInicioPeriodo}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Data Fim</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
class="input input-bordered"
|
||||
bind:value={dataFimPeriodo}
|
||||
min={dataInicioPeriodo || undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Dias</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center">
|
||||
<span class="font-bold text-primary">{diasPeriodoAtual}</span>
|
||||
<span class="ml-2 text-sm opacity-70">dias</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions mt-4">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary gap-2"
|
||||
onclick={adicionarPeriodo}
|
||||
disabled={!dataInicioPeriodo || !dataFimPeriodo || diasPeriodoAtual <= 0}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Adicionar Período
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de períodos adicionados -->
|
||||
{#if periodosFerias.length > 0}
|
||||
<div class="card bg-base-100 shadow-lg mb-6">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title mb-4">Períodos Adicionados ({periodosFerias.length})</h3>
|
||||
<div class="space-y-3">
|
||||
{#each periodosFerias as periodo, index (index)}
|
||||
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-4">
|
||||
<div
|
||||
class="badge badge-lg badge-primary flex h-12 w-12 items-center justify-center font-bold text-white"
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold">
|
||||
{formatarDataString(periodo.dataInicio)}
|
||||
até
|
||||
{formatarDataString(periodo.dataFim)}
|
||||
</p>
|
||||
<p class="text-base-content/70 text-sm">
|
||||
{periodo.dias} dias corridos
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-error btn-sm gap-2"
|
||||
onclick={() => removerPeriodo(index)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
Remover
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Validações -->
|
||||
{#if validacao && periodosFerias.length > 0}
|
||||
@@ -529,17 +739,9 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold">
|
||||
{new Date(periodo.dataInicio).toLocaleDateString('pt-BR', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
{formatarDataString(periodo.dataInicio)}
|
||||
até
|
||||
{new Date(periodo.dataFim).toLocaleDateString('pt-BR', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})}
|
||||
{formatarDataString(periodo.dataFim)}
|
||||
</p>
|
||||
<p class="text-base-content/70 text-sm">
|
||||
{periodo.dias} dias corridos
|
||||
|
||||
Reference in New Issue
Block a user