refactor: update user role management and enhance UI components
- Updated the user role management logic to improve type safety and error handling, including better handling of role permissions and user associations. - Refactored the UI components for user management, enhancing the layout and styling for better user experience. - Removed outdated code related to menu permissions and streamlined the database schema for roles and profiles. - Improved the overall structure and readability of the codebase, ensuring consistency across components.
This commit is contained in:
@@ -2,18 +2,17 @@
|
||||
import { useConvexClient, useQuery } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import WizardSolicitacaoFerias from "$lib/components/ferias/WizardSolicitacaoFerias.svelte";
|
||||
import DashboardFerias from "$lib/components/ferias/DashboardFerias.svelte";
|
||||
import AprovarFerias from "$lib/components/AprovarFerias.svelte";
|
||||
import WizardSolicitacaoFerias from "$lib/components/ferias/WizardSolicitacaoFerias.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">(
|
||||
"meu-perfil"
|
||||
);
|
||||
let mostrarFormSolicitar = $state(false);
|
||||
let solicitacaoSelecionada = $state<any>(null);
|
||||
let mostrarModalFoto = $state(false);
|
||||
let uploadandoFoto = $state(false);
|
||||
@@ -26,6 +25,10 @@
|
||||
let fotoPerfilLocal = $state<string | null>(null);
|
||||
let avatarLocal = $state<string | null>(null);
|
||||
|
||||
// Estados para Minhas Férias
|
||||
let mostrarWizard = $state(false);
|
||||
let filtroStatusFerias = $state<string>("todos");
|
||||
|
||||
// Galeria de avatares (30 avatares profissionais 3D realistas)
|
||||
const avatarGallery = generateAvatarGallery(30);
|
||||
|
||||
@@ -54,14 +57,6 @@
|
||||
: { data: null }
|
||||
);
|
||||
|
||||
const minhasSolicitacoesQuery = $derived(
|
||||
funcionarioQuery.data
|
||||
? useQuery(api.ferias.listarMinhasSolicitacoes, {
|
||||
funcionarioId: funcionarioQuery.data._id,
|
||||
})
|
||||
: { data: [] }
|
||||
);
|
||||
|
||||
const solicitacoesSubordinadosQuery = $derived(
|
||||
authStore.usuario?._id
|
||||
? useQuery(api.ferias.listarSolicitacoesSubordinados, {
|
||||
@@ -70,6 +65,14 @@
|
||||
: { data: [] }
|
||||
);
|
||||
|
||||
const minhasSolicitacoesQuery = $derived(
|
||||
funcionarioQuery.data
|
||||
? useQuery(api.ferias.listarMinhasSolicitacoes, {
|
||||
funcionarioId: funcionarioQuery.data._id,
|
||||
})
|
||||
: { data: [] }
|
||||
);
|
||||
|
||||
const meuTimeQuery = $derived(
|
||||
funcionarioQuery.data
|
||||
? useQuery(api.times.obterTimeFuncionario, {
|
||||
@@ -87,18 +90,34 @@
|
||||
);
|
||||
|
||||
const funcionario = $derived(funcionarioQuery.data);
|
||||
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
|
||||
const solicitacoesSubordinados = $derived(
|
||||
solicitacoesSubordinadosQuery?.data || []
|
||||
);
|
||||
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
|
||||
const meuTime = $derived(meuTimeQuery?.data);
|
||||
const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []);
|
||||
|
||||
// Verificar se é gestor
|
||||
const ehGestor = $derived((meusTimesGestor || []).length > 0);
|
||||
|
||||
// Filtrar minhas solicitações
|
||||
const solicitacoesFiltradas = $derived(
|
||||
minhasSolicitacoes.filter((s) => {
|
||||
if (filtroStatusFerias !== "todos" && s.status !== filtroStatusFerias) return false;
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
// Estatísticas das minhas férias
|
||||
const statsMinhasFerias = $derived({
|
||||
total: minhasSolicitacoes.length,
|
||||
aguardando: minhasSolicitacoes.filter((s) => s.status === "aguardando_aprovacao").length,
|
||||
aprovadas: minhasSolicitacoes.filter((s) => s.status === "aprovado" || s.status === "data_ajustada_aprovada").length,
|
||||
reprovadas: minhasSolicitacoes.filter((s) => s.status === "reprovado").length,
|
||||
emFerias: funcionario?.statusFerias === "em_ferias" ? 1 : 0,
|
||||
});
|
||||
|
||||
async function recarregar() {
|
||||
mostrarFormSolicitar = false;
|
||||
solicitacaoSelecionada = null;
|
||||
}
|
||||
|
||||
@@ -1060,72 +1079,147 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if abaAtiva === "minhas-ferias"}
|
||||
<!-- Minhas Férias MODERNO -->
|
||||
<div class="space-y-8">
|
||||
{#if !mostrarFormSolicitar}
|
||||
<!-- Dashboard de Férias -->
|
||||
{#if funcionario}
|
||||
<DashboardFerias funcionarioId={funcionario._id} />
|
||||
|
||||
<!-- Botão para solicitar -->
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-lg gap-3 shadow-2xl hover:shadow-3xl transition-all hover:scale-105"
|
||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none;"
|
||||
onclick={() => (mostrarFormSolicitar = true)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Solicitar Novas Férias
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-warning shadow-xl">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
<!-- Minhas Férias -->
|
||||
<div class="space-y-6">
|
||||
<!-- Estatísticas -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
<div class="stat bg-base-100 shadow-lg rounded-box border border-base-300">
|
||||
<div class="stat-figure text-primary">
|
||||
<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>
|
||||
<h3 class="font-bold">Perfil de funcionário não encontrado</h3>
|
||||
<div class="text-xs">
|
||||
Seu usuário ainda não está associado a um cadastro de
|
||||
funcionário. Entre em contato com o RH.
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Total</div>
|
||||
<div class="stat-value text-primary">{statsMinhasFerias.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">{statsMinhasFerias.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">{statsMinhasFerias.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">{statsMinhasFerias.reprovadas}</div>
|
||||
<div class="stat-desc">Indeferidas</div>
|
||||
</div>
|
||||
|
||||
<div class="stat bg-gradient-to-br from-purple-500/10 to-purple-600/20 shadow-lg rounded-box border-2 border-purple-500/30">
|
||||
<div class="stat-figure text-purple-600">
|
||||
<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="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Em Férias</div>
|
||||
<div class="stat-value text-purple-600">{statsMinhasFerias.emFerias}</div>
|
||||
<div class="stat-desc">Agora</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>
|
||||
{#if funcionario}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary gap-2"
|
||||
onclick={() => (mostrarWizard = true)}
|
||||
>
|
||||
<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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Agendar Férias
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-1 gap-4 mt-4">
|
||||
<div class="form-control">
|
||||
<label class="label" for="status">
|
||||
<span class="label-text">Status</span>
|
||||
</label>
|
||||
<select id="status" class="select select-bordered" bind:value={filtroStatusFerias}>
|
||||
<option value="todos">Todos</option>
|
||||
<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>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- Wizard de Solicitação de Férias -->
|
||||
{#if funcionario}
|
||||
<WizardSolicitacaoFerias
|
||||
funcionarioId={funcionario._id}
|
||||
onSucesso={recarregar}
|
||||
onCancelar={() => (mostrarFormSolicitar = false)}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</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 ({solicitacoesFiltradas.length})
|
||||
</h2>
|
||||
|
||||
{#if solicitacoesFiltradas.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>Ano</th>
|
||||
<th>Períodos</th>
|
||||
<th>Total Dias</th>
|
||||
<th>Status</th>
|
||||
<th>Solicitado em</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each solicitacoesFiltradas as solicitacao}
|
||||
<tr>
|
||||
<td>{solicitacao.anoReferencia}</td>
|
||||
<td>{solicitacao.periodos.length} período(s)</td>
|
||||
<td class="font-bold">{solicitacao.periodos.reduce((acc: number, p: any) => acc + p.diasCorridos, 0)} dias</td>
|
||||
<td>
|
||||
<div class={`badge ${getStatusBadge(solicitacao.status)}`}>
|
||||
{getStatusTexto(solicitacao.status)}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-xs">{new Date(solicitacao._creationTime).toLocaleDateString("pt-BR")}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if abaAtiva === "aprovar-ferias"}
|
||||
<!-- Aprovar Férias (Gestores) PREMIUM -->
|
||||
@@ -1599,3 +1693,26 @@
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Modal Wizard Solicitação de Férias -->
|
||||
{#if mostrarWizard && funcionario}
|
||||
<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 Férias
|
||||
</h3>
|
||||
<div class="max-h-[80vh] overflow-y-auto">
|
||||
<WizardSolicitacaoFerias
|
||||
funcionarioId={funcionario._id}
|
||||
onSucesso={() => {
|
||||
mostrarWizard = false;
|
||||
}}
|
||||
onCancelar={() => (mostrarWizard = false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="modal-backdrop" onclick={() => (mostrarWizard = false)}></div>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user