- Deleted the solicitacoesAcesso route and its associated components to streamline the dashboard. - Updated dashboard stats to remove references to access requests, ensuring accurate data representation. - Refactored backend queries to eliminate access request data handling, enhancing performance and maintainability. - Adjusted type definitions to reflect the removal of access request functionalities.
851 lines
31 KiB
Svelte
851 lines
31 KiB
Svelte
<script lang="ts">
|
|
import { useQuery } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import { onMount } from "svelte";
|
|
import { goto, replaceState } from "$app/navigation";
|
|
import { afterNavigate } from "$app/navigation";
|
|
import { resolve } from "$app/paths";
|
|
import { UserPlus, Mail } from "lucide-svelte";
|
|
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
|
import { loginModalStore } from "$lib/stores/loginModal.svelte";
|
|
|
|
// Queries para dados do dashboard
|
|
const statsQuery = useQuery(api.dashboard.getStats, {});
|
|
const activityQuery = useQuery(api.dashboard.getRecentActivity, {});
|
|
|
|
// Queries para monitoramento em tempo real
|
|
const statusSistemaQuery = useQuery(api.monitoramento.getStatusSistema, {});
|
|
const atividadeBDQuery = useQuery(
|
|
api.monitoramento.getAtividadeBancoDados,
|
|
{},
|
|
);
|
|
const distribuicaoQuery = useQuery(
|
|
api.monitoramento.getDistribuicaoRequisicoes,
|
|
{},
|
|
);
|
|
|
|
// Estado para animações
|
|
let currentTime = $state(new Date());
|
|
let showAlert = $state(false);
|
|
let alertType = $state<
|
|
"auth_required" | "access_denied" | "invalid_token" | null
|
|
>(null);
|
|
let redirectRoute = $state("");
|
|
|
|
// Forçar atualização das queries de monitoramento a cada 1 segundo
|
|
let refreshKey = $state(0);
|
|
|
|
// Limpar URL após navegação estar completa
|
|
afterNavigate(({ to }) => {
|
|
if (to?.url.searchParams.has("error")) {
|
|
const error = to.url.searchParams.get("error");
|
|
const route = to.url.searchParams.get("route") || to.url.searchParams.get("redirect") || "";
|
|
|
|
if (error) {
|
|
alertType = error as any;
|
|
redirectRoute = route;
|
|
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)
|
|
try {
|
|
replaceState(to.url.pathname, {});
|
|
} catch (e) {
|
|
// Se ainda não estiver pronto, usar goto com replaceState
|
|
goto(to.url.pathname, { replaceState: true, noScroll: true });
|
|
}
|
|
|
|
// Auto-fechar após 10 segundos
|
|
setTimeout(() => {
|
|
showAlert = false;
|
|
}, 10000);
|
|
}
|
|
}
|
|
});
|
|
|
|
onMount(() => {
|
|
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
|
|
const interval = setInterval(() => {
|
|
currentTime = new Date();
|
|
refreshKey = (refreshKey + 1) % 1000; // Incrementar para forçar re-render
|
|
}, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
});
|
|
|
|
function closeAlert() {
|
|
showAlert = false;
|
|
}
|
|
|
|
function getAlertMessage(): { title: string; message: string; icon: string } {
|
|
switch (alertType) {
|
|
case "auth_required":
|
|
return {
|
|
title: "Autenticação Necessária",
|
|
message: `Para acessar "${redirectRoute}", você precisa fazer login no sistema.`,
|
|
icon: "🔐",
|
|
};
|
|
case "access_denied":
|
|
return {
|
|
title: "Acesso Negado",
|
|
message: `Você não tem permissão para acessar "${redirectRoute}". Entre em contato com a equipe de TI para solicitar acesso.`,
|
|
icon: "⛔",
|
|
};
|
|
case "invalid_token":
|
|
return {
|
|
title: "Sessão Expirada",
|
|
message: "Sua sessão expirou. Por favor, faça login novamente.",
|
|
icon: "⏰",
|
|
};
|
|
default:
|
|
return {
|
|
title: "Aviso",
|
|
message: "Ocorreu um erro. Tente novamente.",
|
|
icon: "⚠️",
|
|
};
|
|
}
|
|
}
|
|
|
|
// Função para formatar números
|
|
function formatNumber(num: number): string {
|
|
return new Intl.NumberFormat("pt-BR").format(num);
|
|
}
|
|
|
|
// Função para calcular porcentagem
|
|
function calcPercentage(value: number, total: number): number {
|
|
if (total === 0) return 0;
|
|
return Math.round((value / total) * 100);
|
|
}
|
|
|
|
// Obter saudação baseada na hora
|
|
function getSaudacao(): string {
|
|
const hora = currentTime.getHours();
|
|
if (hora < 12) return "Bom dia";
|
|
if (hora < 18) return "Boa tarde";
|
|
return "Boa noite";
|
|
}
|
|
</script>
|
|
|
|
<ProtectedRoute>
|
|
<main class="container mx-auto px-4 py-4">
|
|
<!-- Alerta de Acesso Negado / Autenticação -->
|
|
{#if showAlert}
|
|
{@const alertData = getAlertMessage()}
|
|
<div
|
|
class="alert {alertType === 'access_denied'
|
|
? 'alert-error'
|
|
: alertType === 'auth_required'
|
|
? 'alert-warning'
|
|
: 'alert-info'} mb-6 shadow-xl animate-pulse"
|
|
>
|
|
<div class="flex items-start gap-4">
|
|
<span class="text-4xl">{alertData.icon}</span>
|
|
<div class="flex-1">
|
|
<h3 class="font-bold text-lg mb-1">{alertData.title}</h3>
|
|
<p class="text-sm">{alertData.message}</p>
|
|
{#if alertType === "access_denied"}
|
|
<div class="mt-3 flex gap-2">
|
|
<a href={resolve("/abrir-chamado")} class="btn btn-sm btn-primary">
|
|
<svelte:component
|
|
this={UserPlus}
|
|
class="h-4 w-4"
|
|
strokeWidth={2}
|
|
/>
|
|
Abrir Chamado
|
|
</a>
|
|
<a href={resolve("/ti")} class="btn btn-sm btn-ghost">
|
|
<svelte:component this={Mail} class="h-4 w-4" strokeWidth={2} />
|
|
Contatar TI
|
|
</a>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-circle btn-ghost"
|
|
onclick={closeAlert}>✕</button
|
|
>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Cabeçalho com Boas-vindas -->
|
|
<div
|
|
class="bg-linear-to-r from-primary/20 to-secondary/20 rounded-2xl p-8 mb-6 shadow-lg"
|
|
>
|
|
<div
|
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"
|
|
>
|
|
<div>
|
|
<h1 class="text-4xl font-bold text-primary mb-2">
|
|
{getSaudacao()}! 👋
|
|
</h1>
|
|
<p class="text-xl text-base-content/80">
|
|
Bem-vindo ao SGSE - Sistema de Gerenciamento de Secretaria
|
|
</p>
|
|
<p class="text-sm text-base-content/60 mt-2">
|
|
{currentTime.toLocaleDateString("pt-BR", {
|
|
weekday: "long",
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
})}
|
|
{" - "}
|
|
{currentTime.toLocaleTimeString("pt-BR")}
|
|
</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<div class="badge badge-primary badge-lg">Sistema Online</div>
|
|
<div class="badge badge-success badge-lg">Atualizado</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cards de Estatísticas Principais -->
|
|
{#if statsQuery.isLoading}
|
|
<div class="flex justify-center items-center py-12">
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
</div>
|
|
{:else if statsQuery.data}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
|
<!-- Total de Funcionários -->
|
|
<div
|
|
class="card bg-linear-to-br from-blue-500/10 to-blue-600/20 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-base-content/70 font-semibold">
|
|
Total de Funcionários
|
|
</p>
|
|
<h2 class="text-4xl font-bold text-primary mt-2">
|
|
{formatNumber(statsQuery.data.totalFuncionarios)}
|
|
</h2>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
{statsQuery.data.funcionariosAtivos} ativos
|
|
</p>
|
|
</div>
|
|
<div
|
|
class="radial-progress text-primary"
|
|
style="--value:{calcPercentage(
|
|
statsQuery.data.funcionariosAtivos,
|
|
statsQuery.data.totalFuncionarios,
|
|
)}; --size:4rem;"
|
|
>
|
|
<span class="text-xs font-bold"
|
|
>{calcPercentage(
|
|
statsQuery.data.funcionariosAtivos,
|
|
statsQuery.data.totalFuncionarios,
|
|
)}%</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Solicitações Pendentes -->
|
|
<div
|
|
class="card bg-linear-to-br from-yellow-500/10 to-yellow-600/20 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-base-content/70 font-semibold">
|
|
Solicitações Pendentes
|
|
</p>
|
|
<h2 class="text-4xl font-bold text-warning mt-2">
|
|
4
|
|
</h2>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
de 5 total
|
|
</p>
|
|
</div>
|
|
<div class="p-4 bg-warning/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-8 w-8 text-warning"
|
|
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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Símbolos Cadastrados -->
|
|
<div
|
|
class="card bg-linear-to-br from-green-500/10 to-green-600/20 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-base-content/70 font-semibold">
|
|
Símbolos Cadastrados
|
|
</p>
|
|
<h2 class="text-4xl font-bold text-success mt-2">
|
|
{formatNumber(statsQuery.data.totalSimbolos)}
|
|
</h2>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
{statsQuery.data.cargoComissionado} CC / {statsQuery.data
|
|
.funcaoGratificada} FG
|
|
</p>
|
|
</div>
|
|
<div class="p-4 bg-success/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-8 w-8 text-success"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Atividade 24h -->
|
|
{#if activityQuery.data}
|
|
<div
|
|
class="card bg-linear-to-br from-purple-500/10 to-purple-600/20 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-base-content/70 font-semibold">
|
|
Atividade (24h)
|
|
</p>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
{activityQuery.data.funcionariosCadastrados24h} cadastros
|
|
</p>
|
|
</div>
|
|
<div class="p-4 bg-secondary/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-8 w-8 text-secondary"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Monitoramento em Tempo Real -->
|
|
{#if statusSistemaQuery.data}
|
|
{@const status = statusSistemaQuery.data}
|
|
{@const atividade = atividadeBDQuery.data || { historico: Array.from({ length: 30 }, () => ({ entradas: 0, saidas: 0 })) }}
|
|
{@const distribuicao = distribuicaoQuery.data || { queries: 0, mutations: 0, leituras: 0, escritas: 0 }}
|
|
{@const maxAtividade = Math.max(
|
|
1,
|
|
...atividade.historico.map((p) =>
|
|
Math.max(p.entradas, p.saidas),
|
|
),
|
|
)}
|
|
|
|
<div class="mb-6">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="p-2 bg-error/10 rounded-lg animate-pulse">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-error"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-base-content">
|
|
Monitoramento em Tempo Real
|
|
</h2>
|
|
<p class="text-sm text-base-content/60">
|
|
Atualizado a cada segundo • {new Date(
|
|
status.ultimaAtualizacao,
|
|
).toLocaleTimeString("pt-BR")}
|
|
</p>
|
|
</div>
|
|
<div class="ml-auto badge badge-error badge-lg gap-2">
|
|
<span
|
|
class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-error opacity-75"
|
|
></span>
|
|
<span class="relative inline-flex rounded-full h-3 w-3 bg-error"
|
|
></span>
|
|
LIVE
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cards de Status do Sistema -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
<!-- Usuários Online -->
|
|
<div
|
|
class="card bg-linear-to-br from-primary/10 to-primary/5 border-2 border-primary/20 shadow-lg"
|
|
>
|
|
<div class="card-body p-4">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p
|
|
class="text-xs text-base-content/70 font-semibold uppercase"
|
|
>
|
|
Usuários Online
|
|
</p>
|
|
<h3 class="text-3xl font-bold text-primary mt-1">
|
|
{status.usuariosOnline}
|
|
</h3>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
sessões ativas
|
|
</p>
|
|
</div>
|
|
<div class="p-3 bg-primary/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary"
|
|
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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Total de Registros -->
|
|
<div
|
|
class="card bg-linear-to-br from-success/10 to-success/5 border-2 border-success/20 shadow-lg"
|
|
>
|
|
<div class="card-body p-4">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p
|
|
class="text-xs text-base-content/70 font-semibold uppercase"
|
|
>
|
|
Total Registros
|
|
</p>
|
|
<h3 class="text-3xl font-bold text-success mt-1">
|
|
{status.totalRegistros.toLocaleString("pt-BR")}
|
|
</h3>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
no banco de dados
|
|
</p>
|
|
</div>
|
|
<div class="p-3 bg-success/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-success"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tempo Médio de Resposta -->
|
|
<div
|
|
class="card bg-linear-to-br from-info/10 to-info/5 border-2 border-info/20 shadow-lg"
|
|
>
|
|
<div class="card-body p-4">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p
|
|
class="text-xs text-base-content/70 font-semibold uppercase"
|
|
>
|
|
Tempo Resposta
|
|
</p>
|
|
<h3 class="text-3xl font-bold text-info mt-1">
|
|
{status.tempoMedioResposta}ms
|
|
</h3>
|
|
<p class="text-xs text-base-content/60 mt-1">média atual</p>
|
|
</div>
|
|
<div class="p-3 bg-info/20 rounded-full">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-info"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Uso de Sistema -->
|
|
<div
|
|
class="card bg-linear-to-br from-warning/10 to-warning/5 border-2 border-warning/20 shadow-lg"
|
|
>
|
|
<div class="card-body p-4">
|
|
<div>
|
|
<p
|
|
class="text-xs text-base-content/70 font-semibold uppercase mb-2"
|
|
>
|
|
Uso do Sistema
|
|
</p>
|
|
<div class="space-y-2">
|
|
<div>
|
|
<div class="flex justify-between text-xs mb-1">
|
|
<span class="text-base-content/70">CPU</span>
|
|
<span class="font-bold text-warning"
|
|
>{status.cpuUsada}%</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-warning w-full"
|
|
value={status.cpuUsada}
|
|
max="100"
|
|
></progress>
|
|
</div>
|
|
<div>
|
|
<div class="flex justify-between text-xs mb-1">
|
|
<span class="text-base-content/70">Memória</span>
|
|
<span class="font-bold text-warning"
|
|
>{status.memoriaUsada}%</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-warning w-full"
|
|
value={status.memoriaUsada}
|
|
max="100"
|
|
></progress>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfico de Atividade do Banco de Dados em Tempo Real -->
|
|
<div class="card bg-base-100 shadow-xl mb-6">
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div>
|
|
<h3 class="text-xl font-bold text-base-content">
|
|
Atividade do Banco de Dados
|
|
</h3>
|
|
<p class="text-sm text-base-content/60">
|
|
Entradas e saídas em tempo real (último minuto)
|
|
</p>
|
|
</div>
|
|
<div class="badge badge-success gap-2">
|
|
<span class="loading loading-spinner loading-xs"></span>
|
|
Atualizando
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relative h-64">
|
|
<!-- Eixo Y -->
|
|
<div
|
|
class="absolute left-0 top-0 bottom-8 w-10 flex flex-col justify-between text-right pr-2"
|
|
>
|
|
{#each [10, 8, 6, 4, 2, 0] as val}
|
|
<span class="text-xs text-base-content/60">{val}</span>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Grid e Barras -->
|
|
<div class="absolute left-12 right-4 top-0 bottom-8">
|
|
<!-- Grid horizontal -->
|
|
{#each Array.from({ length: 6 }) as _, i}
|
|
<div
|
|
class="absolute left-0 right-0 border-t border-base-content/10"
|
|
style="top: {(i / 5) * 100}%;"
|
|
></div>
|
|
{/each}
|
|
|
|
<!-- Barras de atividade -->
|
|
<div class="flex items-end justify-around h-full gap-1">
|
|
{#each atividade.historico as ponto, idx}
|
|
<div class="flex-1 flex items-end gap-0.5 h-full group relative">
|
|
<!-- Entradas (verde) -->
|
|
<div
|
|
class="flex-1 bg-linear-to-t from-success to-success/70 rounded-t transition-all duration-300 hover:scale-110"
|
|
style="height: {(ponto.entradas / maxAtividade) * 100}%; min-height: 2px;"
|
|
title="Entradas: {ponto.entradas}"
|
|
></div>
|
|
<!-- Saídas (vermelho) -->
|
|
<div
|
|
class="flex-1 bg-linear-to-t from-error to-error/70 rounded-t transition-all duration-300 hover:scale-110"
|
|
style="height: {(ponto.saidas / maxAtividade) * 100}%; min-height: 2px;"
|
|
title="Saídas: {ponto.saidas}"
|
|
></div>
|
|
|
|
<!-- Tooltip no hover -->
|
|
<div
|
|
class="absolute bottom-full mb-2 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-base-300 text-base-content px-2 py-1 rounded text-xs whitespace-nowrap shadow-lg z-10"
|
|
>
|
|
<div>↑ {ponto.entradas} entradas</div>
|
|
<div>↓ {ponto.saidas} saídas</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Linha do eixo X -->
|
|
<div
|
|
class="absolute left-12 right-4 bottom-8 border-t-2 border-base-content/30"
|
|
></div>
|
|
|
|
<!-- Labels do eixo X -->
|
|
<div
|
|
class="absolute left-12 right-4 bottom-0 flex justify-between text-xs text-base-content/60"
|
|
>
|
|
<span>-60s</span>
|
|
<span>-30s</span>
|
|
<span>agora</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legenda -->
|
|
<div
|
|
class="flex justify-center gap-6 mt-4 pt-4 border-t border-base-300"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<div
|
|
class="w-4 h-4 bg-linear-to-t from-success to-success/70 rounded"
|
|
></div>
|
|
<span class="text-sm text-base-content/70">Entradas no BD</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div
|
|
class="w-4 h-4 bg-linear-to-t from-error to-error/70 rounded"
|
|
></div>
|
|
<span class="text-sm text-base-content/70">Saídas do BD</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Distribuição de Requisições -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h3 class="text-lg font-bold text-base-content mb-4">
|
|
Tipos de Operações
|
|
</h3>
|
|
<div class="space-y-3">
|
|
<div>
|
|
<div class="flex justify-between text-sm mb-1">
|
|
<span>Queries (Leituras)</span>
|
|
<span class="font-bold text-primary"
|
|
>{distribuicao.queries}</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-primary w-full"
|
|
value={distribuicao.queries}
|
|
max={Math.max(distribuicao.queries + distribuicao.mutations, 1)}
|
|
></progress>
|
|
</div>
|
|
<div>
|
|
<div class="flex justify-between text-sm mb-1">
|
|
<span>Mutations (Escritas)</span>
|
|
<span class="font-bold text-secondary"
|
|
>{distribuicao.mutations}</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-secondary w-full"
|
|
value={distribuicao.mutations}
|
|
max={Math.max(distribuicao.queries + distribuicao.mutations, 1)}
|
|
></progress>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h3 class="text-lg font-bold text-base-content mb-4">
|
|
Operações no Banco
|
|
</h3>
|
|
<div class="space-y-3">
|
|
<div>
|
|
<div class="flex justify-between text-sm mb-1">
|
|
<span>Leituras</span>
|
|
<span class="font-bold text-info"
|
|
>{distribuicao.leituras}</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-info w-full"
|
|
value={distribuicao.leituras}
|
|
max={Math.max(distribuicao.leituras + distribuicao.escritas, 1)}
|
|
></progress>
|
|
</div>
|
|
<div>
|
|
<div class="flex justify-between text-sm mb-1">
|
|
<span>Escritas</span>
|
|
<span class="font-bold text-warning"
|
|
>{distribuicao.escritas}</span
|
|
>
|
|
</div>
|
|
<progress
|
|
class="progress progress-warning w-full"
|
|
value={distribuicao.escritas}
|
|
max={Math.max(distribuicao.leituras + distribuicao.escritas, 1)}
|
|
></progress>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Cards de Status -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">Status do Sistema</h3>
|
|
<div class="space-y-2 mt-4">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm">Banco de Dados</span>
|
|
<span class="badge badge-success">Online</span>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm">API</span>
|
|
<span class="badge badge-success">Operacional</span>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm">Backup</span>
|
|
<span class="badge badge-success">Atualizado</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">Acesso Rápido</h3>
|
|
<div class="space-y-2 mt-4">
|
|
<a
|
|
href={resolve("/recursos-humanos/funcionarios/cadastro")}
|
|
class="btn btn-sm btn-primary w-full"
|
|
>
|
|
Novo Funcionário
|
|
</a>
|
|
<a
|
|
href={resolve("/recursos-humanos/simbolos/cadastro")}
|
|
class="btn btn-sm btn-primary w-full"
|
|
>
|
|
Novo Símbolo
|
|
</a>
|
|
<a
|
|
href={resolve("/ti/painel-administrativo")}
|
|
class="btn btn-sm btn-primary w-full"
|
|
>
|
|
Painel Admin
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">Informações</h3>
|
|
<div class="space-y-2 mt-4 text-sm">
|
|
<p class="text-base-content/70">
|
|
<strong>Versão:</strong> 1.0.0
|
|
</p>
|
|
<p class="text-base-content/70">
|
|
<strong>Última Atualização:</strong>
|
|
{new Date().toLocaleDateString("pt-BR")}
|
|
</p>
|
|
<p class="text-base-content/70">
|
|
<strong>Suporte:</strong> TI SGSE
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</main>
|
|
</ProtectedRoute>
|
|
|
|
<style>
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.card {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
</style>
|