refactor: enhance ProtectedRoute and dashboard components for improved access control and user experience
- Updated the ProtectedRoute component to optimize access checking logic, preventing unnecessary re-checks and improving authentication flow. - Enhanced the dashboard page to automatically open the login modal for authentication errors and refined loading states for better user feedback. - Improved UI elements across various components for consistency and visual appeal, including updated tab styles and enhanced alert messages. - Removed redundant footer from the vacation management page to streamline the interface.
This commit is contained in:
@@ -21,11 +21,26 @@
|
|||||||
let isChecking = $state(true);
|
let isChecking = $state(true);
|
||||||
let hasAccess = $state(false);
|
let hasAccess = $state(false);
|
||||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let hasCheckedOnce = $state(false);
|
||||||
|
let lastUserState = $state<typeof currentUser | undefined>(undefined);
|
||||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||||
|
|
||||||
// Usar $effect para reagir às mudanças na query
|
// Usar $effect para reagir apenas às mudanças na query currentUser
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
// Não verificar novamente se já tem acesso concedido e usuário está autenticado
|
||||||
|
if (hasAccess && currentUser?.data) {
|
||||||
|
lastUserState = currentUser;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evitar loop: só verificar se currentUser realmente mudou
|
||||||
|
// Comparar dados, não o objeto proxy
|
||||||
|
const currentData = currentUser?.data;
|
||||||
|
const lastData = lastUserState?.data;
|
||||||
|
if (currentData !== lastData || (currentUser === undefined) !== (lastUserState === undefined)) {
|
||||||
|
lastUserState = currentUser;
|
||||||
checkAccess();
|
checkAccess();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkAccess() {
|
function checkAccess() {
|
||||||
@@ -42,6 +57,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marcar que já verificou pelo menos uma vez
|
||||||
|
hasCheckedOnce = true;
|
||||||
|
|
||||||
// Se a query retornou dados, verificar autenticação
|
// Se a query retornou dados, verificar autenticação
|
||||||
if (currentUser?.data) {
|
if (currentUser?.data) {
|
||||||
// Verificar roles
|
// Verificar roles
|
||||||
@@ -67,20 +85,29 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não tem dados e requer autenticação, aguardar um pouco antes de redirecionar
|
// Se não tem dados e requer autenticação
|
||||||
// (pode estar carregando ainda)
|
|
||||||
if (requireAuth && !currentUser?.data) {
|
if (requireAuth && !currentUser?.data) {
|
||||||
|
// Se a query já retornou (não está mais undefined), finalizar estado
|
||||||
|
if (currentUser !== undefined) {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
// Evitar redirecionamento em loop - verificar se já está na URL de erro
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (!urlParams.has('error')) {
|
||||||
|
// Só redirecionar se não estiver em loop
|
||||||
|
if (!hasCheckedOnce || currentUser === null) {
|
||||||
|
window.location.href = `${redirectTo}?error=auth_required&redirect=${encodeURIComponent(currentPath)}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Se já tem erro na URL, permitir renderização para mostrar o alerta
|
||||||
|
isChecking = false;
|
||||||
|
hasAccess = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se ainda está carregando (undefined), aguardar
|
||||||
isChecking = true;
|
isChecking = true;
|
||||||
hasAccess = false;
|
hasAccess = false;
|
||||||
|
|
||||||
// Aguardar 3 segundos antes de redirecionar (dar tempo para a query carregar)
|
|
||||||
timeoutId = setTimeout(() => {
|
|
||||||
// Verificar novamente antes de redirecionar
|
|
||||||
if (!currentUser?.data) {
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
window.location.href = `${redirectTo}?error=auth_required&redirect=${encodeURIComponent(currentPath)}`;
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,13 @@
|
|||||||
import { UserPlus, Mail } from "lucide-svelte";
|
import { UserPlus, Mail } from "lucide-svelte";
|
||||||
import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte";
|
import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte";
|
||||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||||
|
import { loginModalStore } from "$lib/stores/loginModal.svelte";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const isLoading = $derived(auth.isLoading && !data.currentUser);
|
const isLoading = $derived(auth.isLoading && !data?.currentUser);
|
||||||
const isAuthenticated = $derived(auth.isAuthenticated || !!data.currentUser);
|
const isAuthenticated = $derived(auth.isAuthenticated || !!data?.currentUser);
|
||||||
|
|
||||||
$inspect({ isLoading, isAuthenticated });
|
$inspect({ isLoading, isAuthenticated });
|
||||||
|
|
||||||
@@ -56,6 +57,11 @@
|
|||||||
redirectRoute = route;
|
redirectRoute = route;
|
||||||
showAlert = true;
|
showAlert = true;
|
||||||
|
|
||||||
|
// Se for erro de autenticação, abrir modal de login automaticamente
|
||||||
|
if (error === "auth_required") {
|
||||||
|
loginModalStore.open(route || to.url.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
// Limpar URL usando SvelteKit (após router estar inicializado)
|
// Limpar URL usando SvelteKit (após router estar inicializado)
|
||||||
try {
|
try {
|
||||||
replaceState(to.url.pathname, {});
|
replaceState(to.url.pathname, {});
|
||||||
@@ -75,6 +81,17 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true;
|
mounted = true;
|
||||||
|
|
||||||
|
// Verificar se há erro na URL ao carregar a página
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlParams.has("error")) {
|
||||||
|
const error = urlParams.get("error");
|
||||||
|
const route = urlParams.get("route") || urlParams.get("redirect") || "";
|
||||||
|
|
||||||
|
if (error === "auth_required") {
|
||||||
|
loginModalStore.open(route || window.location.pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Atualizar relógio e forçar refresh das queries a cada segundo
|
// Atualizar relógio e forçar refresh das queries a cada segundo
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
currentTime = new Date();
|
currentTime = new Date();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
||||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||||
import type { FunctionReturnType } from 'convex/server';
|
import type { FunctionReturnType } from 'convex/server';
|
||||||
import { X, Calendar } from 'lucide-svelte';
|
import { X, Calendar, Users, Clock, CheckCircle2, Eye, FileCheck, CalendarDays, User, Mail, Shield, Briefcase, Hash, CreditCard, Building2, CheckCircle, ListChecks, Info } from 'lucide-svelte';
|
||||||
import TicketCard from '$lib/components/chamados/TicketCard.svelte';
|
import TicketCard from '$lib/components/chamados/TicketCard.svelte';
|
||||||
import TicketTimeline from '$lib/components/chamados/TicketTimeline.svelte';
|
import TicketTimeline from '$lib/components/chamados/TicketTimeline.svelte';
|
||||||
import { chamadosStore } from '$lib/stores/chamados';
|
import { chamadosStore } from '$lib/stores/chamados';
|
||||||
@@ -680,48 +680,26 @@
|
|||||||
<!-- Tabs PREMIUM -->
|
<!-- Tabs PREMIUM -->
|
||||||
<div
|
<div
|
||||||
role="tablist"
|
role="tablist"
|
||||||
class="tabs tabs-boxed from-base-200 to-base-300 mb-8 bg-linear-to-r p-2 shadow-xl"
|
class="tabs tabs-boxed from-base-200 to-base-300 mb-8 bg-gradient-to-r p-2 shadow-xl rounded-xl border border-base-300"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'meu-perfil' ? 'tab-active scale-105 bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'meu-perfil' ? 'tab-active scale-105 bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'meu-perfil')}
|
onclick={() => (abaAtiva = 'meu-perfil')}
|
||||||
>
|
>
|
||||||
<svg
|
<User class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Meu Perfil
|
Meu Perfil
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'meus-chamados' ? 'tab-active scale-105 bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'meus-chamados' ? 'tab-active scale-105 bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'meus-chamados')}
|
onclick={() => (abaAtiva = 'meus-chamados')}
|
||||||
aria-label="Meus Chamados"
|
aria-label="Meus Chamados"
|
||||||
>
|
>
|
||||||
<svg
|
<FileCheck class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7h18M3 12h12M3 17h18" />
|
|
||||||
</svg>
|
|
||||||
Meus Chamados
|
Meus Chamados
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -729,46 +707,20 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'minhas-ferias' ? 'tab-active scale-105 bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'minhas-ferias' ? 'tab-active scale-105 bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'minhas-ferias')}
|
onclick={() => (abaAtiva = 'minhas-ferias')}
|
||||||
>
|
>
|
||||||
<svg
|
<Calendar class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
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>
|
|
||||||
Minhas Férias
|
Minhas Férias
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'minhas-ausencias' ? 'tab-active scale-105 bg-linear-to-r from-orange-600 to-amber-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'minhas-ausencias' ? 'tab-active scale-105 bg-gradient-to-r from-orange-600 to-amber-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'minhas-ausencias')}
|
onclick={() => (abaAtiva = 'minhas-ausencias')}
|
||||||
>
|
>
|
||||||
<svg
|
<Clock class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
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
|
Minhas Ausências
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -776,26 +728,13 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'aprovar-ferias' ? 'tab-active scale-105 bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'aprovar-ferias' ? 'tab-active scale-105 bg-gradient-to-r from-green-600 to-emerald-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'aprovar-ferias')}
|
onclick={() => (abaAtiva = 'aprovar-ferias')}
|
||||||
>
|
>
|
||||||
<svg
|
<CheckCircle2 class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Aprovar Férias
|
Aprovar Férias
|
||||||
{#if (solicitacoesSubordinados || []).filter((s) => s.status === 'aguardando_aprovacao').length > 0}
|
{#if (solicitacoesSubordinados || []).filter((s) => s.status === 'aguardando_aprovacao').length > 0}
|
||||||
<span class="badge badge-error badge-sm ml-2 animate-pulse">
|
<span class="badge badge-error badge-sm ml-1 animate-pulse">
|
||||||
{(solicitacoesSubordinados || []).filter(
|
{(solicitacoesSubordinados || []).filter(
|
||||||
(s) => s.status === 'aguardando_aprovacao'
|
(s) => s.status === 'aguardando_aprovacao'
|
||||||
).length}
|
).length}
|
||||||
@@ -806,26 +745,13 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === 'aprovar-ausencias' ? 'tab-active scale-105 bg-linear-to-r from-orange-600 to-amber-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
class={`tab tab-lg font-semibold transition-all duration-300 gap-2 ${abaAtiva === 'aprovar-ausencias' ? 'tab-active scale-105 bg-gradient-to-r from-orange-600 to-amber-600 text-white shadow-lg' : 'hover:bg-base-100'}`}
|
||||||
onclick={() => (abaAtiva = 'aprovar-ausencias')}
|
onclick={() => (abaAtiva = 'aprovar-ausencias')}
|
||||||
>
|
>
|
||||||
<svg
|
<Clock class="h-5 w-5" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="mr-2 h-5 w-5"
|
|
||||||
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
|
Aprovar Ausências
|
||||||
{#if (ausenciasSubordinados || []).filter((a) => a.status === 'aguardando_aprovacao').length > 0}
|
{#if (ausenciasSubordinados || []).filter((a) => a.status === 'aguardando_aprovacao').length > 0}
|
||||||
<span class="badge badge-error badge-sm ml-2 animate-pulse">
|
<span class="badge badge-error badge-sm ml-1 animate-pulse">
|
||||||
{(ausenciasSubordinados || []).filter((a) => a.status === 'aguardando_aprovacao')
|
{(ausenciasSubordinados || []).filter((a) => a.status === 'aguardando_aprovacao')
|
||||||
.length}
|
.length}
|
||||||
</span>
|
</span>
|
||||||
@@ -966,107 +892,68 @@
|
|||||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
<!-- Informações Pessoais PREMIUM -->
|
<!-- Informações Pessoais PREMIUM -->
|
||||||
<div
|
<div
|
||||||
class="card bg-base-100 hover:shadow-3xl border-t-4 border-purple-500 shadow-2xl transition-shadow"
|
class="card bg-gradient-to-br from-base-100 to-base-200 hover:shadow-3xl border-t-4 border-purple-500 shadow-2xl transition-shadow overflow-hidden"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body p-6">
|
||||||
<h2 class="card-title mb-6 flex items-center gap-2 text-2xl">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<svg
|
<div class="flex items-center gap-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-purple-600 shadow-lg ring-2 ring-purple-500/20">
|
||||||
class="h-7 w-7 text-purple-600"
|
<User class="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||||
fill="none"
|
</div>
|
||||||
viewBox="0 0 24 24"
|
<div>
|
||||||
stroke="currentColor"
|
<h2 class="text-2xl font-bold text-base-content flex items-center gap-2">
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Informações Pessoais
|
Informações Pessoais
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<p class="text-sm text-base-content/60 mt-0.5">
|
||||||
|
Seus dados pessoais e de acesso
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500/20 to-purple-600/20">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<User class="text-purple-600 h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold"
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide"
|
||||||
>Nome Completo</span
|
>Nome Completo</span
|
||||||
>
|
>
|
||||||
<p class="text-base-content text-lg font-semibold">
|
<p class="text-base-content text-base font-semibold mt-1">
|
||||||
{currentUser.data?.nome}
|
{currentUser.data?.nome}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500/20 to-blue-600/20">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<Mail class="text-blue-600 h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold"
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide"
|
||||||
>E-mail Institucional</span
|
>E-mail Institucional</span
|
||||||
>
|
>
|
||||||
<p class="text-base-content text-lg font-semibold break-all">
|
<p class="text-base-content text-base font-semibold break-all mt-1">
|
||||||
{currentUser.data?.email}
|
{currentUser.data?.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-primary/20 to-primary/30">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<Shield class="text-primary h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold"
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide"
|
||||||
>Perfil de Acesso</span
|
>Perfil de Acesso</span
|
||||||
>
|
>
|
||||||
<div class="badge badge-primary badge-lg mt-1 font-bold">
|
<div class="badge badge-primary badge-lg mt-2 font-bold shadow-sm">
|
||||||
{currentUser.data?.role?.nome || 'Usuário'}
|
{currentUser.data?.role?.nome || 'Usuário'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1078,149 +965,97 @@
|
|||||||
<!-- Dados Funcionais PREMIUM -->
|
<!-- Dados Funcionais PREMIUM -->
|
||||||
{#if funcionario}
|
{#if funcionario}
|
||||||
<div
|
<div
|
||||||
class="card bg-base-100 hover:shadow-3xl border-t-4 border-blue-500 shadow-2xl transition-shadow"
|
class="card bg-gradient-to-br from-base-100 to-base-200 hover:shadow-3xl border-t-4 border-blue-500 shadow-2xl transition-shadow overflow-hidden"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body p-6">
|
||||||
<h2 class="card-title mb-6 flex items-center gap-2 text-2xl">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<svg
|
<div class="flex items-center gap-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 shadow-lg ring-2 ring-blue-500/20">
|
||||||
class="h-7 w-7 text-blue-600"
|
<Briefcase class="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||||
fill="none"
|
</div>
|
||||||
viewBox="0 0 24 24"
|
<div>
|
||||||
stroke="currentColor"
|
<h2 class="text-2xl font-bold text-base-content flex items-center gap-2">
|
||||||
>
|
|
||||||
<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>
|
|
||||||
Dados Funcionais
|
Dados Funcionais
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<p class="text-sm text-base-content/60 mt-0.5">
|
||||||
|
Informações profissionais e organizacionais
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500/20 to-blue-600/20">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<Hash class="text-blue-600 h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold"
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide"
|
||||||
>Matrícula</span
|
>Matrícula</span
|
||||||
>
|
>
|
||||||
<p class="text-base-content text-lg font-semibold">
|
<p class="text-base-content text-base font-semibold mt-1">
|
||||||
{funcionario.matricula || 'Não informada'}
|
{funcionario.matricula || 'Não informada'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-green-500/20 to-green-600/20">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<CreditCard class="text-green-600 h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold">CPF</span>
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide">CPF</span>
|
||||||
<p class="text-base-content text-lg font-semibold">
|
<p class="text-base-content text-base font-semibold mt-1">
|
||||||
{funcionario.cpf}
|
{funcionario.cpf}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500/20 to-orange-600/20">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<Building2 class="text-orange-600 h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
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 class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold">Time</span>
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide">Time</span>
|
||||||
{#if meuTime}
|
{#if meuTime}
|
||||||
<div class="mt-1 flex items-center gap-2">
|
<div class="mt-2">
|
||||||
<div
|
<div
|
||||||
class="badge badge-lg font-semibold"
|
class="badge badge-lg font-semibold shadow-sm"
|
||||||
style="background-color: {meuTime.cor}20; border-color: {meuTime.cor}; color: {meuTime.cor}"
|
style="background-color: {meuTime.cor}20; border-color: {meuTime.cor}; color: {meuTime.cor}"
|
||||||
>
|
>
|
||||||
{meuTime.nome}
|
{meuTime.nome}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="text-base-content/60 mt-2 text-xs">
|
||||||
<p class="text-base-content/60 mt-1 text-xs">
|
|
||||||
Gestor: <span class="font-semibold">{meuTime.gestor?.nome}</span>
|
Gestor: <span class="font-semibold">{meuTime.gestor?.nome}</span>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-base-content/50 mt-1 text-sm">Não atribuído a um time</p>
|
<p class="text-base-content/50 mt-1 text-sm">Não atribuído a um time</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hover:bg-base-200 flex items-start gap-3 rounded-lg p-3 transition-colors"
|
class="hover:bg-base-200/60 flex items-start gap-3 rounded-lg border border-base-300/50 p-4 transition-all shadow-sm hover:shadow-md"
|
||||||
>
|
>
|
||||||
<svg
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-success/20 to-success/30">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<CheckCircle class="text-success h-5 w-5" strokeWidth={2} />
|
||||||
class="text-primary mt-1 h-6 w-6"
|
</div>
|
||||||
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 class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="label-text text-base-content/70 text-sm font-bold"
|
<span class="label-text text-base-content/60 text-xs font-semibold uppercase tracking-wide"
|
||||||
>Status Atual</span
|
>Status Atual</span
|
||||||
>
|
>
|
||||||
{#if funcionario.statusFerias === 'em_ferias'}
|
{#if funcionario.statusFerias === 'em_ferias'}
|
||||||
<div class="badge badge-warning badge-lg mt-1 font-bold">
|
<div class="badge badge-warning badge-lg mt-2 font-bold shadow-sm">
|
||||||
🏖️ Em Férias
|
🏖️ Em Férias
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="badge badge-success badge-lg mt-1 font-bold">✅ Ativo</div>
|
<div class="badge badge-success badge-lg mt-2 font-bold shadow-sm">✅ Ativo</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1751,57 +1586,68 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lista de Solicitações -->
|
<!-- Lista de Solicitações -->
|
||||||
<div class="card bg-base-100 shadow-lg">
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 border-t-4 border-primary shadow-2xl overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="card-body p-6">
|
||||||
<h2 class="card-title mb-4 text-lg">
|
<div class="flex items-center justify-between mb-6">
|
||||||
Minhas Solicitações ({solicitacoesFiltradas.length})
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-primary/80 shadow-lg ring-2 ring-primary/20">
|
||||||
|
<ListChecks class="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-bold text-base-content flex items-center gap-2">
|
||||||
|
Minhas Solicitações
|
||||||
</h2>
|
</h2>
|
||||||
|
<p class="text-sm text-base-content/60 mt-0.5">
|
||||||
|
Histórico de solicitações de férias
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge badge-lg badge-primary gap-2 px-4 py-3 shadow-md">
|
||||||
|
<ListChecks class="h-4 w-4" strokeWidth={2} />
|
||||||
|
{solicitacoesFiltradas.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if solicitacoesFiltradas.length === 0}
|
{#if solicitacoesFiltradas.length === 0}
|
||||||
<div class="alert">
|
<div class="alert alert-info shadow-lg border border-info/20">
|
||||||
<svg
|
<Info class="h-6 w-6 shrink-0 stroke-current" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<span class="font-semibold">Nenhuma solicitação encontrada com os filtros aplicados.</span>
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="stroke-info h-6 w-6 shrink-0"
|
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto rounded-lg border border-base-300 shadow-inner">
|
||||||
<table class="table-zebra table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-gradient-to-r from-base-200 to-base-300">
|
||||||
<th>Ano</th>
|
<th class="font-bold text-base-content">Ano</th>
|
||||||
<th>Período</th>
|
<th class="font-bold text-base-content">Período</th>
|
||||||
<th>Dias</th>
|
<th class="font-bold text-base-content">Dias</th>
|
||||||
<th>Status</th>
|
<th class="font-bold text-base-content">Status</th>
|
||||||
<th>Solicitado em</th>
|
<th class="font-bold text-base-content">Solicitado em</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each solicitacoesFiltradas as periodo (periodo._id)}
|
{#each solicitacoesFiltradas as periodo (periodo._id)}
|
||||||
<tr>
|
<tr class="hover:bg-base-200/50 transition-all duration-200 border-b border-base-300">
|
||||||
<td>{periodo.anoReferencia}</td>
|
<td class="font-semibold text-base-content/80">{periodo.anoReferencia}</td>
|
||||||
<td>
|
<td class="font-medium text-base-content/70">
|
||||||
{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
<div class="flex items-center gap-1.5">
|
||||||
|
<CalendarDays class="h-3.5 w-3.5 text-base-content/50" strokeWidth={2} />
|
||||||
|
<span>{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
||||||
periodo.dataFim
|
periodo.dataFim
|
||||||
)}
|
)}</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="font-bold">{periodo.diasFerias} dias</td>
|
|
||||||
<td>
|
<td>
|
||||||
<div class={`badge ${getStatusBadge(periodo.status)}`}>
|
<div class="badge badge-primary badge-lg font-bold shadow-sm">
|
||||||
|
{periodo.diasFerias} dias
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class={`badge badge-sm font-semibold shadow-sm ${getStatusBadge(periodo.status)}`}>
|
||||||
{getStatusTexto(periodo.status)}
|
{getStatusTexto(periodo.status)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-xs"
|
<td class="text-xs text-base-content/60 font-medium"
|
||||||
>{new Date(periodo._creationTime).toLocaleDateString('pt-BR')}</td
|
>{new Date(periodo._creationTime).toLocaleDateString('pt-BR')}</td
|
||||||
>
|
>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -2104,72 +1950,59 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if abaAtiva === 'aprovar-ferias'}
|
{:else if abaAtiva === 'aprovar-ferias'}
|
||||||
<!-- Aprovar Férias (Gestores) PREMIUM -->
|
<!-- Aprovar Férias (Gestores) PREMIUM -->
|
||||||
<div class="card bg-base-100 border-t-4 border-green-500 shadow-2xl">
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 border-t-4 border-green-500 shadow-2xl overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="card-body p-6">
|
||||||
<h2 class="card-title mb-6 flex items-center gap-2 text-2xl">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<svg
|
<div class="flex items-center gap-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-green-500 to-green-600 shadow-lg ring-2 ring-green-500/20">
|
||||||
class="h-7 w-7 text-green-600"
|
<Users class="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||||
fill="none"
|
</div>
|
||||||
viewBox="0 0 24 24"
|
<div>
|
||||||
stroke="currentColor"
|
<h2 class="text-2xl font-bold text-base-content flex items-center gap-2">
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Solicitações da Equipe
|
Solicitações da Equipe
|
||||||
<div class="badge badge-lg badge-primary ml-2">
|
</h2>
|
||||||
|
<p class="text-sm text-base-content/60 mt-0.5">
|
||||||
|
Gerencie as solicitações de férias da sua equipe
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge badge-lg badge-primary gap-2 px-4 py-3 shadow-md">
|
||||||
|
<FileCheck class="h-4 w-4" strokeWidth={2} />
|
||||||
{solicitacoesSubordinados.length}
|
{solicitacoesSubordinados.length}
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
|
|
||||||
{#if solicitacoesSubordinados.length === 0}
|
{#if solicitacoesSubordinados.length === 0}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success shadow-lg border border-success/20">
|
||||||
<svg
|
<CheckCircle2 class="h-6 w-6 shrink-0 stroke-current" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="h-6 w-6 shrink-0 stroke-current"
|
|
||||||
>
|
|
||||||
<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>
|
<span class="font-semibold">Nenhuma solicitação pendente no momento.</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto rounded-lg border border-base-300 shadow-inner">
|
||||||
<table class="table-zebra table-lg table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-base-200">
|
<tr class="bg-gradient-to-r from-base-200 to-base-300">
|
||||||
<th class="font-bold">Funcionário</th>
|
<th class="font-bold text-base-content">Funcionário</th>
|
||||||
<th class="font-bold">Time</th>
|
<th class="font-bold text-base-content">Time</th>
|
||||||
<th class="font-bold">Ano</th>
|
<th class="font-bold text-base-content">Ano</th>
|
||||||
<th class="font-bold">Período</th>
|
<th class="font-bold text-base-content">Período</th>
|
||||||
<th class="font-bold">Dias</th>
|
<th class="font-bold text-base-content">Dias</th>
|
||||||
<th class="font-bold">Status</th>
|
<th class="font-bold text-base-content">Status</th>
|
||||||
<th class="font-bold">Ações</th>
|
<th class="font-bold text-base-content text-center">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each solicitacoesSubordinados as periodo (periodo._id)}
|
{#each solicitacoesSubordinados as periodo (periodo._id)}
|
||||||
<tr class="hover:bg-base-200 transition-colors">
|
<tr class="hover:bg-base-200/50 transition-all duration-200 border-b border-base-300">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-bold">
|
<div class="font-semibold text-base-content">
|
||||||
{periodo.funcionario?.nome}
|
{periodo.funcionario?.nome}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if periodo.time}
|
{#if periodo.time}
|
||||||
<div
|
<div
|
||||||
class="badge badge-lg font-semibold"
|
class="badge badge-sm font-semibold shadow-sm"
|
||||||
style="background-color: {periodo.time.cor}20; border-color: {periodo
|
style="background-color: {periodo.time.cor}20; border-color: {periodo
|
||||||
.time.cor}; color: {periodo.time.cor}"
|
.time.cor}; color: {periodo.time.cor}"
|
||||||
>
|
>
|
||||||
@@ -2177,72 +2010,49 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="font-semibold">{periodo.anoReferencia}</td>
|
<td class="font-semibold text-base-content/80">{periodo.anoReferencia}</td>
|
||||||
<td class="font-semibold">
|
<td class="font-medium text-base-content/70">
|
||||||
{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
<div class="flex items-center gap-1.5">
|
||||||
|
<CalendarDays class="h-3.5 w-3.5 text-base-content/50" strokeWidth={2} />
|
||||||
|
<span>{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
||||||
periodo.dataFim
|
periodo.dataFim
|
||||||
)}
|
)}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="badge badge-primary badge-lg font-bold shadow-sm">
|
||||||
|
{periodo.diasFerias} dias
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-lg font-bold">{periodo.diasFerias}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div
|
||||||
class={`badge badge-lg font-semibold ${getStatusBadge(periodo.status)}`}
|
class={`badge badge-sm font-semibold shadow-sm ${getStatusBadge(periodo.status)}`}
|
||||||
>
|
>
|
||||||
{getStatusTexto(periodo.status)}
|
{getStatusTexto(periodo.status)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="flex justify-center">
|
||||||
{#if periodo.status === 'aguardando_aprovacao'}
|
{#if periodo.status === 'aguardando_aprovacao'}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm gap-2 shadow-lg transition-transform hover:scale-105"
|
class="btn btn-primary btn-sm gap-2 shadow-md transition-all duration-200 hover:scale-105 hover:shadow-lg"
|
||||||
onclick={() => selecionarPeriodo(periodo._id)}
|
onclick={() => selecionarPeriodo(periodo._id)}
|
||||||
>
|
>
|
||||||
<svg
|
<FileCheck class="h-4 w-4" strokeWidth={2} />
|
||||||
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
|
Analisar
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-ghost btn-sm gap-2 hover:bg-base-300 transition-all duration-200"
|
||||||
onclick={() => selecionarPeriodo(periodo._id)}
|
onclick={() => selecionarPeriodo(periodo._id)}
|
||||||
>
|
>
|
||||||
<svg
|
<Eye class="h-4 w-4" strokeWidth={2} />
|
||||||
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
|
Detalhes
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -2254,71 +2064,58 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if abaAtiva === 'aprovar-ausencias'}
|
{:else if abaAtiva === 'aprovar-ausencias'}
|
||||||
<!-- Aprovar Ausências (Gestores) -->
|
<!-- Aprovar Ausências (Gestores) -->
|
||||||
<div class="card bg-base-100 border-t-4 border-orange-500 shadow-2xl">
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 border-t-4 border-orange-500 shadow-2xl overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="card-body p-6">
|
||||||
<h2 class="card-title mb-6 flex items-center gap-2 text-2xl">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<svg
|
<div class="flex items-center gap-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-orange-600 shadow-lg ring-2 ring-orange-500/20">
|
||||||
class="h-7 w-7 text-orange-600"
|
<Clock class="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||||
fill="none"
|
</div>
|
||||||
viewBox="0 0 24 24"
|
<div>
|
||||||
stroke="currentColor"
|
<h2 class="text-2xl font-bold text-base-content flex items-center gap-2">
|
||||||
>
|
|
||||||
<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
|
Solicitações de Ausências da Equipe
|
||||||
<div class="badge badge-lg badge-warning ml-2">
|
</h2>
|
||||||
|
<p class="text-sm text-base-content/60 mt-0.5">
|
||||||
|
Gerencie as solicitações de ausências da sua equipe
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge badge-lg badge-warning gap-2 px-4 py-3 shadow-md">
|
||||||
|
<FileCheck class="h-4 w-4" strokeWidth={2} />
|
||||||
{ausenciasSubordinados.length}
|
{ausenciasSubordinados.length}
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
|
|
||||||
{#if ausenciasSubordinados.length === 0}
|
{#if ausenciasSubordinados.length === 0}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success shadow-lg border border-success/20">
|
||||||
<svg
|
<CheckCircle2 class="h-6 w-6 shrink-0 stroke-current" strokeWidth={2} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="h-6 w-6 shrink-0 stroke-current"
|
|
||||||
>
|
|
||||||
<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>
|
<span class="font-semibold">Nenhuma solicitação pendente no momento.</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto rounded-lg border border-base-300 shadow-inner">
|
||||||
<table class="table-zebra table-lg table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-base-200">
|
<tr class="bg-gradient-to-r from-base-200 to-base-300">
|
||||||
<th class="font-bold">Funcionário</th>
|
<th class="font-bold text-base-content">Funcionário</th>
|
||||||
<th class="font-bold">Time</th>
|
<th class="font-bold text-base-content">Time</th>
|
||||||
<th class="font-bold">Período</th>
|
<th class="font-bold text-base-content">Período</th>
|
||||||
<th class="font-bold">Dias</th>
|
<th class="font-bold text-base-content">Dias</th>
|
||||||
<th class="font-bold">Status</th>
|
<th class="font-bold text-base-content">Status</th>
|
||||||
<th class="font-bold">Ações</th>
|
<th class="font-bold text-base-content text-center">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each ausenciasSubordinados as ausencia (ausencia._id)}
|
{#each ausenciasSubordinados as ausencia (ausencia._id)}
|
||||||
<tr class="hover:bg-base-200 transition-colors">
|
<tr class="hover:bg-base-200/50 transition-all duration-200 border-b border-base-300">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-bold">
|
<div class="font-semibold text-base-content">
|
||||||
{ausencia.funcionario?.nome || 'N/A'}
|
{ausencia.funcionario?.nome || 'N/A'}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if ausencia.time}
|
{#if ausencia.time}
|
||||||
<div
|
<div
|
||||||
class="badge badge-lg font-semibold"
|
class="badge badge-sm font-semibold shadow-sm"
|
||||||
style="background-color: {ausencia.time
|
style="background-color: {ausencia.time
|
||||||
.cor}20; border-color: {ausencia.time.cor}; color: {ausencia.time
|
.cor}20; border-color: {ausencia.time.cor}; color: {ausencia.time
|
||||||
.cor}"
|
.cor}"
|
||||||
@@ -2327,76 +2124,53 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="font-semibold">
|
<td class="font-medium text-base-content/70">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<CalendarDays class="h-3.5 w-3.5 text-base-content/50" strokeWidth={2} />
|
||||||
|
<span>
|
||||||
{new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até
|
{new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até
|
||||||
{new Date(ausencia.dataFim).toLocaleDateString('pt-BR')}
|
{new Date(ausencia.dataFim).toLocaleDateString('pt-BR')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-lg font-bold">
|
<td>
|
||||||
|
<div class="badge badge-warning badge-lg font-bold shadow-sm">
|
||||||
{Math.ceil(
|
{Math.ceil(
|
||||||
(new Date(ausencia.dataFim).getTime() -
|
(new Date(ausencia.dataFim).getTime() -
|
||||||
new Date(ausencia.dataInicio).getTime()) /
|
new Date(ausencia.dataInicio).getTime()) /
|
||||||
(1000 * 60 * 60 * 24)
|
(1000 * 60 * 60 * 24)
|
||||||
) + 1} dias
|
) + 1} dias
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div
|
||||||
class={`badge badge-lg font-semibold ${getStatusBadge(ausencia.status)}`}
|
class={`badge badge-sm font-semibold shadow-sm ${getStatusBadge(ausencia.status)}`}
|
||||||
>
|
>
|
||||||
{getStatusTexto(ausencia.status)}
|
{getStatusTexto(ausencia.status)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="flex justify-center">
|
||||||
{#if ausencia.status === 'aguardando_aprovacao'}
|
{#if ausencia.status === 'aguardando_aprovacao'}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-warning btn-sm gap-2 shadow-lg transition-transform hover:scale-105"
|
class="btn btn-warning btn-sm gap-2 shadow-md transition-all duration-200 hover:scale-105 hover:shadow-lg"
|
||||||
onclick={() => (solicitacaoAusenciaAprovar = ausencia._id)}
|
onclick={() => (solicitacaoAusenciaAprovar = ausencia._id)}
|
||||||
>
|
>
|
||||||
<svg
|
<FileCheck class="h-4 w-4" strokeWidth={2} />
|
||||||
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
|
Analisar
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-ghost btn-sm gap-2 hover:bg-base-300 transition-all duration-200"
|
||||||
onclick={() => (solicitacaoAusenciaAprovar = ausencia._id)}
|
onclick={() => (solicitacaoAusenciaAprovar = ausencia._id)}
|
||||||
>
|
>
|
||||||
<svg
|
<Eye class="h-4 w-4" strokeWidth={2} />
|
||||||
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
|
Detalhes
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1993,12 +1993,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<footer
|
|
||||||
class="border-base-300/60 bg-base-100 text-base-content/70 mt-8 border-t py-6 text-center text-sm"
|
|
||||||
>
|
|
||||||
SGSE - Sistema de Gerenciamento de Secretaria.
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Calendário de Férias */
|
/* Calendário de Férias */
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ export const getStatusSistema = query({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Atividade do banco no último minuto (agregada em buckets)
|
* Atividade do banco no último minuto (agregada em buckets)
|
||||||
* Usa mensagensPorMinuto como proxy de atividade quando disponível.
|
* Usa logsAtividades e systemMetrics para calcular atividade real.
|
||||||
*/
|
*/
|
||||||
export const getAtividadeBancoDados = query({
|
export const getAtividadeBancoDados = query({
|
||||||
args: {},
|
args: {},
|
||||||
@@ -652,6 +652,14 @@ export const getAtividadeBancoDados = query({
|
|||||||
const agora = Date.now();
|
const agora = Date.now();
|
||||||
const haUmMinuto = agora - 60 * 1000;
|
const haUmMinuto = agora - 60 * 1000;
|
||||||
|
|
||||||
|
// Buscar atividades reais do sistema
|
||||||
|
const atividadesRecentes = await ctx.db
|
||||||
|
.query('logsAtividades')
|
||||||
|
.withIndex('by_timestamp', (q) => q.gte('timestamp', haUmMinuto))
|
||||||
|
.order('asc')
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Buscar métricas também (para mensagens se houver)
|
||||||
const metricasRecentes = await ctx.db
|
const metricasRecentes = await ctx.db
|
||||||
.query('systemMetrics')
|
.query('systemMetrics')
|
||||||
.withIndex('by_timestamp', (q) => q.gte('timestamp', haUmMinuto))
|
.withIndex('by_timestamp', (q) => q.gte('timestamp', haUmMinuto))
|
||||||
@@ -666,15 +674,30 @@ export const getAtividadeBancoDados = query({
|
|||||||
for (let i = 0; i < numBuckets; i++) {
|
for (let i = 0; i < numBuckets; i++) {
|
||||||
const inicio = haUmMinuto + i * bucketSizeMs;
|
const inicio = haUmMinuto + i * bucketSizeMs;
|
||||||
const fim = inicio + bucketSizeMs;
|
const fim = inicio + bucketSizeMs;
|
||||||
|
|
||||||
|
// Contar atividades de criação/inserção (entradas)
|
||||||
|
const atividadesBucket = atividadesRecentes.filter(
|
||||||
|
(a) => a.timestamp >= inicio && a.timestamp < fim
|
||||||
|
);
|
||||||
|
const entradasAtividades = atividadesBucket.filter(
|
||||||
|
a => a.acao === 'criar' || a.acao === 'inserir' || a.acao === 'cadastrar'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
// Contar atividades de exclusão/remoção (saídas)
|
||||||
|
const saidasAtividades = atividadesBucket.filter(
|
||||||
|
a => a.acao === 'excluir' || a.acao === 'remover' || a.acao === 'deletar'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
// Usar mensagensPorMinuto como adicional se disponível
|
||||||
const bucketMetricas = metricasRecentes.filter(
|
const bucketMetricas = metricasRecentes.filter(
|
||||||
(m) => m.timestamp >= inicio && m.timestamp < fim
|
(m) => m.timestamp >= inicio && m.timestamp < fim
|
||||||
);
|
);
|
||||||
|
|
||||||
// Usar mensagensPorMinuto como proxy de "entradas"; "saídas" como fração
|
|
||||||
const somaMensagens =
|
const somaMensagens =
|
||||||
bucketMetricas.reduce((acc, m) => acc + (m.mensagensPorMinuto ?? 0), 0) || 0;
|
bucketMetricas.reduce((acc, m) => acc + (m.mensagensPorMinuto ?? 0), 0) || 0;
|
||||||
const entradas = Math.max(0, Math.round(somaMensagens));
|
|
||||||
const saidas = Math.max(0, Math.round(entradas * 0.6));
|
// Combinar atividades reais com métricas de mensagens
|
||||||
|
const entradas = Math.max(0, Math.round(entradasAtividades + somaMensagens * 0.3));
|
||||||
|
const saidas = Math.max(0, Math.round(saidasAtividades + somaMensagens * 0.2));
|
||||||
|
|
||||||
historico.push({ entradas, saidas });
|
historico.push({ entradas, saidas });
|
||||||
}
|
}
|
||||||
@@ -684,7 +707,7 @@ export const getAtividadeBancoDados = query({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Distribuição de operações (estimada a partir das métricas)
|
* Distribuição de operações (calculada a partir de logsAtividades e métricas)
|
||||||
*/
|
*/
|
||||||
export const getDistribuicaoRequisicoes = query({
|
export const getDistribuicaoRequisicoes = query({
|
||||||
args: {},
|
args: {},
|
||||||
@@ -696,21 +719,43 @@ export const getDistribuicaoRequisicoes = query({
|
|||||||
}),
|
}),
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const umaHoraAtras = Date.now() - 60 * 60 * 1000;
|
const umaHoraAtras = Date.now() - 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// Buscar atividades reais do sistema
|
||||||
|
const atividades = await ctx.db
|
||||||
|
.query('logsAtividades')
|
||||||
|
.withIndex('by_timestamp', (q) => q.gte('timestamp', umaHoraAtras))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Buscar métricas também
|
||||||
const metricas = await ctx.db
|
const metricas = await ctx.db
|
||||||
.query('systemMetrics')
|
.query('systemMetrics')
|
||||||
.withIndex('by_timestamp', (q) => q.gte('timestamp', umaHoraAtras))
|
.withIndex('by_timestamp', (q) => q.gte('timestamp', umaHoraAtras))
|
||||||
.order('desc')
|
.order('desc')
|
||||||
.take(100);
|
.take(100);
|
||||||
|
|
||||||
const totalOps = Math.max(
|
// Contar operações de leitura (consultas, visualizações)
|
||||||
|
const leituras = atividades.filter(
|
||||||
|
a => a.acao === 'consultar' || a.acao === 'visualizar' || a.acao === 'listar' || a.acao === 'buscar'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
// Contar operações de escrita (criar, editar, excluir)
|
||||||
|
const escritas = atividades.filter(
|
||||||
|
a => a.acao === 'criar' || a.acao === 'editar' || a.acao === 'excluir' ||
|
||||||
|
a.acao === 'inserir' || a.acao === 'atualizar' || a.acao === 'deletar' ||
|
||||||
|
a.acao === 'cadastrar' || a.acao === 'remover'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
// Adicionar estimativa baseada em mensagens se disponível
|
||||||
|
const totalMensagens = Math.max(
|
||||||
0,
|
0,
|
||||||
Math.round(metricas.reduce((acc, m) => acc + (m.mensagensPorMinuto ?? 0), 0))
|
Math.round(metricas.reduce((acc, m) => acc + (m.mensagensPorMinuto ?? 0), 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
const queries = Math.round(totalOps * 0.7);
|
// Queries são leituras + parte das mensagens (como consultas de chat)
|
||||||
const mutations = Math.max(0, totalOps - queries);
|
const queries = leituras + Math.round(totalMensagens * 0.5);
|
||||||
const leituras = queries;
|
|
||||||
const escritas = mutations;
|
// Mutations são escritas + parte das mensagens (como envio de mensagens)
|
||||||
|
const mutations = escritas + Math.round(totalMensagens * 0.3);
|
||||||
|
|
||||||
return { queries, mutations, leituras, escritas };
|
return { queries, mutations, leituras, escritas };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user