feat: implement absence management features in the dashboard

- Added functionality for managing absence requests, including listing, approving, and rejecting requests.
- Enhanced the user interface to display statistics and pending requests for better oversight.
- Updated backend schema to support absence requests and notifications, ensuring data integrity and efficient handling.
- Integrated new components for absence request forms and approval workflows, improving user experience and administrative efficiency.
This commit is contained in:
2025-11-04 14:23:46 -03:00
parent f02eb473ca
commit a93d55f02b
13 changed files with 3837 additions and 497 deletions

View File

@@ -94,7 +94,7 @@
const resultado = await convex.mutation(api.autenticacao.alterarSenha, {
token: authStore.token,
senhaAntiga: senhaAtual,
senhaAtual: senhaAtual,
novaSenha: novaSenha,
});

View File

@@ -1,48 +1,218 @@
<script lang="ts">
</script>
<main class="container mx-auto px-4 py-4">
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><a href="/" class="text-primary hover:underline">Dashboard</a></li>
<li>Secretaria de Gestão de Pessoas</li>
</ul>
</div>
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<div class="p-3 bg-teal-500/20 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-teal-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Secretaria de Gestão de Pessoas</h1>
<p class="text-base-content/70">Gestão estratégica de pessoas</p>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex flex-col items-center justify-center py-12 text-center">
<div class="mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-base-content/20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
<h2 class="text-2xl font-bold mb-2">Módulo em Desenvolvimento</h2>
<p class="text-base-content/70 max-w-md mb-6">
O módulo da Secretaria de Gestão de Pessoas está sendo desenvolvido e em breve estará disponível com funcionalidades completas de gestão estratégica de pessoas.
</p>
<div class="badge badge-warning badge-lg gap-2">
<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="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Em Desenvolvimento
</div>
</div>
</div>
</div>
</main>
<script lang="ts">
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import { goto } from "$app/navigation";
const client = useConvexClient();
// Buscar todas as solicitações de ausências
const ausenciasQuery = useQuery(api.ausencias.listarTodas, {});
const ausencias = $derived(ausenciasQuery?.data || []);
// Estatísticas
const stats = $derived({
total: ausencias.length,
pendentes: ausencias.filter((a) => a.status === "aguardando_aprovacao").length,
aprovadas: ausencias.filter((a) => a.status === "aprovado").length,
reprovadas: ausencias.filter((a) => a.status === "reprovado").length,
});
// Solicitações pendentes (últimas 5)
const pendentes = $derived(
ausencias
.filter((a) => a.status === "aguardando_aprovacao")
.slice(0, 5)
);
function calcularDias(dataInicio: string, dataFim: string): number {
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
const diff = fim.getTime() - inicio.getTime();
return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
}
function getStatusBadge(status: string) {
const badges: Record<string, string> = {
aguardando_aprovacao: "badge-warning",
aprovado: "badge-success",
reprovado: "badge-error",
};
return badges[status] || "badge-neutral";
}
function getStatusTexto(status: string) {
const textos: Record<string, string> = {
aguardando_aprovacao: "Aguardando",
aprovado: "Aprovado",
reprovado: "Reprovado",
};
return textos[status] || status;
}
</script>
<main class="container mx-auto px-4 py-4">
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><a href="/" class="text-primary hover:underline">Dashboard</a></li>
<li>Secretaria de Gestão de Pessoas</li>
</ul>
</div>
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<div class="p-3 bg-teal-500/20 rounded-xl">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 text-teal-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">
Secretaria de Gestão de Pessoas
</h1>
<p class="text-base-content/70">Gestão estratégica de pessoas</p>
</div>
</div>
</div>
<!-- Card: Gestão de Ausências -->
<div class="card bg-base-100 shadow-xl mb-6 border-t-4 border-orange-500">
<div class="card-body">
<div class="flex items-center justify-between mb-4">
<h2 class="card-title text-2xl">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-7 w-7 text-orange-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Gestão de Ausências
</h2>
<button
type="button"
class="btn btn-sm btn-outline btn-primary"
onclick={() => goto("/recursos-humanos/ausencias")}
>
Ver Todas
</button>
</div>
<!-- Estatísticas -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Total</div>
<div class="stat-value text-orange-600">{stats.total}</div>
<div class="stat-desc">Solicitações</div>
</div>
<div class="stat bg-warning/10 rounded-lg">
<div class="stat-title">Pendentes</div>
<div class="stat-value text-warning">{stats.pendentes}</div>
<div class="stat-desc">Aguardando</div>
</div>
<div class="stat bg-success/10 rounded-lg">
<div class="stat-title">Aprovadas</div>
<div class="stat-value text-success">{stats.aprovadas}</div>
<div class="stat-desc">Deferidas</div>
</div>
<div class="stat bg-error/10 rounded-lg">
<div class="stat-title">Reprovadas</div>
<div class="stat-value text-error">{stats.reprovadas}</div>
<div class="stat-desc">Indeferidas</div>
</div>
</div>
<!-- Lista de Pendentes -->
<div>
<h3 class="font-bold text-lg mb-3">
Solicitações Pendentes de Aprovação
</h3>
{#if pendentes.length === 0}
<div class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>Nenhuma solicitação pendente no momento.</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Funcionário</th>
<th>Período</th>
<th>Dias</th>
<th>Status</th>
<th>Solicitado em</th>
</tr>
</thead>
<tbody>
{#each pendentes as ausencia}
<tr>
<td class="font-semibold">
{ausencia.funcionario?.nome || "N/A"}
</td>
<td>
{new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "}
{new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
</td>
<td class="font-bold">
{calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias
</td>
<td>
<div class={`badge ${getStatusBadge(ausencia.status)}`}>
{getStatusTexto(ausencia.status)}
</div>
</td>
<td class="text-xs">
{new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if stats.pendentes > 5}
<div class="mt-4 text-center">
<button
type="button"
class="btn btn-sm btn-outline"
onclick={() => goto("/recursos-humanos/ausencias")}
>
Ver todas as {stats.pendentes} pendentes
</button>
</div>
{/if}
{/if}
</div>
</div>
</div>
</main>

View File

@@ -4,13 +4,15 @@
import { authStore } from "$lib/stores/auth.svelte";
import AprovarFerias from "$lib/components/AprovarFerias.svelte";
import WizardSolicitacaoFerias from "$lib/components/ferias/WizardSolicitacaoFerias.svelte";
import WizardSolicitacaoAusencia from "$lib/components/ausencias/WizardSolicitacaoAusencia.svelte";
import AprovarAusencias from "$lib/components/AprovarAusencias.svelte";
import { generateAvatarGallery, type Avatar } from "$lib/utils/avatars";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
import { page } from "$app/stores";
const client = useConvexClient();
let abaAtiva = $state<"meu-perfil" | "minhas-ferias" | "aprovar-ferias">(
let abaAtiva = $state<"meu-perfil" | "minhas-ferias" | "minhas-ausencias" | "aprovar-ferias" | "aprovar-ausencias">(
"meu-perfil"
);
let solicitacaoSelecionada = $state<any>(null);
@@ -29,6 +31,14 @@
let mostrarWizard = $state(false);
let filtroStatusFerias = $state<string>("todos");
// Estados para Minhas Ausências
let mostrarWizardAusencia = $state(false);
let filtroStatusAusencias = $state<string>("todos");
let solicitacaoAusenciaSelecionada = $state<Id<"solicitacoesAusencias"> | null>(null);
// Estados para Aprovar Ausências (Gestores)
let solicitacaoAusenciaAprovar = $state<Id<"solicitacoesAusencias"> | null>(null);
// Galeria de avatares (30 avatares profissionais 3D realistas)
const avatarGallery = generateAvatarGallery(30);
@@ -42,10 +52,15 @@
}
});
// FuncionarioId disponível diretamente do authStore
const funcionarioIdDisponivel = $derived(authStore.usuario?.funcionarioId ?? null);
// Debug: Verificar funcionarioId
$effect(() => {
console.log("🔍 [Perfil] funcionarioId:", authStore.usuario?.funcionarioId);
console.log("🔍 [Perfil] Usuário completo:", authStore.usuario);
console.log("🔍 [Perfil] funcionarioIdDisponivel:", funcionarioIdDisponivel);
console.log("🔍 [Perfil] Botão habilitado?", !!funcionarioIdDisponivel);
});
// Queries
@@ -65,6 +80,14 @@
: { data: [] }
);
const ausenciasSubordinadosQuery = $derived(
authStore.usuario?._id
? useQuery(api.ausencias.listarSolicitacoesSubordinados, {
gestorId: authStore.usuario._id as Id<"usuarios">,
})
: { data: [] }
);
const minhasSolicitacoesQuery = $derived(
funcionarioQuery.data
? useQuery(api.ferias.listarMinhasSolicitacoes, {
@@ -73,6 +96,14 @@
: { data: [] }
);
const minhasAusenciasQuery = $derived(
funcionarioQuery.data
? useQuery(api.ausencias.listarMinhasSolicitacoes, {
funcionarioId: funcionarioQuery.data._id,
})
: { data: [] }
);
const meuTimeQuery = $derived(
funcionarioQuery.data
? useQuery(api.times.obterTimeFuncionario, {
@@ -93,7 +124,11 @@
const solicitacoesSubordinados = $derived(
solicitacoesSubordinadosQuery?.data || []
);
const ausenciasSubordinados = $derived(
ausenciasSubordinadosQuery?.data || []
);
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
const minhasAusencias = $derived(minhasAusenciasQuery?.data || []);
const meuTime = $derived(meuTimeQuery?.data);
const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []);
@@ -108,6 +143,14 @@
})
);
// Filtrar minhas ausências
const ausenciasFiltradas = $derived(
minhasAusencias.filter((a) => {
if (filtroStatusAusencias !== "todos" && a.status !== filtroStatusAusencias) return false;
return true;
})
);
// Estatísticas das minhas férias
const statsMinhasFerias = $derived({
total: minhasSolicitacoes.length,
@@ -117,6 +160,14 @@
emFerias: funcionario?.statusFerias === "em_ferias" ? 1 : 0,
});
// Estatísticas das minhas ausências
const statsMinhasAusencias = $derived({
total: minhasAusencias.length,
aguardando: minhasAusencias.filter((a) => a.status === "aguardando_aprovacao").length,
aprovadas: minhasAusencias.filter((a) => a.status === "aprovado").length,
reprovadas: minhasAusencias.filter((a) => a.status === "reprovado").length,
});
async function recarregar() {
solicitacaoSelecionada = null;
}
@@ -512,6 +563,29 @@
Minhas Férias
</button>
<button
type="button"
role="tab"
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "minhas-ausencias" ? "tab-active bg-gradient-to-r from-orange-600 to-amber-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
onclick={() => (abaAtiva = "minhas-ausencias")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Minhas Ausências
</button>
{#if ehGestor}
<button
type="button"
@@ -542,6 +616,36 @@
</span>
{/if}
</button>
<button
type="button"
role="tab"
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "aprovar-ausencias" ? "tab-active bg-gradient-to-r from-orange-600 to-amber-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
onclick={() => (abaAtiva = "aprovar-ausencias")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Aprovar Ausências
{#if (ausenciasSubordinados || []).filter((a: any) => a.status === "aguardando_aprovacao").length > 0}
<span class="badge badge-error badge-sm ml-2 animate-pulse">
{(ausenciasSubordinados || []).filter(
(a: any) => a.status === "aguardando_aprovacao"
).length}
</span>
{/if}
</button>
{/if}
</div>
@@ -1221,6 +1325,151 @@
</div>
</div>
</div>
{:else if abaAtiva === "minhas-ausencias"}
<!-- Minhas Ausências -->
<div class="space-y-6">
<!-- Estatísticas -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="stat bg-base-100 shadow-lg rounded-box border border-base-300">
<div class="stat-figure text-orange-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div class="stat-title">Total</div>
<div class="stat-value text-orange-500">{statsMinhasAusencias.total}</div>
<div class="stat-desc">Solicitações</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-warning/30">
<div class="stat-figure text-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="stat-title">Aguardando</div>
<div class="stat-value text-warning">{statsMinhasAusencias.aguardando}</div>
<div class="stat-desc">Pendentes</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-success/30">
<div class="stat-figure text-success">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="stat-title">Aprovadas</div>
<div class="stat-value text-success">{statsMinhasAusencias.aprovadas}</div>
<div class="stat-desc">Deferidas</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-error/30">
<div class="stat-figure text-error">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="stat-title">Reprovadas</div>
<div class="stat-value text-error">{statsMinhasAusencias.reprovadas}</div>
<div class="stat-desc">Indeferidas</div>
</div>
</div>
<!-- Filtros e Botão Nova Solicitação -->
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<h2 class="card-title text-lg">Filtros</h2>
<button
type="button"
class="btn btn-warning gap-2"
onclick={() => {
const funcionarioId = authStore.usuario?.funcionarioId;
console.log("🔍 [Perfil] Click no botão - funcionarioId:", funcionarioId);
if (funcionarioId) {
mostrarWizardAusencia = true;
} else {
alert(`Não foi possível identificar seu funcionário.\n\nVerifique no console (F12) o objeto usuario:\n${JSON.stringify(authStore.usuario, null, 2)}\n\nEntre em contato com o suporte se o problema persistir.`);
}
}}
disabled={!funcionarioIdDisponivel}
title={funcionarioIdDisponivel ? "Clique para solicitar uma ausência" : "Funcionário não identificado. Entre em contato com o suporte."}
>
<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 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Solicitar Ausência
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-1 gap-4 mt-4">
<div class="form-control">
<label class="label" for="status-ausencias">
<span class="label-text">Status</span>
</label>
<select id="status-ausencias" class="select select-bordered" bind:value={filtroStatusAusencias}>
<option value="todos">Todos</option>
<option value="aguardando_aprovacao">Aguardando Aprovação</option>
<option value="aprovado">Aprovado</option>
<option value="reprovado">Reprovado</option>
</select>
</div>
</div>
</div>
</div>
<!-- Lista de Solicitações -->
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="card-title text-lg mb-4">
Minhas Solicitações ({ausenciasFiltradas.length})
</h2>
{#if ausenciasFiltradas.length === 0}
<div class="alert">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>Nenhuma solicitação encontrada com os filtros aplicados.</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Período</th>
<th>Dias</th>
<th>Motivo</th>
<th>Status</th>
<th>Solicitado em</th>
</tr>
</thead>
<tbody>
{#each ausenciasFiltradas as ausencia}
<tr>
<td>
{new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até {new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
</td>
<td class="font-bold">
{Math.ceil((new Date(ausencia.dataFim).getTime() - new Date(ausencia.dataInicio).getTime()) / (1000 * 60 * 60 * 24)) + 1} dias
</td>
<td class="max-w-xs truncate" title={ausencia.motivo}>
{ausencia.motivo}
</td>
<td>
<div class={`badge ${getStatusBadge(ausencia.status)}`}>
{getStatusTexto(ausencia.status)}
</div>
</td>
<td class="text-xs">{new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
</div>
{:else if abaAtiva === "aprovar-ferias"}
<!-- Aprovar Férias (Gestores) PREMIUM -->
<div class="card bg-base-100 shadow-2xl border-t-4 border-green-500">
@@ -1379,10 +1628,162 @@
{/if}
</div>
</div>
{:else if abaAtiva === "aprovar-ausencias"}
<!-- Aprovar Ausências (Gestores) -->
<div class="card bg-base-100 shadow-2xl border-t-4 border-orange-500">
<div class="card-body">
<h2 class="card-title text-2xl mb-6 flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-7 w-7 text-orange-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Solicitações de Ausências da Equipe
<div class="badge badge-lg badge-warning ml-2">
{ausenciasSubordinados.length}
</div>
</h2>
{#if ausenciasSubordinados.length === 0}
<div class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span class="font-semibold"
>Nenhuma solicitação pendente no momento.</span
>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra table-lg">
<thead>
<tr class="bg-base-200">
<th class="font-bold">Funcionário</th>
<th class="font-bold">Time</th>
<th class="font-bold">Período</th>
<th class="font-bold">Dias</th>
<th class="font-bold">Status</th>
<th class="font-bold">Ações</th>
</tr>
</thead>
<tbody>
{#each ausenciasSubordinados as ausencia}
<tr class="hover:bg-base-200 transition-colors">
<td>
<div class="font-bold">
{ausencia.funcionario?.nome || "N/A"}
</div>
</td>
<td>
{#if ausencia.time}
<div
class="badge badge-lg font-semibold"
style="background-color: {ausencia.time.cor}20; border-color: {ausencia.time.cor}; color: {ausencia.time.cor}"
>
{ausencia.time.nome}
</div>
{/if}
</td>
<td class="font-semibold">
{new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "}
{new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
</td>
<td class="font-bold text-lg">
{Math.ceil((new Date(ausencia.dataFim).getTime() - new Date(ausencia.dataInicio).getTime()) / (1000 * 60 * 60 * 24)) + 1} dias
</td>
<td>
<div
class={`badge badge-lg font-semibold ${getStatusBadge(ausencia.status)}`}
>
{getStatusTexto(ausencia.status)}
</div>
</td>
<td>
{#if ausencia.status === "aguardando_aprovacao"}
<button
type="button"
class="btn btn-warning btn-sm gap-2 shadow-lg hover:scale-105 transition-transform"
onclick={() =>
(solicitacaoAusenciaAprovar = ausencia._id)}
>
<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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
/>
</svg>
Analisar
</button>
{:else}
<button
type="button"
class="btn btn-ghost btn-sm gap-2"
onclick={() =>
(solicitacaoAusenciaAprovar = ausencia._id)}
>
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
Detalhes
</button>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
{/if}
</div>
<!-- Modal de Aprovação -->
<!-- Modal de Aprovação de Férias -->
{#if solicitacaoSelecionada}
<dialog class="modal modal-open">
<div class="modal-box max-w-4xl">
@@ -1716,3 +2117,56 @@
<div class="modal-backdrop" onclick={() => (mostrarWizard = false)}></div>
</dialog>
{/if}
<!-- Modal Wizard Solicitação de Ausência -->
{#if mostrarWizardAusencia && funcionarioIdDisponivel}
<dialog class="modal modal-open">
<div class="modal-box max-w-4xl max-h-[90vh] overflow-hidden">
<h3 class="font-bold text-2xl mb-6 text-center">
Nova Solicitação de Ausência
</h3>
<div class="max-h-[80vh] overflow-y-auto">
<WizardSolicitacaoAusencia
funcionarioId={funcionarioIdDisponivel}
onSucesso={() => {
mostrarWizardAusencia = false;
}}
onCancelar={() => (mostrarWizardAusencia = false)}
/>
</div>
</div>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="modal-backdrop" onclick={() => (mostrarWizardAusencia = false)}></div>
</dialog>
{/if}
<!-- Modal de Aprovação de Ausências -->
{#if solicitacaoAusenciaAprovar && authStore.usuario}
{#await client.query(api.ausencias.obterDetalhes, {
solicitacaoId: solicitacaoAusenciaAprovar,
}) then detalhes}
{#if detalhes}
<dialog class="modal modal-open">
<div class="modal-box max-w-4xl">
<AprovarAusencias
solicitacao={detalhes}
gestorId={authStore.usuario._id}
onSucesso={() => {
solicitacaoAusenciaAprovar = null;
}}
onCancelar={() => (solicitacaoAusenciaAprovar = null)}
/>
</div>
<form method="dialog" class="modal-backdrop">
<button
type="button"
onclick={() => (solicitacaoAusenciaAprovar = null)}
aria-label="Fechar modal"
>Fechar</button
>
</form>
</dialog>
{/if}
{/await}
{/if}

View File

@@ -0,0 +1,419 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import { authStore } from "$lib/stores/auth.svelte";
import AprovarAusencias from "$lib/components/AprovarAusencias.svelte";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
const client = useConvexClient();
// Buscar TODAS as solicitações de ausências (Dashboard RH)
const todasAusenciasQuery = useQuery(api.ausencias.listarTodas, {});
let filtroStatus = $state<string>("todos");
let solicitacaoSelecionada = $state<Id<"solicitacoesAusencias"> | null>(null);
const ausencias = $derived(todasAusenciasQuery?.data || []);
// Filtrar solicitações
const ausenciasFiltradas = $derived(
ausencias.filter((a) => {
// Filtro de status
if (filtroStatus !== "todos" && a.status !== filtroStatus) return false;
return true;
})
);
// Estatísticas gerais
const stats = $derived({
total: ausencias.length,
aguardando: ausencias.filter((a) => a.status === "aguardando_aprovacao").length,
aprovadas: ausencias.filter((a) => a.status === "aprovado").length,
reprovadas: ausencias.filter((a) => a.status === "reprovado").length,
});
function getStatusBadge(status: string) {
const badges: Record<string, string> = {
aguardando_aprovacao: "badge-warning",
aprovado: "badge-success",
reprovado: "badge-error",
};
return badges[status] || "badge-neutral";
}
function getStatusTexto(status: string) {
const textos: Record<string, string> = {
aguardando_aprovacao: "Aguardando",
aprovado: "Aprovado",
reprovado: "Reprovado",
};
return textos[status] || status;
}
function calcularDias(dataInicio: string, dataFim: string): number {
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
const diff = fim.getTime() - inicio.getTime();
return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
}
async function selecionarSolicitacao(solicitacaoId: Id<"solicitacoesAusencias">) {
solicitacaoSelecionada = solicitacaoId;
}
async function recarregar() {
solicitacaoSelecionada = null;
}
</script>
<main class="container mx-auto px-4 py-6 max-w-7xl">
<!-- Breadcrumb -->
<div class="text-sm breadcrumbs mb-4">
<ul>
<li>
<a href="/recursos-humanos" class="text-primary hover:underline"
>Recursos Humanos</a
>
</li>
<li>Gestão de Ausências</li>
</ul>
</div>
<!-- Header -->
<div class="mb-6">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="p-3 bg-orange-500/20 rounded-xl">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 text-orange-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Dashboard de Ausências</h1>
<p class="text-base-content/70">
Visão geral de todas as solicitações de ausências
</p>
</div>
</div>
<button
class="btn btn-ghost gap-2"
onclick={() => goto("/recursos-humanos")}
>
<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="M10 19l-7-7m0 0l7-7m-7 7h18"
/>
</svg>
Voltar
</button>
</div>
</div>
<!-- Estatísticas -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="stat bg-base-100 shadow-lg rounded-box border border-base-300">
<div class="stat-figure text-orange-500">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<div class="stat-title">Total</div>
<div class="stat-value text-orange-500">{stats.total}</div>
<div class="stat-desc">Solicitações</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-warning/30">
<div class="stat-figure text-warning">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div class="stat-title">Pendentes</div>
<div class="stat-value text-warning">{stats.aguardando}</div>
<div class="stat-desc">Aguardando</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-success/30">
<div class="stat-figure text-success">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div class="stat-title">Aprovadas</div>
<div class="stat-value text-success">{stats.aprovadas}</div>
<div class="stat-desc">Deferidas</div>
</div>
<div class="stat bg-base-100 shadow-lg rounded-box border border-error/30">
<div class="stat-figure text-error">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div class="stat-title">Reprovadas</div>
<div class="stat-value text-error">{stats.reprovadas}</div>
<div class="stat-desc">Indeferidas</div>
</div>
</div>
<!-- Filtros -->
<div class="card bg-base-100 shadow-lg mb-6">
<div class="card-body">
<h2 class="card-title text-lg mb-4">Filtros</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-control">
<label class="label" for="filtro-status">
<span class="label-text">Status</span>
</label>
<select
id="filtro-status"
class="select select-bordered"
bind:value={filtroStatus}
>
<option value="todos">Todos</option>
<option value="aguardando_aprovacao">Aguardando Aprovação</option>
<option value="aprovado">Aprovado</option>
<option value="reprovado">Reprovado</option>
</select>
</div>
</div>
</div>
</div>
<!-- Lista de Solicitações -->
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="card-title text-lg mb-4">
Todas as Solicitações ({ausenciasFiltradas.length})
</h2>
{#if ausenciasFiltradas.length === 0}
<div class="alert">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-info shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>Nenhuma solicitação encontrada com os filtros aplicados.</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Funcionário</th>
<th>Time</th>
<th>Período</th>
<th>Dias</th>
<th>Motivo</th>
<th>Status</th>
<th>Solicitado em</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each ausenciasFiltradas as ausencia}
<tr>
<td class="font-semibold">
{ausencia.funcionario?.nome || "N/A"}
</td>
<td>
{#if ausencia.time}
<div
class="badge badge-sm font-semibold"
style="background-color: {ausencia.time.cor}20; border-color: {ausencia.time.cor}; color: {ausencia.time.cor}"
>
{ausencia.time.nome}
</div>
{:else}
<span class="text-base-content/50">Sem time</span>
{/if}
</td>
<td>
{new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "}
{new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
</td>
<td class="font-bold">
{calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias
</td>
<td class="max-w-xs truncate" title={ausencia.motivo}>
{ausencia.motivo}
</td>
<td>
<div class={`badge ${getStatusBadge(ausencia.status)}`}>
{getStatusTexto(ausencia.status)}
</div>
</td>
<td class="text-xs">
{new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")}
</td>
<td>
{#if ausencia.status === "aguardando_aprovacao"}
<button
type="button"
class="btn btn-primary btn-sm gap-2"
onclick={() => selecionarSolicitacao(ausencia._id)}
>
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
Ver Detalhes
</button>
{:else}
<button
type="button"
class="btn btn-ghost btn-sm gap-2"
onclick={() => selecionarSolicitacao(ausencia._id)}
>
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
Ver Detalhes
</button>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
</main>
<!-- Modal de Aprovação -->
{#if solicitacaoSelecionada && authStore.usuario}
{#await client.query(api.ausencias.obterDetalhes, {
solicitacaoId: solicitacaoSelecionada,
}) then detalhes}
{#if detalhes}
<dialog class="modal modal-open">
<div class="modal-box max-w-4xl">
<AprovarAusencias
solicitacao={detalhes}
gestorId={authStore.usuario._id}
onSucesso={recarregar}
onCancelar={() => (solicitacaoSelecionada = null)}
/>
</div>
<form method="dialog" class="modal-backdrop">
<button
type="button"
onclick={() => (solicitacaoSelecionada = null)}
aria-label="Fechar modal"
>Fechar</button
>
</form>
</dialog>
{/if}
{/await}
{/if}

View File

@@ -1,48 +1,214 @@
<script lang="ts">
</script>
<main class="container mx-auto px-4 py-4">
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><a href="/" class="text-primary hover:underline">Dashboard</a></li>
<li>Secretaria Executiva</li>
</ul>
</div>
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<div class="p-3 bg-indigo-500/20 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Secretaria Executiva</h1>
<p class="text-base-content/70">Gestão executiva e administrativa</p>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex flex-col items-center justify-center py-12 text-center">
<div class="mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-base-content/20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<h2 class="text-2xl font-bold mb-2">Módulo em Desenvolvimento</h2>
<p class="text-base-content/70 max-w-md mb-6">
O módulo da Secretaria Executiva está sendo desenvolvido e em breve estará disponível com funcionalidades completas de gestão executiva e administrativa.
</p>
<div class="badge badge-warning badge-lg gap-2">
<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="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Em Desenvolvimento
</div>
</div>
</div>
</div>
</main>
<script lang="ts">
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import { goto } from "$app/navigation";
const client = useConvexClient();
// Buscar todas as solicitações de ausências
const ausenciasQuery = useQuery(api.ausencias.listarTodas, {});
const ausencias = $derived(ausenciasQuery?.data || []);
// Estatísticas
const stats = $derived({
total: ausencias.length,
pendentes: ausencias.filter((a) => a.status === "aguardando_aprovacao").length,
aprovadas: ausencias.filter((a) => a.status === "aprovado").length,
reprovadas: ausencias.filter((a) => a.status === "reprovado").length,
});
// Solicitações pendentes (últimas 5)
const pendentes = $derived(
ausencias
.filter((a) => a.status === "aguardando_aprovacao")
.slice(0, 5)
);
function calcularDias(dataInicio: string, dataFim: string): number {
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
const diff = fim.getTime() - inicio.getTime();
return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
}
function getStatusBadge(status: string) {
const badges: Record<string, string> = {
aguardando_aprovacao: "badge-warning",
aprovado: "badge-success",
reprovado: "badge-error",
};
return badges[status] || "badge-neutral";
}
function getStatusTexto(status: string) {
const textos: Record<string, string> = {
aguardando_aprovacao: "Aguardando",
aprovado: "Aprovado",
reprovado: "Reprovado",
};
return textos[status] || status;
}
</script>
<main class="container mx-auto px-4 py-4">
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><a href="/" class="text-primary hover:underline">Dashboard</a></li>
<li>Secretaria Executiva</li>
</ul>
</div>
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<div class="p-3 bg-indigo-500/20 rounded-xl">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 text-indigo-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Secretaria Executiva</h1>
<p class="text-base-content/70">Gestão executiva e administrativa</p>
</div>
</div>
</div>
<!-- Card: Gestão de Ausências -->
<div class="card bg-base-100 shadow-xl mb-6 border-t-4 border-orange-500">
<div class="card-body">
<div class="flex items-center justify-between mb-4">
<h2 class="card-title text-2xl">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-7 w-7 text-orange-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
Gestão de Ausências
</h2>
<button
type="button"
class="btn btn-sm btn-outline btn-primary"
onclick={() => goto("/recursos-humanos/ausencias")}
>
Ver Todas
</button>
</div>
<!-- Estatísticas -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title">Total</div>
<div class="stat-value text-orange-600">{stats.total}</div>
<div class="stat-desc">Solicitações</div>
</div>
<div class="stat bg-warning/10 rounded-lg">
<div class="stat-title">Pendentes</div>
<div class="stat-value text-warning">{stats.pendentes}</div>
<div class="stat-desc">Aguardando</div>
</div>
<div class="stat bg-success/10 rounded-lg">
<div class="stat-title">Aprovadas</div>
<div class="stat-value text-success">{stats.aprovadas}</div>
<div class="stat-desc">Deferidas</div>
</div>
<div class="stat bg-error/10 rounded-lg">
<div class="stat-title">Reprovadas</div>
<div class="stat-value text-error">{stats.reprovadas}</div>
<div class="stat-desc">Indeferidas</div>
</div>
</div>
<!-- Lista de Pendentes -->
<div>
<h3 class="font-bold text-lg mb-3">Solicitações Pendentes de Aprovação</h3>
{#if pendentes.length === 0}
<div class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>Nenhuma solicitação pendente no momento.</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Funcionário</th>
<th>Período</th>
<th>Dias</th>
<th>Status</th>
<th>Solicitado em</th>
</tr>
</thead>
<tbody>
{#each pendentes as ausencia}
<tr>
<td class="font-semibold">
{ausencia.funcionario?.nome || "N/A"}
</td>
<td>
{new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "}
{new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
</td>
<td class="font-bold">
{calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias
</td>
<td>
<div class={`badge ${getStatusBadge(ausencia.status)}`}>
{getStatusTexto(ausencia.status)}
</div>
</td>
<td class="text-xs">
{new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if stats.pendentes > 5}
<div class="mt-4 text-center">
<button
type="button"
class="btn btn-sm btn-outline"
onclick={() => goto("/recursos-humanos/ausencias")}
>
Ver todas as {stats.pendentes} pendentes
</button>
</div>
{/if}
{/if}
</div>
</div>
</div>
</main>