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.
This commit is contained in:
2025-11-17 19:07:03 -03:00
parent 2c3d231d20
commit 0e5a26b5fd
4 changed files with 178 additions and 127 deletions

View File

@@ -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<number>(-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<TodasSolicitacoes>([]);
let ultimoDataHash = $state<string>('');
// 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<number, SolicitacoesPorAnoResumo>();
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<number>(-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<string>('');
let timeoutAtualizacaoCalendario = $state<ReturnType<typeof setTimeout> | 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<string, string> = {
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 @@
<option value="aguardando_aprovacao">Aguardando Aprovação</option>
<option value="aprovado">Aprovado</option>
<option value="reprovado">Reprovado</option>
<option value="data_ajustada_aprovada">Data Ajustada</option>
<option value="Cancelado_RH">Cancelado RH</option>
</select>
<p class="text-base-content/60 text-xs leading-relaxed">