diff --git a/apps/web/src/lib/components/AprovarFerias.svelte b/apps/web/src/lib/components/AprovarFerias.svelte
index 7dc924e..8a8cdac 100644
--- a/apps/web/src/lib/components/AprovarFerias.svelte
+++ b/apps/web/src/lib/components/AprovarFerias.svelte
@@ -63,6 +63,28 @@
processando = true;
erro = '';
+ // Validar se as datas e condições estão dentro do regime do funcionário
+ if (!solicitacao.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
+ }))
+ });
+
+ if (!validacao.valido) {
+ erro = `Não é possível aprovar: ${validacao.erros.join('; ')}`;
+ processando = false;
+ return;
+ }
+
await client.mutation(api.ferias.aprovar, {
solicitacaoId: solicitacao._id,
gestorId: gestorId
@@ -105,6 +127,37 @@
processando = true;
erro = '';
+ // Validar se as datas ajustadas e condições estão dentro do regime do funcionário
+ if (!solicitacao.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;
+ }
+ }
+
+ 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
+ }))
+ });
+
+ if (!validacao.valido) {
+ erro = `Não é possível aprovar com ajuste: ${validacao.erros.join('; ')}`;
+ processando = false;
+ return;
+ }
+
await client.mutation(api.ferias.ajustarEAprovar, {
solicitacaoId: solicitacao._id,
gestorId: gestorId,
diff --git a/apps/web/src/lib/components/ti/charts/BarChart3D.svelte b/apps/web/src/lib/components/ti/charts/BarChart3D.svelte
new file mode 100644
index 0000000..cef1b58
--- /dev/null
+++ b/apps/web/src/lib/components/ti/charts/BarChart3D.svelte
@@ -0,0 +1,372 @@
+
+
+
+
+
diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte
index 3da2728..95c78f1 100644
--- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte
@@ -91,6 +91,7 @@
: { data: null }
);
+ // Query para times onde o usuário é gestor - usando $derived para garantir reatividade
const meusTimesGestorQuery = $derived(
currentUser?.data?._id
? useQuery(api.times.listarPorGestor, {
@@ -105,10 +106,66 @@
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
const minhasAusencias = $derived(minhasAusenciasQuery?.data || []);
const meuTime = $derived(meuTimeQuery?.data);
+
+ // Extração de meusTimesGestor
const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []);
- // Verificar se é gestor
- const ehGestor = $derived((meusTimesGestor || []).length > 0);
+ // Estado estável para controlar se é gestor (evita desaparecimento das abas)
+ let ehGestorEstavel = $state(false);
+ let ultimaVerificacaoGestor = $state(null);
+
+ // Calcular se é gestor - com lógica mais robusta (sem atualizar estado aqui)
+ const ehGestorCalculado = $derived.by(() => {
+ // Verificar se tem times como gestor
+ const temTimesComoGestor = (meusTimesGestor || []).length > 0;
+
+ // Verificar se tem role de TI que pode aprovar (TI_MASTER, TI_ADMIN)
+ const rolePermiteAprovacao = currentUser?.data?.role?.nome === 'TI_MASTER' ||
+ currentUser?.data?.role?.nome === 'TI_ADMIN';
+
+ // Verificar se tem solicitações de subordinados (indica que é gestor)
+ const temSolicitacoesSubordinados = (solicitacoesSubordinados || []).length > 0 ||
+ (ausenciasSubordinados || []).length > 0;
+
+ // É gestor se: tem times OU tem role de TI OU tem solicitações de subordinados
+ return temTimesComoGestor || rolePermiteAprovacao || temSolicitacoesSubordinados;
+ });
+
+ // Efeito para atualizar o estado estável (evita desaparecimento das abas)
+ $effect(() => {
+ const resultado = ehGestorCalculado;
+
+ // Log para depuração (apenas quando mudar)
+ if (import.meta.env.DEV && resultado !== ehGestorEstavel) {
+ console.log('🔍 [Perfil] Status de gestor mudou:', {
+ temTimesComoGestor: (meusTimesGestor || []).length > 0,
+ rolePermiteAprovacao: currentUser?.data?.role?.nome === 'TI_MASTER' || currentUser?.data?.role?.nome === 'TI_ADMIN',
+ role: currentUser?.data?.role?.nome,
+ temSolicitacoesSubordinados: (solicitacoesSubordinados || []).length > 0 || (ausenciasSubordinados || []).length > 0,
+ resultado,
+ meusTimesGestor: meusTimesGestor?.length || 0,
+ timestamp: new Date().toISOString()
+ });
+ }
+
+ // Atualizar estado estável apenas se o resultado for true (para manter as abas visíveis)
+ // ou se já passou tempo suficiente desde a última verificação
+ const agora = Date.now();
+ if (resultado) {
+ ehGestorEstavel = true;
+ ultimaVerificacaoGestor = agora;
+ } else if (ultimaVerificacaoGestor === null || (agora - ultimaVerificacaoGestor) > 5000) {
+ // Só atualiza para false se passou mais de 5 segundos desde a última verificação positiva
+ // Isso evita que as abas desapareçam durante atualizações temporárias das queries
+ ehGestorEstavel = resultado;
+ if (!resultado) {
+ ultimaVerificacaoGestor = agora;
+ }
+ }
+ });
+
+ // Valor final usado para renderizar as abas
+ const ehGestor = $derived(ehGestorEstavel);
// Filtrar minhas solicitações
const solicitacoesFiltradas = $derived(
diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
index a346d4b..028d93f 100644
--- a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
@@ -776,9 +776,243 @@
{#if eventosQuery?.data}
-
+
- Defina o status das solicitações que deseja visualizar.
-
-
+ {#if isLoading && !hasError}
+ {#each Array.from({ length: 4 }, (_, i) => i) as index (index)}
+
+
+
+
+
-
-
-
-
-
- Nome do funcionário
-
-
-
-
- Pesquise por nome completo ou parcial para localizar rapidamente um colaborador.
-
-
-
-
-
-
-
-
- Matrícula
-
-
-
-
- Utilize a matrícula funcional para filtrar solicitações específicas.
-
-
-
-
-
-
-
-
- E-mail institucional
-
-
-
-
- Busque usando o correio institucional cadastrado na ficha do colaborador.
-
-
-
-
-
-
-
-
- Mês de referência
-
-
-
-
- Filtra as solicitações que possuem períodos ativos dentro do mês informado.
-
-
-
-
-
-
-
-
- Período personalizado
-
-
-
-
- Data inicial
-
-
-
- Data final
-
-
-
-
- Combine as datas para localizar períodos específicos de férias aprovadas ou em
- andamento.
-
-
-
-
-
-
-
-
-
-
-
-
+ {/each}
+ {:else}
+
+
-
-
- Impressão da Programação de Férias
-
-
- Escolha o período desejado e gere um relatório pronto para impressão com todos os
- colaboradores em férias, incluindo detalhes completos de cada período.
-
-
+
Total
+
{stats.total}
+
Solicitações
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- O relatório será aberto em uma nova aba com formatação própria para impressão. Verifique se
- o bloqueador de pop-ups está desabilitado para o domínio.
-
-
-
-
-
-
-
- Solicitações ({solicitacoesFiltradas.length})
-
-
- {#if solicitacoesFiltradas.length === 0}
-
+
+
- Nenhuma solicitação encontrada com os filtros aplicados.
- {:else}
-
-
-
-
-
Funcionário
-
Time
-
Ano
-
Períodos
-
Total Dias
-
Status
-
Solicitado em
-
-
-
- {#each solicitacoesFiltradas as solicitacao (solicitacao._id)}
-
- Ajuste com o mouse os intervalos exibidos no gráfico.
-
-
- {/if}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Dias Totais Aprovados por Ano de Referência
-
-
- Volume agregado de dias e número de solicitações por ano
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+ {/if}
+
+
+ {#if !isLoading || !hasError}
+
+
+
+
+
+
+
+
+ Impressão da Programação de Férias
+
+
+ Escolha o período desejado e gere um relatório pronto para impressão com todos os
+ colaboradores em férias, incluindo detalhes completos de cada período.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ O relatório será aberto em uma nova aba com formatação própria para impressão. Verifique
+ se o bloqueador de pop-ups está desabilitado para o domínio.
+
+
+
+ {/if}
+
+
+ {#if isLoading && !hasError}
+
+
+
+
+ {#each Array.from({ length: 5 }, (_, i) => i) as index (index)}
+
+ {/each}
+
+
+
+ {:else}
+
+
+
+ Solicitações ({solicitacoesFiltradas.length})
+
+
+ {#if solicitacoesFiltradas.length === 0}
+
+
+ Nenhuma solicitação encontrada com os filtros aplicados.
+
+ {:else}
+
+
+
+
+
Funcionário
+
Time
+
Ano
+
Períodos
+
Total Dias
+
Status
+
Solicitado em
+
Ações
+
+
+
+ {#each solicitacoesFiltradas as solicitacao (solicitacao._id)}
+