From 0e5a26b5fdfc94fd5e445089dceaf55bf536e050 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Mon, 17 Nov 2025 19:07:03 -0300 Subject: [PATCH] feat: add 'Cancelado_RH' status to vacation management - Introduced a new status 'Cancelado_RH' for vacation requests, allowing for better tracking of cancellations by HR. - Updated the UI components to reflect the new status, including badge colors and alert messages. - Enhanced backend schema and mutation to support the new status, ensuring consistency across the application. - Improved logging and state management for better performance and user experience. --- .../lib/components/AlterarStatusFerias.svelte | 39 +-- .../recursos-humanos/ferias/+page.svelte | 260 +++++++++++------- packages/backend/convex/ferias.ts | 3 +- packages/backend/convex/schema.ts | 3 +- 4 files changed, 178 insertions(+), 127 deletions(-) diff --git a/apps/web/src/lib/components/AlterarStatusFerias.svelte b/apps/web/src/lib/components/AlterarStatusFerias.svelte index cb4c52a..2eff5bb 100644 --- a/apps/web/src/lib/components/AlterarStatusFerias.svelte +++ b/apps/web/src/lib/components/AlterarStatusFerias.svelte @@ -29,7 +29,8 @@ aprovado: 'badge-success', reprovado: 'badge-error', data_ajustada_aprovada: 'badge-info', - EmFérias: 'badge-info' + EmFérias: 'badge-info', + Cancelado_RH: 'badge-error' }; return badges[status] || 'badge-neutral'; } @@ -40,19 +41,20 @@ aprovado: 'Aprovado', reprovado: 'Reprovado', data_ajustada_aprovada: 'Data Ajustada e Aprovada', - EmFérias: 'Em Férias' + EmFérias: 'Em Férias', + Cancelado_RH: 'Cancelado RH' }; return textos[status] || status; } - async function voltarParaAguardando() { + async function cancelarPorRH() { try { processando = true; erro = ''; await client.mutation(api.ferias.atualizarStatus, { feriasId: solicitacao._id, - novoStatus: 'aguardando_aprovacao', + novoStatus: 'Cancelado_RH', usuarioId: usuarioId }); @@ -150,10 +152,10 @@ {/if} - - {#if solicitacao.status !== 'aguardando_aprovacao'} + + {#if solicitacao.status !== 'Cancelado_RH'}
-
+
-

Alterar Status

+

Cancelar Férias

- Ao voltar para "Aguardando Aprovação", a solicitação ficará disponível para aprovação ou - reprovação pelo gestor. + Ao cancelar as férias, o status será alterado para "Cancelado RH" e a solicitação não poderá mais ser processada.
@@ -179,8 +180,8 @@
{:else}
-
+
- Esta solicitação já está aguardando aprovação. + Esta solicitação já foi cancelada pelo RH.
{/if} diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte index 47e9ba1..c21d87f 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte @@ -45,17 +45,13 @@ // Estados de loading e error const isLoading = $derived(todasSolicitacoesQuery?.isLoading ?? true); - // Debug: Log dos dados carregados + // Debug: Log dos dados carregados (apenas uma vez quando dados mudam) + let ultimoTotalDados = $state(-1); $effect(() => { - if (todasSolicitacoesQuery?.data) { - console.log('📦 [Backend] Dados carregados:', { - total: todasSolicitacoesQuery.data.length, - isLoading: todasSolicitacoesQuery.isLoading, - dados: todasSolicitacoesQuery.data - }); - } - if (todasSolicitacoesQuery?.isLoading !== undefined) { - console.log('🔄 [Backend] Estado de loading:', todasSolicitacoesQuery.isLoading); + const total = todasSolicitacoesQuery?.data?.length ?? 0; + if (total !== ultimoTotalDados && total > 0) { + ultimoTotalDados = total; + console.log('📦 [Backend] Dados carregados:', { total }); } }); @@ -83,11 +79,18 @@ // Manter último valor válido para evitar dados desaparecendo let ultimasSolicitacoesValidas = $state([]); + let ultimoDataHash = $state(''); - // Atualizar apenas quando temos dados válidos + // Atualizar apenas quando temos dados válidos e quando realmente mudou $effect(() => { - if (todasSolicitacoesQuery?.data && !hasError) { - ultimasSolicitacoesValidas = todasSolicitacoesQuery.data; + const dataAtual = todasSolicitacoesQuery?.data; + if (dataAtual && !hasError) { + // Criar hash simples para comparar se os dados realmente mudaram + const dataHash = JSON.stringify(dataAtual.map(d => d._id)); + if (dataHash !== ultimoDataHash) { + ultimoDataHash = dataHash; + ultimasSolicitacoesValidas = dataAtual; + } } }); @@ -259,31 +262,69 @@ let rangeInicioIndice = $state(0); let rangeFimIndice = $state(0); + let ultimoTamanhoPeriodos = $state(0); + let atualizandoRanges = $state(false); $effect(() => { - if (periodosPorMes.length === 0) { - rangeInicioIndice = 0; - rangeFimIndice = 0; + // Prevenir loops infinitos + if (atualizandoRanges) { return; } - const ultimoIndice = periodosPorMes.length - 1; - - if (rangeFimIndice === 0 && rangeInicioIndice === 0) { - rangeFimIndice = ultimoIndice; + const tamanhoAtual = periodosPorMes.length; + + // Só atualizar se o tamanho mudou + if (tamanhoAtual === 0) { + if (rangeInicioIndice !== 0 || rangeFimIndice !== 0) { + atualizandoRanges = true; + rangeInicioIndice = 0; + rangeFimIndice = 0; + ultimoTamanhoPeriodos = 0; + atualizandoRanges = false; + } return; } - if (rangeInicioIndice > ultimoIndice) { - rangeInicioIndice = ultimoIndice; + // Se o tamanho mudou, resetar os ranges + if (tamanhoAtual !== ultimoTamanhoPeriodos) { + atualizandoRanges = true; + ultimoTamanhoPeriodos = tamanhoAtual; + const ultimoIndice = tamanhoAtual - 1; + + if (rangeFimIndice === 0 && rangeInicioIndice === 0) { + rangeFimIndice = ultimoIndice; + atualizandoRanges = false; + return; + } + atualizandoRanges = false; } - if (rangeFimIndice > ultimoIndice) { - rangeFimIndice = ultimoIndice; + const ultimoIndice = tamanhoAtual - 1; + let precisaAtualizar = false; + let novoInicio = rangeInicioIndice; + let novoFim = rangeFimIndice; + + if (novoInicio > ultimoIndice) { + novoInicio = ultimoIndice; + precisaAtualizar = true; } - if (rangeInicioIndice > rangeFimIndice) { - rangeInicioIndice = rangeFimIndice; + if (novoFim > ultimoIndice) { + novoFim = ultimoIndice; + precisaAtualizar = true; + } + + if (novoInicio > novoFim) { + novoInicio = novoFim; + precisaAtualizar = true; + } + + // Só atualizar se realmente precisar e se os valores mudaram + if (precisaAtualizar && (novoInicio !== rangeInicioIndice || novoFim !== rangeFimIndice)) { + atualizandoRanges = true; + rangeInicioIndice = novoInicio; + rangeFimIndice = novoFim; + atualizandoRanges = false; } }); @@ -305,19 +346,16 @@ (() => { const agregados = new SvelteMap(); - for (const solicitacao of solicitacoesAprovadas) { - const totalDias = solicitacao.periodos.reduce( - (acc, periodo) => acc + periodo.diasFerias, - 0 - ); - const existente = agregados.get(solicitacao.anoReferencia) ?? { - ano: solicitacao.anoReferencia, + for (const periodo of solicitacoesAprovadas) { + // solicitacoesAprovadas são períodos individuais, não agrupados + const existente = agregados.get(periodo.anoReferencia) ?? { + ano: periodo.anoReferencia, solicitacoes: 0, diasTotais: 0 }; existente.solicitacoes += 1; - existente.diasTotais += totalDias; - agregados.set(solicitacao.anoReferencia, existente); + existente.diasTotais += periodo.diasFerias; + agregados.set(periodo.anoReferencia, existente); } return Array.from(agregados.values()).sort((a, b) => a.ano - b.ano); @@ -456,13 +494,13 @@ }) ); - // Debug: Log dos eventos gerados + // Debug: Log dos eventos gerados (apenas quando mudar significativamente) + let ultimoTotalEventos = $state(-1); $effect(() => { - console.log('📅 [Eventos] Total de eventos:', eventosFerias.length); - console.log('📋 [Periodos] Total de períodos:', periodosDetalhados.length); - console.log('✅ [Aprovadas] Total de solicitações aprovadas:', solicitacoesAprovadas.length); - if (eventosFerias.length > 0) { - console.log('📅 [Eventos] Primeiro evento:', eventosFerias[0]); + const totalEventos = eventosFerias.length; + if (totalEventos !== ultimoTotalEventos && import.meta.env.DEV) { + ultimoTotalEventos = totalEventos; + console.log('📅 [Eventos] Total:', totalEventos); } }); @@ -484,16 +522,9 @@ } try { - console.log('🔄 [Calendário] Iniciando inicialização...'); - console.log('📊 [Calendário] Estado:', { - container: !!calendarioContainer, - loading: isLoading, - error: hasError, - eventos: eventosFerias.length, - solicitacoes: solicitacoes.length, - solicitacoesAprovadas: solicitacoesAprovadas.length, - periodosDetalhados: periodosDetalhados.length - }); + if (import.meta.env.DEV) { + console.log('🔄 [Calendário] Iniciando inicialização...'); + } const [coreModule, dayGridModule, interactionModule, localeModule] = await Promise.all([ import('@fullcalendar/core'), @@ -510,7 +541,6 @@ calendarioInstance || calendarioInicializado ) { - console.log('⚠️ [Calendário] Condições alteradas após imports'); return; } @@ -519,11 +549,6 @@ const interactionPlugin = interactionModule.default; const ptBrLocale = localeModule.default; - console.log('✅ [Calendário] Criando instância com', eventosFerias.length, 'eventos'); - if (eventosFerias.length > 0) { - console.log('📅 [Calendário] Primeiro evento:', eventosFerias[0]); - } - calendarioInstance = new CalendarClass(calendarioContainer, { plugins: [dayGridPlugin, interactionPlugin], initialView: 'dayGridMonth', @@ -547,7 +572,9 @@ calendarioInstance.render(); calendarioInicializado = true; - console.log('✅ [Calendário] Inicializado com sucesso!'); + if (import.meta.env.DEV) { + console.log('✅ [Calendário] Inicializado com sucesso!'); + } } catch (error) { console.error('❌ [Calendário] Erro ao inicializar:', error); if (error instanceof Error) { @@ -613,68 +640,87 @@ }; }); - // Sincronizar eventos do calendário quando mudarem + // Sincronizar eventos do calendário quando mudarem (com debounce) + let ultimosEventosSerializados = $state(''); + let timeoutAtualizacaoCalendario = $state | null>(null); + $effect(() => { // Não atualizar se o calendário não estiver inicializado ou estiver carregando if (!calendarioInstance || !calendarioInicializado || isLoading || hasError) { return; } - try { - console.log('🔄 Atualizando eventos do calendário:', eventosFerias.length, 'eventos'); + // Serializar eventos para comparar mudanças + const eventosSerializados = JSON.stringify(eventosFerias.map(e => ({ + id: e.id, + start: e.start, + end: e.end, + title: e.title + }))); - // Remover todos os eventos existentes - calendarioInstance.removeAllEvents(); - - // Adicionar novos eventos - const eventosClonados = eventosFerias.map((evento) => ({ - ...evento, - extendedProps: { ...evento.extendedProps } - })); - - // Adicionar eventos em lote - for (const evento of eventosClonados) { - calendarioInstance.addEvent(evento); - } - - console.log('✅ Eventos atualizados com sucesso!'); - } catch (error) { - // Log do erro, mas não interromper o fluxo - if (error instanceof Error) { - console.error('❌ Erro ao atualizar eventos do calendário:', error.message); - } else { - console.error('❌ Erro ao atualizar eventos do calendário:', error); - } + // Se os eventos não mudaram, não fazer nada + if (eventosSerializados === ultimosEventosSerializados) { + return; } + + // Limpar timeout anterior se existir + if (timeoutAtualizacaoCalendario) { + clearTimeout(timeoutAtualizacaoCalendario); + } + + // Atualizar referência serializada imediatamente para evitar re-execuções + ultimosEventosSerializados = eventosSerializados; + + // Debounce para evitar atualizações muito frequentes + timeoutAtualizacaoCalendario = setTimeout(() => { + try { + // Verificar novamente se o calendário ainda está válido + if (!calendarioInstance || !calendarioInicializado) { + return; + } + + // Remover todos os eventos existentes + calendarioInstance.removeAllEvents(); + + // Adicionar novos eventos + const eventosClonados = eventosFerias.map((evento) => ({ + ...evento, + extendedProps: { ...evento.extendedProps } + })); + + // Adicionar eventos em lote + for (const evento of eventosClonados) { + calendarioInstance.addEvent(evento); + } + } catch (error) { + // Log do erro, mas não interromper o fluxo + if (error instanceof Error) { + console.error('❌ Erro ao atualizar eventos do calendário:', error.message); + } else { + console.error('❌ Erro ao atualizar eventos do calendário:', error); + } + } + }, 300); // Debounce de 300ms + + // Cleanup + return () => { + if (timeoutAtualizacaoCalendario) { + clearTimeout(timeoutAtualizacaoCalendario); + } + }; }); - if (typeof window !== 'undefined') { - $effect(() => { - ( - window as Window & typeof globalThis & { __feriasSolicitacoes: TodasSolicitacoes } - ).__feriasSolicitacoes = solicitacoes; - ( - window as Window & typeof globalThis & { __feriasFiltradas: TodasSolicitacoes } - ).__feriasFiltradas = solicitacoesFiltradas; - ( - window as Window & typeof globalThis & { __feriasAprovadas: TodasSolicitacoes } - ).__feriasAprovadas = solicitacoesAprovadas; - ( - window as Window & typeof globalThis & { __feriasPeriodos: PeriodoDetalhado[] } - ).__feriasPeriodos = periodosDetalhados; - (window as Window & typeof globalThis & { __feriasPorMes: PeriodoPorMes[] }).__feriasPorMes = - periodosPorMes; - ( - window as Window & typeof globalThis & { __feriasPorAno: SolicitacoesPorAnoResumo[] } - ).__feriasPorAno = solicitacoesPorAno; - }); - } + // Atualizar variáveis no window apenas em desenvolvimento (desabilitado temporariamente para evitar loops) + // if (typeof window !== 'undefined' && import.meta.env.DEV) { + // // Código comentado para evitar loops infinitos + // } function getStatusBadge(status: string) { const badges: Record = { aguardando_aprovacao: 'badge-warning', aprovado: 'badge-success', reprovado: 'badge-error', - data_ajustada_aprovada: 'badge-info' + data_ajustada_aprovada: 'badge-info', + Cancelado_RH: 'badge-error' }; return badges[status] || 'badge-neutral'; } @@ -684,7 +730,8 @@ aguardando_aprovacao: 'Aguardando', aprovado: 'Aprovado', reprovado: 'Reprovado', - data_ajustada_aprovada: 'Ajustado' + data_ajustada_aprovada: 'Ajustado', + Cancelado_RH: 'Cancelado RH' }; return textos[status] || status; } @@ -1320,6 +1367,7 @@ +

Defina o status das solicitações que deseja visualizar. diff --git a/packages/backend/convex/ferias.ts b/packages/backend/convex/ferias.ts index 762e4af..a89e9d0 100644 --- a/packages/backend/convex/ferias.ts +++ b/packages/backend/convex/ferias.ts @@ -588,7 +588,8 @@ export const atualizarStatus = mutation({ v.literal("aguardando_aprovacao"), v.literal("aprovado"), v.literal("reprovado"), - v.literal("data_ajustada_aprovada") + v.literal("data_ajustada_aprovada"), + v.literal("Cancelado_RH") ), usuarioId: v.id("usuarios"), }, diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 7a52679..48daf96 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -332,7 +332,8 @@ export default defineSchema({ v.literal("aprovado"), v.literal("reprovado"), v.literal("data_ajustada_aprovada"), - v.literal("EmFérias") + v.literal("EmFérias"), + v.literal("Cancelado_RH") ), gestorId: v.optional(v.id("usuarios")), observacao: v.optional(v.string()),