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:
2025-11-13 15:54:59 -03:00
parent 4ae5baffcc
commit c058865817
11 changed files with 1150 additions and 949 deletions

View File

@@ -15,13 +15,13 @@
type TodasSolicitacoes = FunctionReturnType<typeof api.ferias.listarTodas>;
type Solicitacao = TodasSolicitacoes[number];
type PeriodoDetalhado = {
solicitacaoId: Id<'solicitacoesFerias'>;
funcionarioId: Id<'funcionarios'> | null;
funcionarioId: Id<'funcionarios'>;
anoReferencia: number;
feriasId: Id<'ferias'>;
funcionarioNome: string;
matricula?: string | null;
timeNome?: string | null;
timeCor?: string | null;
anoReferencia: number;
status: Solicitacao['status'];
dataInicio: string;
dataFim: string;
@@ -39,8 +39,8 @@
const client = useConvexClient();
const currentUser = useQuery(api.auth.getCurrentUser, {});
// Estado para controlar qual solicitação está selecionada para mudança de status
let solicitacaoSelecionada = $state<Id<'solicitacoesFerias'> | null>(null);
// Estado para controlar qual período está selecionado para mudança de status
let periodoSelecionado = $state<Id<'ferias'> | null>(null);
// Estados de loading e error
const isLoading = $derived(todasSolicitacoesQuery?.isLoading ?? true);
@@ -106,10 +106,10 @@
let dataInicioRelatorio = $state<string>('');
let dataFimRelatorio = $state<string>('');
// Filtrar solicitações
// Filtrar períodos individuais
const solicitacoesFiltradas = $derived(
solicitacoes.filter((solicitacao) => {
if (filtroStatus !== 'todos' && solicitacao.status !== filtroStatus) {
solicitacoes.filter((periodo) => {
if (filtroStatus !== 'todos' && periodo.status !== filtroStatus) {
return false;
}
@@ -118,7 +118,7 @@
const emailFiltro = normalizarTexto(filtroEmail.trim());
if (nomeFiltro || matriculaFiltro || emailFiltro) {
const funcionario = solicitacao.funcionario;
const funcionario = periodo.funcionario;
if (!funcionario) return false;
const contato = funcionario as {
@@ -165,27 +165,25 @@
const inicioComparacao = inicioFiltro ?? new SvelteDate(-8640000000000000);
const fimComparacao = fimFiltro ?? new SvelteDate(8640000000000000);
return solicitacao.periodos.some((periodo) => {
const inicioPeriodo = criarDataHora(periodo.dataInicio, 'inicio');
const fimPeriodo = criarDataHora(periodo.dataFim, 'fim');
if (!inicioPeriodo || !fimPeriodo) {
const inicioPeriodo = criarDataHora(periodo.dataInicio, 'inicio');
const fimPeriodo = criarDataHora(periodo.dataFim, 'fim');
if (!inicioPeriodo || !fimPeriodo) {
return false;
}
if (intervaloMes) {
if (fimPeriodo < intervaloMes.inicio || inicioPeriodo > intervaloMes.fim) {
return false;
}
}
if (intervaloMes) {
if (fimPeriodo < intervaloMes.inicio || inicioPeriodo > intervaloMes.fim) {
return false;
}
if (inicioFiltro || fimFiltro) {
if (fimPeriodo < inicioComparacao || inicioPeriodo > fimComparacao) {
return false;
}
}
if (inicioFiltro || fimFiltro) {
if (fimPeriodo < inicioComparacao || inicioPeriodo > fimComparacao) {
return false;
}
}
return true;
});
return true;
})
);
@@ -203,27 +201,25 @@
const solicitacoesAprovadas = $derived(
solicitacoesFiltradas.filter(
(s) => s.status === 'aprovado' || s.status === 'data_ajustada_aprovada'
(p) => p.status === 'aprovado' || p.status === 'data_ajustada_aprovada' || p.status === 'EmFérias'
)
);
const periodosDetalhados = $derived<Array<PeriodoDetalhado>>(
solicitacoesAprovadas
.flatMap((solicitacao) =>
solicitacao.periodos.map((periodo) => ({
solicitacaoId: solicitacao._id,
funcionarioId: solicitacao.funcionarioId ?? null,
funcionarioNome: solicitacao.funcionario?.nome ?? 'Funcionário não encontrado',
matricula: solicitacao.funcionario?.matricula ?? null,
timeNome: solicitacao.time?.nome ?? null,
timeCor: solicitacao.time?.cor ?? null,
anoReferencia: solicitacao.anoReferencia,
status: solicitacao.status,
dataInicio: periodo.dataInicio,
dataFim: periodo.dataFim,
diasCorridos: periodo.diasCorridos
}))
)
.map((periodo) => ({
feriasId: periodo._id,
funcionarioId: periodo.funcionarioId,
anoReferencia: periodo.anoReferencia,
funcionarioNome: periodo.funcionario?.nome ?? 'Funcionário não encontrado',
matricula: periodo.funcionario?.matricula ?? null,
timeNome: periodo.time?.nome ?? null,
timeCor: periodo.time?.cor ?? null,
status: periodo.status,
dataInicio: periodo.dataInicio,
dataFim: periodo.dataFim,
diasCorridos: periodo.diasFerias
}))
.sort(
(a, b) => new SvelteDate(a.dataInicio).getTime() - new SvelteDate(b.dataInicio).getTime()
)
@@ -310,7 +306,7 @@
for (const solicitacao of solicitacoesAprovadas) {
const totalDias = solicitacao.periodos.reduce(
(acc, periodo) => acc + periodo.diasCorridos,
(acc, periodo) => acc + periodo.diasFerias,
0
);
const existente = agregados.get(solicitacao.anoReferencia) ?? {
@@ -441,7 +437,7 @@
periodosDetalhados.map((periodo, indice) => {
const corBase = periodo.timeCor ?? coresCalendario[indice % coresCalendario.length];
return {
id: `${String(periodo.solicitacaoId)}-${indice}`,
id: `${String(periodo.feriasId)}-${indice}`,
title: `${periodo.funcionarioNome} (${periodo.diasCorridos} dia${periodo.diasCorridos === 1 ? '' : 's'})`,
start: periodo.dataInicio,
end: adicionarDias(periodo.dataFim, 1),
@@ -692,6 +688,16 @@
return textos[status] || status;
}
// 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]}`;
}
function formatarData(data: string | number | Date) {
const instancia = data instanceof Date ? data : new SvelteDate(data);
return instancia.toLocaleDateString('pt-BR');
@@ -771,16 +777,12 @@
filtroPeriodoFim = '';
}
function totalDiasSolicitacao(solicitacao: Solicitacao): number {
return solicitacao.periodos.reduce((acc, periodo) => acc + periodo.diasCorridos, 0);
}
async function selecionarSolicitacao(solicitacaoId: Id<'solicitacoesFerias'>) {
solicitacaoSelecionada = solicitacaoId;
async function selecionarPeriodo(feriasId: Id<'ferias'>) {
periodoSelecionado = feriasId;
}
async function recarregar() {
solicitacaoSelecionada = null;
periodoSelecionado = null;
}
let chartContainer: HTMLDivElement | null = null;
@@ -1678,61 +1680,61 @@
<tr class="text-base-content/70 text-sm tracking-wide uppercase">
<th>Funcionário</th>
<th>Time</th>
<th>Ano</th>
<th>Períodos</th>
<th>Ano</th>
<th>Período</th>
<th>Dias</th>
<th>Status</th>
<th>Solicitado em</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<tbody>
{#each solicitacoesFiltradas as periodo (periodo._id)}
<tr>
<td>
<div class="flex items-center gap-3">
<div class="avatar placeholder">
<div class="bg-primary text-primary-content w-10 rounded-full">
<span class="text-xs"
<span class="text-xs"
>{periodo.funcionario?.nome.substring(0, 2).toUpperCase()}</span
>
</div>
</div>
<div>
<div>
<div class="font-bold">{periodo.funcionario?.nome}</div>
<div class="text-xs opacity-50">
<div class="text-xs opacity-50">
{periodo.funcionario?.matricula || 'S/N'}
</div>
</div>
</div>
</td>
<td>
<td>
{#if periodo.time}
<div
class="badge badge-outline"
class="badge badge-outline"
style="border-color: {periodo.time.cor}"
>
>
{periodo.time.nome}
</div>
{:else}
<span class="text-base-content/50 text-xs">Sem time</span>
{/if}
</td>
<td>{solicitacao.anoReferencia}</td>
<td>{solicitacao.periodos.length} período(s)</td>
<td class="text-base-content font-bold"
>{totalDiasSolicitacao(solicitacao)} dia(s)</td
</td>
<td>{periodo.anoReferencia}</td>
<td>
<div class={`badge ${getStatusBadge(solicitacao.status)}`}>
<td>
{formatarDataString(periodo.dataInicio)} - {formatarDataString(periodo.dataFim)}
</td>
<td class="text-base-content font-bold">{periodo.diasFerias} dia(s)</td>
<td>
<div class={`badge ${getStatusBadge(periodo.status)}`}>
{getStatusTexto(periodo.status)}
</div>
</td>
</td>
<td class="text-xs">{formatarData(periodo._creationTime)}</td>
<td>
<button
type="button"
class="btn btn-primary btn-sm gap-2"
class="btn btn-primary btn-sm gap-2"
onclick={() => selecionarPeriodo(periodo._id)}
>
<svg
@@ -1917,8 +1919,8 @@
{/if}
</main>
<!-- Modal de Mudança de Status -->
{#if solicitacaoSelecionada && currentUser.data}
<!-- Modal de Mudança de Status -->
{#if periodoSelecionado && currentUser.data}
{#await client.query( api.ferias.obterDetalhes, { feriasId: periodoSelecionado } ) then detalhes}
{#if detalhes}
<dialog class="modal modal-open">
@@ -1926,13 +1928,13 @@
<AlterarStatusFerias
solicitacao={detalhes}
usuarioId={currentUser.data._id}
onSucesso={recarregar}
onSucesso={recarregar}
onCancelar={() => (periodoSelecionado = null)}
/>
</div>
<form method="dialog" class="modal-backdrop">
<button
type="button"
type="button"
onclick={() => (periodoSelecionado = null)}
aria-label="Fechar modal"
>