diff --git a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte
index a7cae71..2bbf51f 100644
--- a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte
@@ -4,6 +4,7 @@
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
import { goto } from "$app/navigation";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
+ import { authStore } from "$lib/stores/auth.svelte";
type RoleRow = {
_id: Id<"roles">;
_creationTime: number;
@@ -32,6 +33,14 @@
// Formato: { "roleId-recurso": true/false }
let recursosExpandidos: Record = $state({});
+ // Gerenciamento de Perfis
+ let modalGerenciarPerfisAberto = $state(false);
+ let perfilSendoEditado = $state(null);
+ let nomeNovoPerfil = $state("");
+ let descricaoNovoPerfil = $state("");
+ let nivelNovoPerfil = $state(3);
+ let processando = $state(false);
+
// Cache de permissões por role
let permissoesPorRole: Record<
string,
@@ -66,13 +75,13 @@
const rolesFiltradas = $derived.by(() => {
if (!rolesQuery.data) return [];
- let rs: Array = rolesQuery.data as Array;
+ let rs = rolesQuery.data; // Removed explicit type annotation
if (filtroRole)
- rs = rs.filter((r: RoleRow) => r._id === (filtroRole as any));
+ rs = rs.filter((r) => r._id === (filtroRole)); // Removed as any
if (busca.trim()) {
const b = busca.toLowerCase();
rs = rs.filter(
- (r: RoleRow) =>
+ (r) =>
r.descricao.toLowerCase().includes(b) ||
r.nome.toLowerCase().includes(b)
);
@@ -120,8 +129,9 @@
];
}
mostrarMensagem("success", "Permissão atualizada com sucesso!");
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao atualizar permissão");
+ } catch (error: unknown) { // Changed to unknown
+ const message = error instanceof Error ? error.message : "Erro ao atualizar permissão";
+ mostrarMensagem("error", message);
} finally {
salvando = false;
}
@@ -132,6 +142,90 @@
const entry = dados?.find((e) => e.recurso === recurso);
return entry ? entry.acoes.includes(acao) : false;
}
+
+ function abrirModalCriarPerfil() {
+ nomeNovoPerfil = "";
+ descricaoNovoPerfil = "";
+ nivelNovoPerfil = 3; // Default to a common level
+ perfilSendoEditado = null;
+ modalGerenciarPerfisAberto = true;
+ }
+
+ function prepararEdicaoPerfil(role: RoleRow) {
+ perfilSendoEditado = role;
+ nomeNovoPerfil = role.nome;
+ descricaoNovoPerfil = role.descricao;
+ nivelNovoPerfil = role.nivel;
+ modalGerenciarPerfisAberto = true;
+ }
+
+ function fecharModalGerenciarPerfis() {
+ modalGerenciarPerfisAberto = false;
+ perfilSendoEditado = null;
+ }
+
+ async function criarNovoPerfil() {
+ if (!nomeNovoPerfil.trim()) return;
+
+ processando = true;
+ try {
+ const result = await client.mutation(api.roles.criar, {
+ nome: nomeNovoPerfil.trim(),
+ descricao: descricaoNovoPerfil.trim(),
+ nivel: nivelNovoPerfil,
+ customizado: true,
+ });
+
+ if (result.sucesso) {
+ mostrarMensagem("success", "Perfil criado com sucesso!");
+ nomeNovoPerfil = "";
+ descricaoNovoPerfil = "";
+ nivelNovoPerfil = 3;
+ fecharModalGerenciarPerfis();
+ if (rolesQuery.refetch) { // Verificação para garantir que refetch existe
+ rolesQuery.refetch(); // Atualiza a lista de perfis
+ }
+ } else {
+ mostrarMensagem("error", `Erro ao criar perfil: ${result.erro}`);
+ }
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : String(error);
+ mostrarMensagem("error", `Erro ao criar perfil: ${message}`);
+ } finally {
+ processando = false;
+ }
+ }
+
+ async function editarPerfil() {
+ if (!perfilSendoEditado || !nomeNovoPerfil.trim()) return;
+
+ processando = true;
+ try {
+ const result = await client.mutation(api.roles.atualizar, {
+ roleId: perfilSendoEditado._id,
+ nome: nomeNovoPerfil.trim(),
+ descricao: descricaoNovoPerfil.trim(),
+ nivel: nivelNovoPerfil,
+ setor: perfilSendoEditado.setor, // Manter setor existente
+ });
+
+ if (result.sucesso) {
+ mostrarMensagem("success", "Perfil atualizado com sucesso!");
+ fecharModalGerenciarPerfis();
+ if (rolesQuery.refetch) { // Verificação para garantir que refetch existe
+ rolesQuery.refetch(); // Atualiza a lista de perfis
+ }
+ } else {
+ mostrarMensagem("error", `Erro ao atualizar perfil: ${result.erro}`);
+ }
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : String(error);
+ mostrarMensagem("error", `Erro ao atualizar perfil: ${message}`);
+ } finally {
+ processando = false;
+ }
+ }
+
@@ -160,7 +254,7 @@
TI
- Gerenciar Permissões
+ Gerenciar Perfis & Permissões
@@ -185,12 +279,32 @@
- Gerenciar Permissões de Acesso
+ Gerenciar Perfis & Permissões de Acesso
Configure as permissões de acesso aos menus do sistema por função
+
+
+
+
{#if roleRow.nivel <= 1}
@@ -574,4 +697,148 @@
{/each}
{/if}
+
+
+ {#if modalGerenciarPerfisAberto}
+
+ {/if}
diff --git a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte
index 505d108..91cf110 100644
--- a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte
@@ -2,238 +2,111 @@
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
- import { authStore } from "$lib/stores/auth.svelte";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
+ import { format } from "date-fns";
+ import { ptBR } from "date-fns/locale";
+
+ type Role = {
+ _id: Id<"roles">;
+ _creationTime: number;
+ nome: string;
+ descricao: string;
+ nivel: number;
+ setor?: string;
+ };
const client = useConvexClient();
-
- // Queries
- const perfisQuery = useQuery(api.perfisCustomizados.listarPerfisCustomizados, {});
const rolesQuery = useQuery(api.roles.listar, {});
+ const roles = $derived(rolesQuery?.data ?? []);
+ const carregando = $derived(rolesQuery === undefined);
- // Estados
- let modo = $state<"listar" | "criar" | "editar" | "detalhes">("listar");
- let perfilSelecionado = $state(null);
- let processando = $state(false);
- let mensagem = $state<{ tipo: "success" | "error" | "warning"; texto: string } | null>(null);
- let modalExcluir = $state(false);
- let perfilParaExcluir = $state(null);
+ let busca = $state("");
+ let filtroSetor = $state("");
+ let roleSelecionada = $state(null);
+ let modalDetalhesAberto = $state(false);
- // Formulário
- let formNome = $state("");
- let formDescricao = $state("");
- let formNivel = $state(3);
- let formClonarDeRoleId = $state("");
-
- // Detalhes do perfil
- let detalhesQuery = $state(null);
+ const setoresDisponiveis = $derived.by(() => {
+ const setores = new Set();
+ roles.forEach((r) => {
+ if (r.setor) setores.add(r.setor);
+ });
+ return Array.from(setores).sort();
+ });
- function mostrarMensagem(tipo: "success" | "error" | "warning", texto: string) {
- mensagem = { tipo, texto };
- setTimeout(() => {
- mensagem = null;
- }, 5000);
+ const rolesFiltradas = $derived.by(() => {
+ let resultado = roles;
+
+ // Filtro por busca (nome ou descrição)
+ if (busca.trim()) {
+ const buscaLower = busca.toLowerCase();
+ resultado = resultado.filter(
+ (r) =>
+ r.nome.toLowerCase().includes(buscaLower) ||
+ r.descricao.toLowerCase().includes(buscaLower)
+ );
+ }
+
+ // Filtro por setor
+ if (filtroSetor) {
+ resultado = resultado.filter((r) => r.setor === filtroSetor);
+ }
+
+ return resultado.sort((a, b) => {
+ // Ordenar por nível primeiro (menor nível = maior privilégio)
+ if (a.nivel !== b.nivel) return a.nivel - b.nivel;
+ // Depois por nome
+ return a.nome.localeCompare(b.nome);
+ });
+ });
+
+ function obterCorNivel(nivel: number): string {
+ if (nivel === 0) return "badge-error";
+ if (nivel === 1) return "badge-warning";
+ if (nivel === 2) return "badge-info";
+ return "badge-ghost";
}
- function abrirCriar() {
- modo = "criar";
- formNome = "";
- formDescricao = "";
- formNivel = 3;
- formClonarDeRoleId = "";
+ function obterTextoNivel(nivel: number): string {
+ if (nivel === 0) return "Máximo";
+ if (nivel === 1) return "Alto";
+ if (nivel === 2) return "Médio";
+ if (nivel === 3) return "Baixo";
+ return `Nível ${nivel}`;
}
- function abrirEditar(perfil: any) {
- modo = "editar";
- perfilSelecionado = perfil;
- formNome = perfil.nome;
- formDescricao = perfil.descricao;
- formNivel = perfil.nivel;
+ function abrirDetalhes(role: Role) {
+ roleSelecionada = role;
+ modalDetalhesAberto = true;
}
- async function abrirDetalhes(perfil: any) {
- modo = "detalhes";
- perfilSelecionado = perfil;
-
- // Buscar detalhes completos
- try {
- const detalhes = await client.query(api.perfisCustomizados.obterPerfilComPermissoes, {
- perfilId: perfil._id,
- });
- detalhesQuery = detalhes;
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao carregar detalhes");
- }
- }
-
- function voltar() {
- modo = "listar";
- perfilSelecionado = null;
- detalhesQuery = null;
- }
-
- async function criarPerfil() {
- if (!formNome.trim() || !formDescricao.trim()) {
- mostrarMensagem("warning", "Preencha todos os campos obrigatórios");
- return;
- }
-
- if (formNivel < 3) {
- mostrarMensagem("warning", "O nível mínimo para perfis customizados é 3");
- return;
- }
-
- if (!authStore.usuario) {
- mostrarMensagem("error", "Usuário não autenticado");
- return;
- }
-
- try {
- processando = true;
-
- const resultado = await client.mutation(api.perfisCustomizados.criarPerfilCustomizado, {
- nome: formNome.trim(),
- descricao: formDescricao.trim(),
- nivel: formNivel,
- clonarDeRoleId: formClonarDeRoleId ? (formClonarDeRoleId as Id<"roles">) : undefined,
- criadoPorId: authStore.usuario._id as Id<"usuarios">,
- });
-
- if (resultado.sucesso) {
- mostrarMensagem("success", "Perfil criado com sucesso!");
- voltar();
- } else {
- mostrarMensagem("error", resultado.erro);
- }
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao criar perfil");
- } finally {
- processando = false;
- }
- }
-
- async function editarPerfil() {
- if (!perfilSelecionado) return;
-
- if (!formNome.trim() || !formDescricao.trim()) {
- mostrarMensagem("warning", "Preencha todos os campos obrigatórios");
- return;
- }
-
- if (!authStore.usuario) {
- mostrarMensagem("error", "Usuário não autenticado");
- return;
- }
-
- try {
- processando = true;
-
- const resultado = await client.mutation(api.perfisCustomizados.editarPerfilCustomizado, {
- perfilId: perfilSelecionado._id,
- nome: formNome.trim(),
- descricao: formDescricao.trim(),
- editadoPorId: authStore.usuario._id as Id<"usuarios">,
- });
-
- if (resultado.sucesso) {
- mostrarMensagem("success", "Perfil atualizado com sucesso!");
- voltar();
- } else {
- mostrarMensagem("error", resultado.erro);
- }
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao editar perfil");
- } finally {
- processando = false;
- }
- }
-
- function abrirModalExcluir(perfil: any) {
- perfilParaExcluir = perfil;
- modalExcluir = true;
- }
-
- function fecharModalExcluir() {
- modalExcluir = false;
- perfilParaExcluir = null;
- }
-
- async function confirmarExclusao() {
- if (!perfilParaExcluir || !authStore.usuario) {
- mostrarMensagem("error", "Erro ao excluir perfil");
- return;
- }
-
- try {
- processando = true;
- modalExcluir = false;
-
- const resultado = await client.mutation(api.perfisCustomizados.excluirPerfilCustomizado, {
- perfilId: perfilParaExcluir._id,
- excluidoPorId: authStore.usuario._id as Id<"usuarios">,
- });
-
- if (resultado.sucesso) {
- mostrarMensagem("success", "Perfil excluído com sucesso!");
- } else {
- mostrarMensagem("error", resultado.erro);
- }
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao excluir perfil");
- } finally {
- processando = false;
- perfilParaExcluir = null;
- }
- }
-
- async function clonarPerfil(perfil: any) {
- const novoNome = prompt(`Digite o nome para o novo perfil (clone de "${perfil.nome}"):`);
- if (!novoNome?.trim()) return;
-
- const novaDescricao = prompt("Digite a descrição para o novo perfil:");
- if (!novaDescricao?.trim()) return;
-
- if (!authStore.usuario) {
- mostrarMensagem("error", "Usuário não autenticado");
- return;
- }
-
- try {
- processando = true;
-
- const resultado = await client.mutation(api.perfisCustomizados.clonarPerfil, {
- perfilOrigemId: perfil._id,
- novoNome: novoNome.trim(),
- novaDescricao: novaDescricao.trim(),
- criadoPorId: authStore.usuario._id as Id<"usuarios">,
- });
-
- if (resultado.sucesso) {
- mostrarMensagem("success", "Perfil clonado com sucesso!");
- } else {
- mostrarMensagem("error", resultado.erro);
- }
- } catch (e: any) {
- mostrarMensagem("error", e.message || "Erro ao clonar perfil");
- } finally {
- processando = false;
- }
+ function fecharDetalhes() {
+ modalDetalhesAberto = false;
+ roleSelecionada = null;
}
function formatarData(timestamp: number): string {
- return new Date(timestamp).toLocaleString("pt-BR");
+ try {
+ return format(new Date(timestamp), "dd/MM/yyyy HH:mm", { locale: ptBR });
+ } catch {
+ return "Data inválida";
+ }
+ }
+
+ function limparFiltros() {
+ busca = "";
+ filtroSetor = "";
}
-
+
-
+
-
+
-
Gerenciar Perfis Customizados
-
- Crie e gerencie perfis de acesso personalizados para os usuários
-
+
Gestão de Perfis
+
Visualize e gerencie os perfis de acesso do sistema
-
-
- {#if modo !== "listar"}
-
- {/if}
- {#if modo === "listar"}
-
-
- Voltar para TI
-
-
- {/if}
-
-
- {#if mensagem}
-
-
-
{mensagem.texto}
-
- {/if}
-
-
- {#if modo === "listar"}
-
+
+ {#if !carregando && roles.length > 0}
+
- {#if !perfisQuery}
-
-
-
- {:else if perfisQuery.data && perfisQuery.data.length === 0}
-
-
📋
-
Nenhum perfil customizado
-
- Crie seu primeiro perfil personalizado clicando no botão acima
-
-
- {:else if perfisQuery.data}
-
-
-
-
- | Nome |
- Descrição |
- Nível |
- Usuários |
- Criado Por |
- Criado Em |
- Ações |
-
-
-
- {#each perfisQuery.data as perfil}
-
- |
- {perfil.nome}
- |
-
-
- {perfil.descricao}
-
- |
-
- {perfil.nivel}
- |
-
-
- {perfil.numeroUsuarios} usuário{perfil.numeroUsuarios !== 1 ? "s" : ""}
-
- |
-
- {perfil.criadorNome}
- |
-
- {formatarData(perfil.criadoEm)}
- |
-
-
-
-
-
-
-
- |
-
- {/each}
-
-
-
- {/if}
-
-
- {/if}
-
-
- {#if modo === "criar"}
-
-
-
Criar Novo Perfil Customizado
-
-
-
-
- {/if}
-
-
- {#if modo === "editar" && perfilSelecionado}
-
-
-
Editar Perfil: {perfilSelecionado.nome}
-
-
-
-
- {/if}
-
-
- {#if modo === "detalhes" && perfilSelecionado}
-
-
-
-
-
{perfilSelecionado.nome}
-
-
-
Descrição
-
{perfilSelecionado.descricao}
-
-
-
Nível de Acesso
-
- {perfilSelecionado.nivel}
-
-
-
-
Criado Por
-
{perfilSelecionado.criadorNome}
-
-
-
Criado Em
-
{formatarData(perfilSelecionado.criadoEm)}
-
-
-
Usuários com este Perfil
-
- {perfilSelecionado.numeroUsuarios} usuário{perfilSelecionado.numeroUsuarios !==
- 1
- ? "s"
- : ""}
-
-
-
-
-
-
-
- {#if !detalhesQuery}
-
- {:else}
-
- {#if detalhesQuery.menuPermissoes && detalhesQuery.menuPermissoes.length > 0}
-
-
-
Permissões de Menu
-
-
-
-
- | Menu |
- Acessar |
- Consultar |
- Gravar |
-
-
-
- {#each detalhesQuery.menuPermissoes as perm}
-
- | {perm.menuPath} |
-
- {#if perm.podeAcessar}
- Sim
- {:else}
- Não
- {/if}
- |
-
- {#if perm.podeConsultar}
- Sim
- {:else}
- Não
- {/if}
- |
-
- {#if perm.podeGravar}
- Sim
- {:else}
- Não
- {/if}
- |
-
- {/each}
-
-
-
-
-
-
- {:else}
-
+
+
Filtros de Busca
+
- {/if}
+ Limpar Filtros
+
+
-
- {#if detalhesQuery.usuarios && detalhesQuery.usuarios.length > 0}
-
-
-
Usuários com este Perfil
-
-
-
-
- | Nome |
- Matrícula |
- Email |
- Status |
-
-
-
- {#each detalhesQuery.usuarios as usuario}
-
- | {usuario.nome} |
- {usuario.matricula} |
- {usuario.email} |
-
- {#if usuario.ativo && !usuario.bloqueado}
- Ativo
- {:else if usuario.bloqueado}
- Bloqueado
- {:else}
- Inativo
- {/if}
- |
-
- {/each}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mostrando {rolesFiltradas.length} de {roles.length} perfil(is)
+
+
+
+ {/if}
+
+
+ {#if carregando}
+
+
+
+ {:else if roles.length === 0}
+
+
+
Nenhum perfil encontrado
+
Não há perfis cadastrados no sistema.
+
+ {:else}
+
+ {#each rolesFiltradas as role}
+
abrirDetalhes(role)}>
+
+
+
{role.descricao}
+
{obterTextoNivel(role.nivel)}
+
+
+
+
+ Nome técnico:
+ {role.nome}
+
+
+ {#if role.setor}
+
+ Setor:
+ {role.setor}
+
+ {/if}
+
+
+ Nível:
+ {role.nivel}
+
+
+
+
- {/if}
- {/if}
+
+ {/each}
{/if}
-
- {#if modalExcluir && perfilParaExcluir}
-