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 hasAccess = $state(false);
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
let hasCheckedOnce = $state(false);
|
||||
let lastUserState = $state<typeof currentUser | undefined>(undefined);
|
||||
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(() => {
|
||||
checkAccess();
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
|
||||
function checkAccess() {
|
||||
@@ -42,6 +57,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Marcar que já verificou pelo menos uma vez
|
||||
hasCheckedOnce = true;
|
||||
|
||||
// Se a query retornou dados, verificar autenticação
|
||||
if (currentUser?.data) {
|
||||
// Verificar roles
|
||||
@@ -67,20 +85,29 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Se não tem dados e requer autenticação, aguardar um pouco antes de redirecionar
|
||||
// (pode estar carregando ainda)
|
||||
// Se não tem dados e requer autenticação
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
import { UserPlus, Mail } from "lucide-svelte";
|
||||
import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte";
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { loginModalStore } from "$lib/stores/loginModal.svelte";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const auth = useAuth();
|
||||
const isLoading = $derived(auth.isLoading && !data.currentUser);
|
||||
const isAuthenticated = $derived(auth.isAuthenticated || !!data.currentUser);
|
||||
const isLoading = $derived(auth.isLoading && !data?.currentUser);
|
||||
const isAuthenticated = $derived(auth.isAuthenticated || !!data?.currentUser);
|
||||
|
||||
$inspect({ isLoading, isAuthenticated });
|
||||
|
||||
@@ -56,6 +57,11 @@
|
||||
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, {});
|
||||
@@ -75,6 +81,17 @@
|
||||
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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1993,12 +1993,6 @@
|
||||
{/if}
|
||||
{/await}
|
||||
{/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>
|
||||
/* Calendário de Férias */
|
||||
|
||||
@@ -636,7 +636,7 @@ export const getStatusSistema = query({
|
||||
|
||||
/**
|
||||
* 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({
|
||||
args: {},
|
||||
@@ -652,6 +652,14 @@ export const getAtividadeBancoDados = query({
|
||||
const agora = Date.now();
|
||||
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
|
||||
.query('systemMetrics')
|
||||
.withIndex('by_timestamp', (q) => q.gte('timestamp', haUmMinuto))
|
||||
@@ -666,15 +674,30 @@ export const getAtividadeBancoDados = query({
|
||||
for (let i = 0; i < numBuckets; i++) {
|
||||
const inicio = haUmMinuto + i * 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(
|
||||
(m) => m.timestamp >= inicio && m.timestamp < fim
|
||||
);
|
||||
|
||||
// Usar mensagensPorMinuto como proxy de "entradas"; "saídas" como fração
|
||||
const somaMensagens =
|
||||
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 });
|
||||
}
|
||||
@@ -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({
|
||||
args: {},
|
||||
@@ -696,21 +719,43 @@ export const getDistribuicaoRequisicoes = query({
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
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
|
||||
.query('systemMetrics')
|
||||
.withIndex('by_timestamp', (q) => q.gte('timestamp', umaHoraAtras))
|
||||
.order('desc')
|
||||
.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,
|
||||
Math.round(metricas.reduce((acc, m) => acc + (m.mensagensPorMinuto ?? 0), 0))
|
||||
);
|
||||
|
||||
const queries = Math.round(totalOps * 0.7);
|
||||
const mutations = Math.max(0, totalOps - queries);
|
||||
const leituras = queries;
|
||||
const escritas = mutations;
|
||||
// Queries são leituras + parte das mensagens (como consultas de chat)
|
||||
const queries = leituras + Math.round(totalMensagens * 0.5);
|
||||
|
||||
// Mutations são escritas + parte das mensagens (como envio de mensagens)
|
||||
const mutations = escritas + Math.round(totalMensagens * 0.3);
|
||||
|
||||
return { queries, mutations, leituras, escritas };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user