refactor: improve layout and backend monitoring functionality - Streamlined the layout component in Svelte for better readability and consistency. - Enhanced the backend monitoring functions by updating argument structures and improving code clarity. - A #10

Merged
killer-cf merged 15 commits from feat-better-auth into master 2025-11-08 22:27:29 +00:00
38 changed files with 4471 additions and 707 deletions
Showing only changes of commit 427c78ec37 - Show all commits

View File

@@ -40,7 +40,7 @@
"@sgse-app/backend": "*",
"@tanstack/svelte-form": "^1.19.2",
"@types/papaparse": "^5.3.14",
"better-auth": "^1.3.34",
"better-auth": "catalog:",
"convex": "catalog:",
"convex-svelte": "^0.0.11",
"date-fns": "^4.1.0",

View File

@@ -5,3 +5,5 @@ declare global {
}
}
}
export {};

View File

@@ -1,6 +1,6 @@
import type { Handle } from "@sveltejs/kit";
import { getToken } from "@mmailaender/convex-better-auth-svelte/sveltekit";
import { createAuth } from "@sgse-app/backend/convex/auth";
import { getToken } from "@mmailaender/convex-better-auth-svelte/sveltekit";
export const handle: Handle = async ({ event, resolve }) => {
event.locals.token = await getToken(createAuth, event.cookies);

View File

@@ -0,0 +1,9 @@
import { api } from "@sgse-app/backend/convex/_generated/api";
import { createConvexHttpClient } from "@mmailaender/convex-better-auth-svelte/sveltekit";
export const load = async ({ locals }) => {
const client = createConvexHttpClient({ token: locals.token });
const currentUser = await client.query(api.auth.getCurrentUser, {});
return { currentUser };
};

View File

@@ -5,6 +5,15 @@
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { UserPlus, Mail } from "lucide-svelte";
import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte";
let { data } = $props();
const auth = useAuth();
const isLoading = $derived(auth.isLoading && !data.currentUser);
const isAuthenticated = $derived(auth.isAuthenticated || !!data.currentUser);
$inspect({ isLoading, isAuthenticated });
// Queries para dados do dashboard
const statsQuery = useQuery(api.dashboard.getStats, {});
@@ -12,14 +21,22 @@
// Queries para monitoramento em tempo real
const statusSistemaQuery = useQuery(api.monitoramento.getStatusSistema, {});
const atividadeBDQuery = useQuery(api.monitoramento.getAtividadeBancoDados, {});
const distribuicaoQuery = useQuery(api.monitoramento.getDistribuicaoRequisicoes, {});
const atividadeBDQuery = useQuery(
api.monitoramento.getAtividadeBancoDados,
{},
);
const distribuicaoQuery = useQuery(
api.monitoramento.getDistribuicaoRequisicoes,
{},
);
// Estado para animações
let mounted = $state(false);
let currentTime = $state(new Date());
let showAlert = $state(false);
let alertType = $state<"auth_required" | "access_denied" | "invalid_token" | null>(null);
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
@@ -67,25 +84,25 @@
return {
title: "Autenticação Necessária",
message: `Para acessar "${redirectRoute}", você precisa fazer login no sistema.`,
icon: "🔐"
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: "⛔"
icon: "⛔",
};
case "invalid_token":
return {
title: "Sessão Expirada",
message: "Sua sessão expirou. Por favor, faça login novamente.",
icon: "⏰"
icon: "⏰",
};
default:
return {
title: "Aviso",
message: "Ocorreu um erro. Tente novamente.",
icon: "⚠️"
icon: "⚠️",
};
}
}
@@ -114,7 +131,13 @@
<!-- 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="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">
@@ -123,7 +146,11 @@
{#if alertType === "access_denied"}
<div class="mt-3 flex gap-2">
<a href="/solicitar-acesso" class="btn btn-sm btn-primary">
<svelte:component this={UserPlus} class="h-4 w-4" strokeWidth={2} />
<svelte:component
this={UserPlus}
class="h-4 w-4"
strokeWidth={2}
/>
Solicitar Acesso
</a>
<a href="/ti" class="btn btn-sm btn-ghost">
@@ -133,14 +160,22 @@
</div>
{/if}
</div>
<button type="button" class="btn btn-sm btn-circle btn-ghost" onclick={closeAlert}>✕</button>
<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-gradient-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
class="bg-gradient-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()}! 👋
@@ -174,11 +209,15 @@
{: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-gradient-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 bg-gradient-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>
<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>
@@ -186,19 +225,34 @@
{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
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-gradient-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 bg-gradient-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>
<p class="text-sm text-base-content/70 font-semibold">
Solicitações Pendentes
</p>
<h2 class="text-4xl font-bold text-warning mt-2">
{formatNumber(statsQuery.data.solicitacoesPendentes)}
</h2>
@@ -207,8 +261,19 @@
</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
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>
@@ -216,21 +281,37 @@
</div>
<!-- Símbolos Cadastrados -->
<div class="card bg-gradient-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 bg-gradient-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>
<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
{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
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>
@@ -239,21 +320,39 @@
<!-- Atividade 24h -->
{#if activityQuery.data}
<div class="card bg-gradient-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 bg-gradient-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-sm text-base-content/70 font-semibold">
Atividade (24h)
</p>
<h2 class="text-4xl font-bold text-secondary mt-2">
{formatNumber(activityQuery.data.funcionariosCadastrados24h + activityQuery.data.solicitacoesAcesso24h)}
{formatNumber(
activityQuery.data.funcionariosCadastrados24h +
activityQuery.data.solicitacoesAcesso24h,
)}
</h2>
<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
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>
@@ -271,19 +370,37 @@
<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
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>
<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')}
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>
<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>
@@ -291,17 +408,38 @@
<!-- 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-gradient-to-br from-primary/10 to-primary/5 border-2 border-primary/20 shadow-lg">
<div
class="card bg-gradient-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>
<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
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>
@@ -309,17 +447,38 @@
</div>
<!-- Total de Registros -->
<div class="card bg-gradient-to-br from-success/10 to-success/5 border-2 border-success/20 shadow-lg">
<div
class="card bg-gradient-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>
<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
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>
@@ -327,17 +486,36 @@
</div>
<!-- Tempo Médio de Resposta -->
<div class="card bg-gradient-to-br from-info/10 to-info/5 border-2 border-info/20 shadow-lg">
<div
class="card bg-gradient-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/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
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>
@@ -345,24 +523,42 @@
</div>
<!-- Uso de Sistema -->
<div class="card bg-gradient-to-br from-warning/10 to-warning/5 border-2 border-warning/20 shadow-lg">
<div
class="card bg-gradient-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>
<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>
<span class="font-bold text-warning"
>{status.cpuUsada}%</span
>
</div>
<progress class="progress progress-warning w-full" value={status.cpuUsada} max="100"></progress>
<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>
<span class="font-bold text-warning"
>{status.memoriaUsada}%</span
>
</div>
<progress class="progress progress-warning w-full" value={status.memoriaUsada} max="100"></progress>
<progress
class="progress progress-warning w-full"
value={status.memoriaUsada}
max="100"
></progress>
</div>
</div>
</div>
@@ -375,8 +571,12 @@
<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>
<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>
@@ -386,7 +586,9 @@
<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">
<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}
@@ -395,30 +597,43 @@
<!-- 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 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}
{@const maxAtividade = Math.max(...atividade.historico.map(p => Math.max(p.entradas, p.saidas)))}
{@const maxAtividade = Math.max(
...atividade.historico.map((p) =>
Math.max(p.entradas, p.saidas),
),
)}
<div class="flex-1 flex items-end gap-0.5 h-full group">
<!-- Entradas (verde) -->
<div
class="flex-1 bg-gradient-to-t from-success to-success/70 rounded-t transition-all duration-300 hover:scale-110"
style="height: {ponto.entradas / Math.max(maxAtividade, 1) * 100}%; min-height: 2px;"
style="height: {(ponto.entradas /
Math.max(maxAtividade, 1)) *
100}%; min-height: 2px;"
title="Entradas: {ponto.entradas}"
></div>
<!-- Saídas (vermelho) -->
<div
class="flex-1 bg-gradient-to-t from-error to-error/70 rounded-t transition-all duration-300 hover:scale-110"
style="height: {ponto.saidas / Math.max(maxAtividade, 1) * 100}%; min-height: 2px;"
style="height: {(ponto.saidas /
Math.max(maxAtividade, 1)) *
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
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>
@@ -428,10 +643,14 @@
</div>
<!-- Linha do eixo X -->
<div class="absolute left-12 right-4 bottom-8 border-t-2 border-base-content/30"></div>
<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">
<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>
@@ -439,13 +658,19 @@
</div>
<!-- Legenda -->
<div class="flex justify-center gap-6 mt-4 pt-4 border-t border-base-300">
<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-gradient-to-t from-success to-success/70 rounded"></div>
<div
class="w-4 h-4 bg-gradient-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-gradient-to-t from-error to-error/70 rounded"></div>
<div
class="w-4 h-4 bg-gradient-to-t from-error to-error/70 rounded"
></div>
<span class="text-sm text-base-content/70">Saídas do BD</span>
</div>
</div>
@@ -456,21 +681,35 @@
<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>
<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>
<span class="font-bold text-primary"
>{distribuicao.queries}</span
>
</div>
<progress class="progress progress-primary w-full" value={distribuicao.queries} max={distribuicao.queries + distribuicao.mutations}></progress>
<progress
class="progress progress-primary w-full"
value={distribuicao.queries}
max={distribuicao.queries + distribuicao.mutations}
></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>
<span class="font-bold text-secondary"
>{distribuicao.mutations}</span
>
</div>
<progress class="progress progress-secondary w-full" value={distribuicao.mutations} max={distribuicao.queries + distribuicao.mutations}></progress>
<progress
class="progress progress-secondary w-full"
value={distribuicao.mutations}
max={distribuicao.queries + distribuicao.mutations}
></progress>
</div>
</div>
</div>
@@ -478,21 +717,35 @@
<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>
<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>
<span class="font-bold text-info"
>{distribuicao.leituras}</span
>
</div>
<progress class="progress progress-info w-full" value={distribuicao.leituras} max={distribuicao.leituras + distribuicao.escritas}></progress>
<progress
class="progress progress-info w-full"
value={distribuicao.leituras}
max={distribuicao.leituras + distribuicao.escritas}
></progress>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span>Escritas</span>
<span class="font-bold text-warning">{distribuicao.escritas}</span>
<span class="font-bold text-warning"
>{distribuicao.escritas}</span
>
</div>
<progress class="progress progress-warning w-full" value={distribuicao.escritas} max={distribuicao.leituras + distribuicao.escritas}></progress>
<progress
class="progress progress-warning w-full"
value={distribuicao.escritas}
max={distribuicao.leituras + distribuicao.escritas}
></progress>
</div>
</div>
</div>
@@ -501,7 +754,6 @@
</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">
@@ -528,18 +780,27 @@
<div class="card-body">
<h3 class="card-title text-lg">Acesso Rápido</h3>
<div class="space-y-2 mt-4">
<a href="/recursos-humanos/funcionarios/cadastro" class="btn btn-sm btn-primary w-full">
<a
href="/recursos-humanos/funcionarios/cadastro"
class="btn btn-sm btn-primary w-full"
>
Novo Funcionário
</a>
<a href="/recursos-humanos/simbolos/cadastro" class="btn btn-sm btn-primary w-full">
<a
href="/recursos-humanos/simbolos/cadastro"
class="btn btn-sm btn-primary w-full"
>
Novo Símbolo
</a>
<a href="/ti/painel-administrativo" class="btn btn-sm btn-primary w-full">
<a
href="/ti/painel-administrativo"
class="btn btn-sm btn-primary w-full"
>
Painel Admin
</a>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
@@ -549,7 +810,8 @@
<strong>Versão:</strong> 1.0.0
</p>
<p class="text-base-content/70">
<strong>Última Atualização:</strong> {new Date().toLocaleDateString("pt-BR")}
<strong>Última Atualização:</strong>
{new Date().toLocaleDateString("pt-BR")}
</p>
<p class="text-base-content/70">
<strong>Suporte:</strong> TI SGSE

View File

@@ -1,118 +1,12 @@
<script lang="ts">
import "../app.css";
import Sidebar from "$lib/components/Sidebar.svelte";
// import { PUBLIC_CONVEX_URL } from "$env/static/public";
// import { setupConvex } from "convex-svelte";
// import { authStore } from "$lib/stores/auth.svelte";
// import { browser } from "$app/environment";
import { createSvelteAuthClient } from "@mmailaender/convex-better-auth-svelte/svelte";
import { authClient } from "$lib/auth";
const { children } = $props();
// Interfaces TypeScript devem estar no nível superior
// interface ConvexHttpClientPrototype {
// _authPatched?: boolean;
// mutation?: (...args: unknown[]) => Promise<unknown>;
// query?: (...args: unknown[]) => Promise<unknown>;
// setAuth?: (token: string) => void;
// }
// interface WindowWithConvexClients extends Window {
// __convexClients?: Array<{ setAuth?: (token: string) => void }>;
// }
// Configurar Convex
createSvelteAuthClient({ authClient });
// setupConvex(PUBLIC_CONVEX_URL);
// CORREÇÃO CRÍTICA: Configurar token no cliente Convex após setup
// O convex-svelte usa WebSocket, então precisamos configurar via setAuth
// if (browser) {
// // Aguardar setupConvex inicializar e então configurar token
// $effect(() => {
// const token = authStore.token;
// if (!token) return;
// // Aguardar um pouco para garantir que setupConvex inicializou
// setTimeout(() => {
// // Tentar acessar o cliente Convex interno do convex-svelte
// // O convex-svelte pode usar uma instância interna, então vamos tentar várias abordagens
// // Abordagem 1: Interceptar WebSocket para adicionar token como query param
// const originalWebSocket = window.WebSocket;
// if (!(window as { _convexWsPatched?: boolean })._convexWsPatched) {
// window.WebSocket = class extends originalWebSocket {
// constructor(url: string | URL, protocols?: string | string[]) {
// const wsUrl = typeof url === 'string' ? url : url.href;
// // Se for conexão Convex e tivermos token, adicionar como query param
// if ((wsUrl.includes(PUBLIC_CONVEX_URL) || wsUrl.includes('convex.cloud')) && token) {
// try {
// const urlObj = new URL(wsUrl);
// if (!urlObj.searchParams.has('authToken')) {
// urlObj.searchParams.set('authToken', token);
// super(urlObj.href, protocols);
// if (import.meta.env.DEV) {
// console.log("✅ [Convex Auth] Token adicionado ao WebSocket:", token.substring(0, 20) + "...");
// }
// return;
// }
// } catch (e) {
// // Se falhar, usar URL original
// }
// }
// super(url, protocols);
// }
// } as typeof WebSocket;
// (window as { _convexWsPatched?: boolean })._convexWsPatched = true;
// console.log("✅ [Convex Auth] Interceptador WebSocket configurado");
// }
// // Abordagem 2: Interceptar fetch para requisições HTTP (fallback)
// const originalFetch = window.fetch;
// if (!(window as { _convexFetchPatched?: boolean })._convexFetchPatched) {
// window.fetch = function(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
// const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
// const currentToken = authStore.token;
// if (currentToken && (url.includes(PUBLIC_CONVEX_URL) || url.includes('convex.cloud'))) {
// const headers = new Headers(init?.headers);
// if (!headers.has('authorization')) {
// headers.set('authorization', `Bearer ${currentToken}`);
// }
// return originalFetch(input, {
// ...init,
// headers: headers,
// });
// }
// return originalFetch(input, init);
// };
// (window as { _convexFetchPatched?: boolean })._convexFetchPatched = true;
// console.log("✅ [Convex Auth] Interceptador Fetch configurado");
// }
// }, 300);
// });
// }
// FASE 4: Integração Better Auth com Convex
// Better Auth agora está configurado e ativo
// Usar $effect para garantir que seja executado apenas no cliente
// if (browser) {
// $effect(() => {
// try {
// createSvelteAuthClient({ authClient });
// } catch (error) {
// console.warn("⚠️ [Better Auth] Erro ao inicializar cliente:", error);
// }
// });
// }
</script>
<div>

View File

@@ -1,8 +1,3 @@
import { createSvelteKitHandler } from "@mmailaender/convex-better-auth-svelte/sveltekit";
import { PUBLIC_CONVEX_URL } from "$env/static/public";
// PUBLIC_CONVEX_SITE_URL é necessário para o Better Auth handler
// Se não estiver definido, usar PUBLIC_CONVEX_URL como fallback
export const { GET, POST } = createSvelteKitHandler({
convexSiteUrl: PUBLIC_CONVEX_URL,
});
export const { GET, POST } = createSvelteKitHandler();

View File

@@ -32,7 +32,7 @@
"@sgse-app/backend": "*",
"@tanstack/svelte-form": "^1.19.2",
"@types/papaparse": "^5.3.14",
"better-auth": "^1.3.34",
"better-auth": "catalog:",
"convex": "catalog:",
"convex-svelte": "^0.0.11",
"date-fns": "^4.1.0",
@@ -67,7 +67,7 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@dicebear/avataaars": "^9.2.4",
"better-auth": "1.3.27",
"better-auth": "catalog:",
"convex": "catalog:",
"nodemailer": "^7.0.10",
},
@@ -85,6 +85,7 @@
},
},
"catalog": {
"better-auth": "1.3.27",
"convex": "^1.28.0",
"typescript": "^5.9.2",
},
@@ -155,8 +156,6 @@
"@better-auth/core": ["@better-auth/core@1.3.27", "", { "dependencies": { "better-call": "1.0.19", "zod": "^4.1.5" } }, "sha512-3Sfdax6MQyronY+znx7bOsfQHI6m1SThvJWb0RDscFEAhfqLy95k1sl+/PgGyg0cwc2cUXoEiAOSqYdFYrg3vA=="],
"@better-auth/telemetry": ["@better-auth/telemetry@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" } }, "sha512-aQZ3wN90YMqV49diWxAMe1k7s2qb55KCsedCZne5PlgCjU4s3YtnqyjC5FEpzw2KY8l8rvR7DMAsDl13NjObKA=="],
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.18", "", {}, "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="],
@@ -841,8 +840,6 @@
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@better-auth/telemetry/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="],
"@convex-dev/better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@sveltejs/kit/@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
@@ -865,8 +862,6 @@
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"web/better-auth": ["better-auth@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/telemetry": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-LWA52SlvnUBJRbN8VLSTLILPomZY3zZAiLxVJCeSQ5uVmaIKkMBhERitkfJcXB9RJcfl4uP+3EqKkb6hX1/uiw=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -921,8 +916,6 @@
"convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
"web/better-auth/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],

View File

@@ -9,7 +9,8 @@
],
"catalog": {
"convex": "^1.28.0",
"typescript": "^5.9.2"
"typescript": "^5.9.2",
"better-auth": "1.3.27"
}
},
"scripts": {

View File

@@ -22,7 +22,7 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@dicebear/avataaars": "^9.2.4",
"better-auth": "1.3.27",
"better-auth": "catalog:",
"convex": "catalog:",
"nodemailer": "^7.0.10"
}