From 8167a407e79237ffb8690b3671110887660ea09d Mon Sep 17 00:00:00 2001 From: killer-cf Date: Tue, 4 Nov 2025 15:28:13 -0300 Subject: [PATCH] refactor: update StatsCard component and improve page layouts - Changed the icon prop type in StatsCard from optional string to any for better flexibility with lucide-svelte components. - Enhanced the layout of various pages by improving indentation and formatting for better readability. - Updated the usage of lucide-svelte icons across multiple components, ensuring a consistent and modern UI. - Refactored the handling of derived states and queries for improved performance and clarity in the codebase. --- .../src/lib/components/ti/StatsCard.svelte | 32 +- .../(dashboard)/ti/auditoria/+page.svelte | 451 ++++++++++++---- .../(dashboard)/ti/monitoramento/+page.svelte | 39 +- .../(dashboard)/ti/notificacoes/+page.svelte | 111 ++-- .../ti/painel-administrativo/+page.svelte | 114 ++-- .../routes/(dashboard)/ti/perfis/+page.svelte | 495 ++++++++---------- .../ti/solicitacoes-acesso/+page.svelte | 412 +++++++++------ 7 files changed, 1013 insertions(+), 641 deletions(-) diff --git a/apps/web/src/lib/components/ti/StatsCard.svelte b/apps/web/src/lib/components/ti/StatsCard.svelte index 8e879ae..e0bed01 100644 --- a/apps/web/src/lib/components/ti/StatsCard.svelte +++ b/apps/web/src/lib/components/ti/StatsCard.svelte @@ -2,25 +2,38 @@ interface Props { title: string; value: string | number; - icon?: string; + icon?: any; // Componente do lucide-svelte trend?: { value: number; isPositive: boolean; }; description?: string; - color?: "primary" | "secondary" | "accent" | "success" | "warning" | "error"; + color?: + | "primary" + | "secondary" + | "accent" + | "success" + | "warning" + | "error" + | "info"; } - let { title, value, icon, trend, description, color = "primary" }: Props = $props(); + let { + title, + value, + icon: Icon, + trend, + description, + color = "primary", + }: Props = $props();
- {#if icon} - - {@html icon} - + {#if Icon} + {@const IconComponent = Icon} + {/if}
{title}
@@ -30,10 +43,9 @@ {/if} {#if trend}
- {trend.isPositive ? '↗︎' : '↘︎'} {Math.abs(trend.value)}% + {trend.isPositive ? "↗︎" : "↘︎"} + {Math.abs(trend.value)}%
{/if}
- - diff --git a/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte b/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte index 85ccaa2..3c7fdb3 100644 --- a/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte @@ -4,19 +4,23 @@ let abaAtiva = $state<"atividades" | "logins">("atividades"); let limite = $state(50); - + // Queries com $derived para garantir reatividade - const atividades = $derived(useQuery(api.logsAtividades.listarAtividades, { limite })); - const logins = $derived(useQuery(api.logsLogin.listarTodosLogins, { limite })); + const atividades = $derived( + useQuery(api.logsAtividades.listarAtividades, { limite }), + ); + const logins = $derived( + useQuery(api.logsLogin.listarTodosLogins, { limite }), + ); function formatarData(timestamp: number) { - return new Date(timestamp).toLocaleString('pt-BR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' + return new Date(timestamp).toLocaleString("pt-BR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", }); } @@ -27,7 +31,7 @@ excluir: "badge-error", bloquear: "badge-error", desbloquear: "badge-success", - resetar_senha: "badge-info" + resetar_senha: "badge-info", }; return colors[acao] || "badge-neutral"; } @@ -39,7 +43,7 @@ excluir: "Excluir", bloquear: "Bloquear", desbloquear: "Desbloquear", - resetar_senha: "Resetar Senha" + resetar_senha: "Resetar Senha", }; return labels[acao] || acao; } @@ -47,8 +51,12 @@ // Estatísticas const totalAtividades = $derived(atividades?.data?.length || 0); const totalLogins = $derived(logins?.data?.length || 0); - const loginsSucesso = $derived(logins?.data?.filter(l => l.sucesso).length || 0); - const loginsFalha = $derived(logins?.data?.filter(l => !l.sucesso).length || 0); + const loginsSucesso = $derived( + logins?.data?.filter((l) => l.sucesso).length || 0, + ); + const loginsFalha = $derived( + logins?.data?.filter((l) => !l.sucesso).length || 0, + );
@@ -64,13 +72,26 @@
- - + +

Auditoria e Logs

-

Monitoramento completo de atividades e acessos do sistema

+

+ Monitoramento completo de atividades e acessos do sistema +

@@ -79,8 +100,19 @@
- - + +
Atividades
@@ -90,8 +122,19 @@
- - + +
Logins Totais
@@ -101,44 +144,98 @@
- - + +
Logins Bem-sucedidos
{loginsSucesso}
-
{totalLogins > 0 ? Math.round((loginsSucesso / totalLogins) * 100) : 0}% de sucesso
+
+ {totalLogins > 0 ? Math.round((loginsSucesso / totalLogins) * 100) : 0}% + de sucesso +
- - + +
Logins Falhados
{loginsFalha}
-
{totalLogins > 0 ? Math.round((loginsFalha / totalLogins) * 100) : 0}% de falhas
+
+ {totalLogins > 0 ? Math.round((loginsFalha / totalLogins) * 100) : 0}% + de falhas +
- - @@ -149,27 +246,52 @@
-
- diff --git a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte index 4d620c1..82ab2b4 100644 --- a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte @@ -6,19 +6,45 @@
-
- - +
+ +

Monitoramento SGSE

-

Sistema de monitoramento técnico em tempo real

+

+ Sistema de monitoramento técnico em tempo real +

- - + + Voltar @@ -27,4 +53,3 @@
- diff --git a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte index e2ffd97..7105b46 100644 --- a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte @@ -53,13 +53,14 @@ let emailIdsRastreados = $state>(new Set()); // Query para buscar status dos emails - const emailIdsArray = $derived( - Array.from(emailIdsRastreados).map((id) => id as Id<"notificacoesEmail">), - ); - const emailsStatusQuery = useQuery( - api.email.buscarEmailsPorIds, - emailIdsArray.length > 0 ? { emailIds: emailIdsArray } : undefined, - ); + const emailsStatusQuery = useQuery(api.email.buscarEmailsPorIds, () => { + const ids = Array.from(emailIdsRastreados).map( + (id) => id as Id<"notificacoesEmail">, + ); + return ids.length > 0 + ? { emailIds: ids } + : { emailIds: [] as Id<"notificacoesEmail">[] }; + }); // Queries para agendamentos const agendamentosEmailQuery = useQuery( @@ -727,12 +728,12 @@ "enviando", "Criando/buscando conversa...", ); - const conversaResult = await client.mutation( + const conversaId = await client.mutation( api.chat.criarOuBuscarConversaIndividual, { outroUsuarioId: destinatario._id as Id<"usuarios"> }, ); - if (conversaResult.conversaId) { + if (conversaId) { const mensagem = usarTemplate ? templateSelecionado?.corpo || "" : mensagemPersonalizada; @@ -748,7 +749,7 @@ resultadoChat = await client.mutation( api.chat.agendarMensagem, { - conversaId: conversaResult.conversaId, + conversaId: conversaId, conteudo: mensagem, agendadaPara: agendadaPara, }, @@ -773,7 +774,7 @@ "Enviando mensagem...", ); resultadoChat = await client.mutation(api.chat.enviarMensagem, { - conversaId: conversaResult.conversaId, + conversaId: conversaId, conteudo: mensagem, tipo: "texto", permitirNotificacaoParaSiMesmo: true, @@ -813,7 +814,7 @@ ); if (usarTemplate && templateId) { const template = templateSelecionado; - if (template) { + if (template && authStore.usuario) { resultadoEmail = await client.mutation( api.email.enviarEmailComTemplate, { @@ -822,7 +823,7 @@ templateCodigo: template.codigo, variaveis: { nome: destinatario.nome, - matricula: destinatario.matricula, + matricula: destinatario.matricula || "", }, enviadoPorId: authStore.usuario._id as Id<"usuarios">, agendadaPara: agendadaPara, @@ -868,6 +869,15 @@ ); } } else { + if (!authStore.usuario) { + adicionarLog( + "email", + destinatario.nome, + "erro", + "Usuário não autenticado", + ); + return; + } resultadoEmail = await client.mutation( api.email.enfileirarEmail, { @@ -981,12 +991,12 @@ "enviando", "Processando...", ); - const conversaResult = await client.mutation( + const conversaId = await client.mutation( api.chat.criarOuBuscarConversaIndividual, { outroUsuarioId: destinatario._id as Id<"usuarios"> }, ); - if (conversaResult.conversaId) { + if (conversaId) { // Para templates, usar corpo direto (o backend já faz substituição via email) // Para mensagem personalizada, usar diretamente const mensagem = usarTemplate @@ -995,7 +1005,7 @@ if (agendadaPara) { await client.mutation(api.chat.agendarMensagem, { - conversaId: conversaResult.conversaId, + conversaId: conversaId, conteudo: mensagem, agendadaPara: agendadaPara, }); @@ -1012,7 +1022,7 @@ ); } else { await client.mutation(api.chat.enviarMensagem, { - conversaId: conversaResult.conversaId, + conversaId: conversaId, conteudo: mensagem, tipo: "texto", permitirNotificacaoParaSiMesmo: true, @@ -1063,7 +1073,7 @@ ); if (usarTemplate && templateId) { const template = templateSelecionado; - if (template) { + if (template && authStore.usuario) { const resultadoEmail = await client.mutation( api.email.enviarEmailComTemplate, { @@ -1121,6 +1131,16 @@ falhasEmail++; } } else { + if (!authStore.usuario) { + adicionarLog( + "email", + destinatario.nome, + "erro", + "Usuário não autenticado", + ); + falhasEmail++; + continue; + } const resultadoEmail = await client.mutation( api.email.enfileirarEmail, { @@ -1376,12 +1396,12 @@ {/if} {#if enviarParaTodos} -
@@ -1623,9 +1643,9 @@
-
@@ -2241,6 +2266,16 @@
- + {/if} diff --git a/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte b/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte index 0349c24..aad2e49 100644 --- a/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte @@ -2,40 +2,51 @@ import { useQuery, useConvexClient } from "convex-svelte"; import { api } from "@sgse-app/backend/convex/_generated/api"; import StatsCard from "$lib/components/ti/StatsCard.svelte"; + import { + Users, + CheckCircle, + Ban, + Clock, + BarChart3, + Plus, + FolderTree, + FileText, + Info, + } from "lucide-svelte"; const client = useConvexClient(); const usuariosQuery = useQuery(api.usuarios.listar, {}); - + // Verificar se está carregando const carregando = $derived(usuariosQuery === undefined); - + // Extrair dados dos usuários const usuarios = $derived(usuariosQuery?.data ?? []); - + // Estatísticas derivadas const stats = $derived.by(() => { // Se ainda está carregando, retorna null para mostrar loading if (carregando) return null; - + // Se não há usuários, retorna stats zeradas (mas não null para não mostrar loading) if (!Array.isArray(usuarios) || usuarios.length === 0) { return { total: 0, ativos: 0, bloqueados: 0, - inativos: 0 + inativos: 0, }; } - - const ativos = usuarios.filter(u => u.ativo && !u.bloqueado).length; - const bloqueados = usuarios.filter(u => u.bloqueado === true).length; - const inativos = usuarios.filter(u => !u.ativo).length; - + + const ativos = usuarios.filter((u) => u.ativo && !u.bloqueado).length; + const bloqueados = usuarios.filter((u) => u.bloqueado === true).length; + const inativos = usuarios.filter((u) => !u.ativo).length; + return { total: usuarios.length, ativos, bloqueados, - inativos + inativos, }; }); @@ -45,13 +56,15 @@
- - - +
-

Dashboard Administrativo TI

-

Painel de controle e monitoramento do sistema

+

+ Dashboard Administrativo TI +

+

+ Painel de controle e monitoramento do sistema +

@@ -59,34 +72,38 @@ {#if stats}
- + - - + 0 ? ((stats.ativos / stats.total) * 100).toFixed(1) + "% do total" : "0% do total"}`} + icon={CheckCircle} color="success" /> - - + - - +
@@ -102,23 +119,17 @@

Ações Rápidas

- - - + Criar Usuário - + - - - + Gerenciar Perfis - + - - - + Ver Logs
@@ -127,9 +138,10 @@
- - - - Sistema de Gestão da Secretaria de Esportes - Versão 2.0 com controle avançado de acesso + + Sistema de Gestão da Secretaria de Esportes - Versão 2.0 com controle + avançado de acesso
diff --git a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte index e61b8e5..9e89883 100644 --- a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte @@ -6,6 +6,21 @@ import type { Id } from "@sgse-app/backend/convex/_generated/dataModel"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; + import { + Users, + Shield, + ShieldAlert, + Info, + Building2, + Search, + X, + Eye, + Calendar, + Tag, + Settings, + User, + AlertCircle, + } from "lucide-svelte"; type Role = { _id: Id<"roles">; @@ -38,12 +53,12 @@ // Estatísticas const stats = $derived.by(() => { if (carregando) return null; - + const porNivel = { - 0: roles.filter(r => r.nivel === 0).length, - 1: roles.filter(r => r.nivel === 1).length, - 2: roles.filter(r => r.nivel === 2).length, - 3: roles.filter(r => r.nivel >= 3).length, + 0: roles.filter((r) => r.nivel === 0).length, + 1: roles.filter((r) => r.nivel === 1).length, + 2: roles.filter((r) => r.nivel === 2).length, + 3: roles.filter((r) => r.nivel >= 3).length, }; return { @@ -52,7 +67,7 @@ nivelAlto: porNivel[1], nivelMedio: porNivel[2], nivelBaixo: porNivel[3], - comSetor: roles.filter(r => r.setor).length, + comSetor: roles.filter((r) => r.setor).length, }; }); @@ -65,7 +80,7 @@ resultado = resultado.filter( (r) => r.nome.toLowerCase().includes(buscaLower) || - r.descricao.toLowerCase().includes(buscaLower) + r.descricao.toLowerCase().includes(buscaLower), ); } @@ -137,33 +152,27 @@ filtroNivel = ""; } - const temFiltrosAtivos = $derived(busca.trim() !== "" || filtroSetor !== "" || filtroNivel !== ""); + const temFiltrosAtivos = $derived( + busca.trim() !== "" || filtroSetor !== "" || filtroNivel !== "", + ); - +
- - - +

Gestão de Perfis

-

Visualize e gerencie os perfis de acesso do sistema

+

+ Visualize e gerencie os perfis de acesso do sistema +

@@ -171,38 +180,45 @@ {#if stats}
- + - + - + - + - + 0 + ? ((stats.comSetor / stats.total) * 100).toFixed(0) + "% do total" + : "0%"} + icon={Building2} color="secondary" />
@@ -214,38 +230,16 @@
- - - +

Filtros de Busca

{#if temFiltrosAtivos} - {/if} @@ -265,20 +259,9 @@ placeholder="Buscar por nome ou descrição..." class="input input-bordered w-full pl-10" /> - - - + />
@@ -287,7 +270,11 @@ - {#each setoresDisponiveis as setor} @@ -300,7 +287,11 @@ - @@ -312,7 +303,12 @@
- {rolesFiltradas.length} de {roles.length} perfil(is) + {rolesFiltradas.length} + de + {roles.length} + perfil(is) {#if temFiltrosAtivos} Filtrado {/if} @@ -329,45 +325,28 @@
{:else if roles.length === 0}
- - - +

Nenhum perfil encontrado

-

Não há perfis cadastrados no sistema.

+

+ Não há perfis cadastrados no sistema. +

{:else if rolesFiltradas.length === 0}
-
- - - +
+

Nenhum perfil encontrado

-

Nenhum perfil corresponde aos filtros aplicados.

+

+ Nenhum perfil corresponde aos filtros aplicados. +

{#if temFiltrosAtivos} - {/if} @@ -376,115 +355,73 @@
{:else}
- {#each rolesFiltradas as role} -
abrirDetalhes(role)}> + {#each rolesFiltradas as role (role._id)} +
abrirDetalhes(role)} + onkeydown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + abrirDetalhes(role); + } + }} + >

{role.descricao}

-
{obterTextoNivel(role.nivel)}
+
+ {obterTextoNivel(role.nivel)} +
- - - +
- +
- + Nome técnico: + {role.nome} - - - Nome técnico: - {role.nome}
- + {#if role.setor} -
- - - +
+ Setor: {role.setor}
{/if} - +
- - - + Nível: {role.nivel}
-
-
@@ -501,40 +438,41 @@