refactor: remove unused authentication files and dependencies; update package.json to streamline dependencies and improve project structure
This commit is contained in:
@@ -1,84 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import MenuProtection from "$lib/components/MenuProtection.svelte";
|
||||
|
||||
import ActionGuard from "$lib/components/ActionGuard.svelte";
|
||||
|
||||
const { children } = $props();
|
||||
|
||||
// Mapa de rotas para verificação de permissões
|
||||
const ROUTE_PERMISSIONS: Record<string, { path: string; requireGravar?: boolean }> = {
|
||||
// Recursos Humanos
|
||||
"/recursos-humanos": { path: "/recursos-humanos" },
|
||||
"/recursos-humanos/funcionarios": { path: "/recursos-humanos/funcionarios" },
|
||||
"/recursos-humanos/funcionarios/cadastro": { path: "/recursos-humanos/funcionarios", requireGravar: true },
|
||||
"/recursos-humanos/funcionarios/excluir": { path: "/recursos-humanos/funcionarios", requireGravar: true },
|
||||
"/recursos-humanos/funcionarios/relatorios": { path: "/recursos-humanos/funcionarios" },
|
||||
"/recursos-humanos/simbolos": { path: "/recursos-humanos/simbolos" },
|
||||
"/recursos-humanos/simbolos/cadastro": { path: "/recursos-humanos/simbolos", requireGravar: true },
|
||||
// Outros menus
|
||||
"/financeiro": { path: "/financeiro" },
|
||||
"/controladoria": { path: "/controladoria" },
|
||||
"/licitacoes": { path: "/licitacoes" },
|
||||
"/compras": { path: "/compras" },
|
||||
"/juridico": { path: "/juridico" },
|
||||
"/comunicacao": { path: "/comunicacao" },
|
||||
"/programas-esportivos": { path: "/programas-esportivos" },
|
||||
"/secretaria-executiva": { path: "/secretaria-executiva" },
|
||||
"/gestao-pessoas": { path: "/gestao-pessoas" },
|
||||
"/ti": { path: "/ti" },
|
||||
};
|
||||
|
||||
// Obter configuração para a rota atual
|
||||
const getCurrentRouteConfig = $derived.by(() => {
|
||||
const currentPath = page.url.pathname;
|
||||
|
||||
// Verificar correspondência exata
|
||||
if (ROUTE_PERMISSIONS[currentPath]) {
|
||||
return ROUTE_PERMISSIONS[currentPath];
|
||||
|
||||
// Resolver recurso/ação a partir da rota
|
||||
const routeAction = $derived.by(() => {
|
||||
const p = page.url.pathname;
|
||||
if (p === "/" || p === "/solicitar-acesso") return null;
|
||||
|
||||
// Funcionários
|
||||
if (p.startsWith("/recursos-humanos/funcionarios")) {
|
||||
if (p.includes("/cadastro"))
|
||||
return { recurso: "funcionarios", acao: "criar" };
|
||||
if (p.includes("/excluir"))
|
||||
return { recurso: "funcionarios", acao: "excluir" };
|
||||
if (p.includes("/editar") || p.includes("/funcionarioId"))
|
||||
return { recurso: "funcionarios", acao: "editar" };
|
||||
return { recurso: "funcionarios", acao: "listar" };
|
||||
}
|
||||
|
||||
// Verificar rotas dinâmicas (com [id])
|
||||
if (currentPath.includes("/editar") || currentPath.includes("/funcionarioId") || currentPath.includes("/simboloId")) {
|
||||
// Extrair o caminho base
|
||||
if (currentPath.includes("/funcionarios/")) {
|
||||
return { path: "/recursos-humanos/funcionarios", requireGravar: true };
|
||||
}
|
||||
if (currentPath.includes("/simbolos/")) {
|
||||
return { path: "/recursos-humanos/simbolos", requireGravar: true };
|
||||
}
|
||||
|
||||
// Símbolos
|
||||
if (p.startsWith("/recursos-humanos/simbolos")) {
|
||||
if (p.includes("/cadastro"))
|
||||
return { recurso: "simbolos", acao: "criar" };
|
||||
if (p.includes("/excluir"))
|
||||
return { recurso: "simbolos", acao: "excluir" };
|
||||
if (p.includes("/editar") || p.includes("/simboloId"))
|
||||
return { recurso: "simbolos", acao: "editar" };
|
||||
return { recurso: "simbolos", acao: "listar" };
|
||||
}
|
||||
|
||||
// Rotas públicas (Dashboard, Solicitar Acesso, etc)
|
||||
if (currentPath === "/" || currentPath === "/solicitar-acesso") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Para qualquer outra rota dentro do dashboard, verificar o primeiro segmento
|
||||
const segments = currentPath.split("/").filter(Boolean);
|
||||
if (segments.length > 0) {
|
||||
const firstSegment = "/" + segments[0];
|
||||
if (ROUTE_PERMISSIONS[firstSegment]) {
|
||||
return ROUTE_PERMISSIONS[firstSegment];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Outras áreas (uso genérico: ver)
|
||||
if (p.startsWith("/financeiro"))
|
||||
return { recurso: "financeiro", acao: "ver" };
|
||||
if (p.startsWith("/controladoria"))
|
||||
return { recurso: "controladoria", acao: "ver" };
|
||||
if (p.startsWith("/licitacoes"))
|
||||
return { recurso: "licitacoes", acao: "ver" };
|
||||
if (p.startsWith("/compras")) return { recurso: "compras", acao: "ver" };
|
||||
if (p.startsWith("/juridico")) return { recurso: "juridico", acao: "ver" };
|
||||
if (p.startsWith("/comunicacao"))
|
||||
return { recurso: "comunicacao", acao: "ver" };
|
||||
if (p.startsWith("/programas-esportivos"))
|
||||
return { recurso: "programas_esportivos", acao: "ver" };
|
||||
if (p.startsWith("/secretaria-executiva"))
|
||||
return { recurso: "secretaria_executiva", acao: "ver" };
|
||||
if (p.startsWith("/gestao-pessoas"))
|
||||
return { recurso: "gestao_pessoas", acao: "ver" };
|
||||
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if getCurrentRouteConfig}
|
||||
<MenuProtection menuPath={getCurrentRouteConfig.path} requireGravar={getCurrentRouteConfig.requireGravar || false}>
|
||||
<main
|
||||
id="container-central"
|
||||
class="w-full max-w-none px-3 lg:px-4 py-4"
|
||||
>
|
||||
{#if routeAction}
|
||||
<ActionGuard recurso={routeAction.recurso} acao={routeAction.acao}>
|
||||
<main id="container-central" class="w-full max-w-none px-3 lg:px-4 py-4">
|
||||
{@render children()}
|
||||
</main>
|
||||
</MenuProtection>
|
||||
</ActionGuard>
|
||||
{:else}
|
||||
<main
|
||||
id="container-central"
|
||||
class="w-full max-w-none px-3 lg:px-4 py-4"
|
||||
>
|
||||
<main id="container-central" class="w-full max-w-none px-3 lg:px-4 py-4">
|
||||
{@render children()}
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
@@ -4,16 +4,46 @@
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
type RoleRow = {
|
||||
_id: Id<"roles">;
|
||||
_creationTime: number;
|
||||
nome: string;
|
||||
descricao: string;
|
||||
nivel: number;
|
||||
setor?: string;
|
||||
customizado: boolean;
|
||||
editavel?: boolean;
|
||||
criadoPor?: Id<"usuarios">;
|
||||
};
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
// Buscar matriz de permissões
|
||||
const matrizQuery = useQuery(api.menuPermissoes.obterMatrizPermissoes, {});
|
||||
// Carregar lista de roles e catálogo de recursos/ações
|
||||
const rolesQuery = useQuery(api.roles.listar, {});
|
||||
const catalogoQuery = useQuery(api.permissoesAcoes.listarRecursosEAcoes, {});
|
||||
|
||||
let salvando = $state(false);
|
||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
|
||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(
|
||||
null
|
||||
);
|
||||
let busca = $state("");
|
||||
let filtroRole = $state("");
|
||||
let expandido: Record<string, boolean> = $state({});
|
||||
|
||||
// Cache de permissões por role
|
||||
let permissoesPorRole: Record<
|
||||
string,
|
||||
Array<{ recurso: string; acoes: Array<string> }>
|
||||
> = $state({});
|
||||
|
||||
async function carregarPermissoesRole(roleId: Id<"roles">) {
|
||||
if (permissoesPorRole[roleId]) return;
|
||||
const dados = await client.query(
|
||||
api.permissoesAcoes.listarPermissoesAcoesPorRole,
|
||||
{ roleId }
|
||||
);
|
||||
permissoesPorRole[roleId] = dados;
|
||||
}
|
||||
|
||||
function mostrarMensagem(tipo: "success" | "error", texto: string) {
|
||||
mensagem = { tipo, texto };
|
||||
@@ -22,89 +52,50 @@
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
const dadosFiltrados = $derived.by(() => {
|
||||
if (!matrizQuery.data) return [];
|
||||
|
||||
let resultado = matrizQuery.data;
|
||||
|
||||
// Filtrar por role
|
||||
if (filtroRole) {
|
||||
resultado = resultado.filter(r => r.role._id === filtroRole);
|
||||
}
|
||||
|
||||
// Filtrar por busca
|
||||
const rolesFiltradas = $derived.by(() => {
|
||||
if (!rolesQuery.data) return [];
|
||||
let rs: Array<RoleRow> = rolesQuery.data as Array<RoleRow>;
|
||||
if (filtroRole)
|
||||
rs = rs.filter((r: RoleRow) => r._id === (filtroRole as any));
|
||||
if (busca.trim()) {
|
||||
const buscaLower = busca.toLowerCase();
|
||||
resultado = resultado.map(roleData => ({
|
||||
...roleData,
|
||||
permissoes: roleData.permissoes.filter(p =>
|
||||
p.menuNome.toLowerCase().includes(buscaLower) ||
|
||||
p.menuPath.toLowerCase().includes(buscaLower)
|
||||
)
|
||||
})).filter(roleData => roleData.permissoes.length > 0);
|
||||
const b = busca.toLowerCase();
|
||||
rs = rs.filter(
|
||||
(r: RoleRow) =>
|
||||
r.descricao.toLowerCase().includes(b) ||
|
||||
r.nome.toLowerCase().includes(b)
|
||||
);
|
||||
}
|
||||
|
||||
return resultado;
|
||||
return rs;
|
||||
});
|
||||
|
||||
async function atualizarPermissao(
|
||||
async function toggleAcao(
|
||||
roleId: Id<"roles">,
|
||||
menuPath: string,
|
||||
campo: "podeAcessar" | "podeConsultar" | "podeGravar",
|
||||
valor: boolean
|
||||
recurso: string,
|
||||
acao: string,
|
||||
conceder: boolean
|
||||
) {
|
||||
try {
|
||||
salvando = true;
|
||||
|
||||
// Buscar a permissão atual
|
||||
const roleData = matrizQuery.data?.find((r) => r.role._id === roleId);
|
||||
const permissaoAtual = roleData?.permissoes.find((p) => p.menuPath === menuPath);
|
||||
|
||||
if (!permissaoAtual) {
|
||||
throw new Error("Permissão não encontrada");
|
||||
}
|
||||
|
||||
// Inicializar com valores atuais
|
||||
let podeAcessar = permissaoAtual.podeAcessar;
|
||||
let podeConsultar = permissaoAtual.podeConsultar;
|
||||
let podeGravar = permissaoAtual.podeGravar;
|
||||
|
||||
// Aplicar lógica de dependências baseada no campo alterado
|
||||
if (campo === "podeAcessar") {
|
||||
podeAcessar = valor;
|
||||
// Se desmarcou "Acessar", desmarcar tudo
|
||||
if (!valor) {
|
||||
podeConsultar = false;
|
||||
podeGravar = false;
|
||||
}
|
||||
// Se marcou "Acessar", manter os outros valores como estão
|
||||
} else if (campo === "podeConsultar") {
|
||||
podeConsultar = valor;
|
||||
// Se marcou "Consultar", marcar "Acessar" automaticamente
|
||||
if (valor) {
|
||||
podeAcessar = true;
|
||||
} else {
|
||||
// Se desmarcou "Consultar", desmarcar "Gravar"
|
||||
podeGravar = false;
|
||||
}
|
||||
} else if (campo === "podeGravar") {
|
||||
podeGravar = valor;
|
||||
// Se marcou "Gravar", marcar "Consultar" e "Acessar" automaticamente
|
||||
if (valor) {
|
||||
podeAcessar = true;
|
||||
podeConsultar = true;
|
||||
}
|
||||
// Se desmarcou "Gravar", manter os outros como estão
|
||||
}
|
||||
|
||||
await client.mutation(api.menuPermissoes.atualizarPermissao, {
|
||||
await client.mutation(api.permissoesAcoes.atualizarPermissaoAcao, {
|
||||
roleId,
|
||||
menuPath,
|
||||
podeAcessar,
|
||||
podeConsultar,
|
||||
podeGravar,
|
||||
recurso,
|
||||
acao,
|
||||
conceder,
|
||||
});
|
||||
|
||||
// Atualizar cache local
|
||||
const atual = permissoesPorRole[roleId] || [];
|
||||
const entry = atual.find((e) => e.recurso === recurso);
|
||||
if (entry) {
|
||||
const set = new Set(entry.acoes);
|
||||
if (conceder) set.add(acao);
|
||||
else set.delete(acao);
|
||||
entry.acoes = Array.from(set);
|
||||
} else {
|
||||
permissoesPorRole[roleId] = [
|
||||
...atual,
|
||||
{ recurso, acoes: conceder ? [acao] : [] },
|
||||
];
|
||||
}
|
||||
mostrarMensagem("success", "Permissão atualizada com sucesso!");
|
||||
} catch (e: any) {
|
||||
mostrarMensagem("error", e.message || "Erro ao atualizar permissão");
|
||||
@@ -113,16 +104,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function inicializarPermissoes(roleId: Id<"roles">) {
|
||||
try {
|
||||
salvando = true;
|
||||
await client.mutation(api.menuPermissoes.inicializarPermissoesRole, { roleId });
|
||||
mostrarMensagem("success", "Permissões inicializadas!");
|
||||
} catch (e: any) {
|
||||
mostrarMensagem("error", e.message || "Erro ao inicializar permissões");
|
||||
} finally {
|
||||
salvando = false;
|
||||
}
|
||||
function isConcedida(roleId: Id<"roles">, recurso: string, acao: string) {
|
||||
const dados = permissoesPorRole[roleId];
|
||||
const entry = dados?.find((e) => e.recurso === recurso);
|
||||
return entry ? entry.acoes.includes(acao) : false;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -132,8 +117,19 @@
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/" class="text-primary hover:text-primary-focus">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
@@ -149,17 +145,43 @@
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<div class="p-3 bg-primary/10 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-primary"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-3xl font-bold text-base-content">Gerenciar Permissões de Acesso</h1>
|
||||
<p class="text-base-content/60 mt-1">Configure as permissões de acesso aos menus do sistema por função</p>
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Gerenciar Permissões de Acesso
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Configure as permissões de acesso aos menus do sistema por função
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto("/ti")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
Voltar
|
||||
</button>
|
||||
@@ -168,14 +190,38 @@
|
||||
|
||||
<!-- Alertas -->
|
||||
{#if mensagem}
|
||||
<div class="alert mb-6 shadow-lg" class:alert-success={mensagem.tipo === "success"} class:alert-error={mensagem.tipo === "error"}>
|
||||
<div
|
||||
class="alert mb-6 shadow-lg"
|
||||
class:alert-success={mensagem.tipo === "success"}
|
||||
class:alert-error={mensagem.tipo === "error"}
|
||||
>
|
||||
{#if mensagem.tipo === "success"}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span class="font-semibold">{mensagem.texto}</span>
|
||||
@@ -189,13 +235,13 @@
|
||||
<!-- Busca por menu -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="busca">
|
||||
<span class="label-text font-semibold">Buscar Menu</span>
|
||||
<span class="label-text font-semibold">Buscar Perfil</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
id="busca"
|
||||
type="text"
|
||||
placeholder="Digite o nome ou caminho do menu..."
|
||||
placeholder="Digite o nome/descrição do perfil..."
|
||||
class="input input-bordered w-full pr-10"
|
||||
bind:value={busca}
|
||||
/>
|
||||
@@ -227,10 +273,10 @@
|
||||
bind:value={filtroRole}
|
||||
>
|
||||
<option value="">Todos os perfis</option>
|
||||
{#if matrizQuery.data}
|
||||
{#each matrizQuery.data as roleData}
|
||||
<option value={roleData.role._id}>
|
||||
{roleData.role.descricao} ({roleData.role.nome})
|
||||
{#if rolesQuery.data}
|
||||
{#each rolesQuery.data as roleRow}
|
||||
<option value={roleRow._id}>
|
||||
{roleRow.descricao} ({roleRow.nome})
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -272,8 +318,18 @@
|
||||
|
||||
<!-- Informações sobre o sistema de permissões -->
|
||||
<div class="alert alert-info mb-6 shadow-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="font-bold text-lg">Como funciona o sistema de permissões:</h3>
|
||||
@@ -281,9 +337,13 @@
|
||||
<div>
|
||||
<h4 class="font-semibold text-sm">Tipos de Permissão:</h4>
|
||||
<ul class="text-sm mt-1 space-y-1">
|
||||
<li>• <strong>Acessar:</strong> Visualizar menu e acessar página</li>
|
||||
<li>
|
||||
• <strong>Acessar:</strong> Visualizar menu e acessar página
|
||||
</li>
|
||||
<li>• <strong>Consultar:</strong> Ver dados (requer "Acessar")</li>
|
||||
<li>• <strong>Gravar:</strong> Criar/editar/excluir (requer "Consultar")</li>
|
||||
<li>
|
||||
• <strong>Gravar:</strong> Criar/editar/excluir (requer "Consultar")
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
@@ -291,27 +351,39 @@
|
||||
<ul class="text-sm mt-1 space-y-1">
|
||||
<li>• <strong>Admin e TI:</strong> Acesso total automático</li>
|
||||
<li>• <strong>Dashboard:</strong> Público para todos</li>
|
||||
<li>• <strong>Perfil Customizado:</strong> Permissões personalizadas</li>
|
||||
<li>
|
||||
• <strong>Perfil Customizado:</strong> Permissões personalizadas
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Matriz de Permissões -->
|
||||
{#if matrizQuery.isLoading}
|
||||
<!-- Matriz de Permissões por Ação -->
|
||||
{#if rolesQuery.isLoading || catalogoQuery.isLoading}
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if matrizQuery.error}
|
||||
{:else if rolesQuery.error}
|
||||
<div class="alert alert-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Erro ao carregar permissões: {matrizQuery.error.message}</span>
|
||||
<span>Erro ao carregar perfis: {rolesQuery.error.message}</span>
|
||||
</div>
|
||||
{:else if matrizQuery.data}
|
||||
{#if dadosFiltrados.length === 0}
|
||||
{:else if rolesQuery.data && catalogoQuery.data}
|
||||
{#if rolesFiltradas.length === 0}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body items-center text-center">
|
||||
<svg
|
||||
@@ -330,7 +402,9 @@
|
||||
</svg>
|
||||
<h3 class="text-xl font-bold mt-4">Nenhum resultado encontrado</h3>
|
||||
<p class="text-base-content/60">
|
||||
{busca ? `Não foram encontrados menus com "${busca}"` : "Nenhuma permissão corresponde aos filtros aplicados"}
|
||||
{busca
|
||||
? `Não foram encontrados perfis com "${busca}"`
|
||||
: "Nenhum perfil corresponde aos filtros aplicados"}
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary btn-sm mt-4"
|
||||
@@ -344,159 +418,119 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each dadosFiltrados as roleData}
|
||||
|
||||
{#each rolesFiltradas as roleRow}
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4 flex-wrap gap-4">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<h2 class="card-title text-2xl">{roleData.role.descricao}</h2>
|
||||
<div class="badge badge-lg badge-primary">Nível {roleData.role.nivel}</div>
|
||||
{#if roleData.role.nivel <= 1}
|
||||
<h2 class="card-title text-2xl">{roleRow.descricao}</h2>
|
||||
<div class="badge badge-lg badge-primary">
|
||||
Nível {roleRow.nivel}
|
||||
</div>
|
||||
{#if roleRow.nivel <= 1}
|
||||
<div class="badge badge-lg badge-success gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
Acesso Total
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-sm text-base-content/60">
|
||||
<span class="font-mono bg-base-200 px-2 py-1 rounded">{roleData.role.nome}</span>
|
||||
<span class="font-mono bg-base-200 px-2 py-1 rounded"
|
||||
>{roleRow.nome}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if roleData.role.nivel > 1}
|
||||
|
||||
{#if roleRow.nivel > 1}
|
||||
<button
|
||||
class="btn btn-sm btn-outline btn-primary"
|
||||
onclick={() => inicializarPermissoes(roleData.role._id)}
|
||||
disabled={salvando}
|
||||
class="btn btn-sm btn-outline"
|
||||
onclick={async () => {
|
||||
expandido[roleRow._id] = !expandido[roleRow._id];
|
||||
if (expandido[roleRow._id])
|
||||
await carregarPermissoesRole(roleRow._id);
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Inicializar Permissões
|
||||
{expandido[roleRow._id] ? "Recolher" : "Expandir"}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if roleData.role.nivel <= 1}
|
||||
{#if roleRow.nivel <= 1}
|
||||
<div class="alert alert-success shadow-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="font-bold">Perfil Administrativo</h3>
|
||||
<div class="text-sm">Este perfil possui acesso total ao sistema automaticamente, sem necessidade de configuração manual.</div>
|
||||
<div class="text-sm">
|
||||
Este perfil possui acesso total ao sistema automaticamente,
|
||||
sem necessidade de configuração manual.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow mb-4 w-full">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total de Menus</div>
|
||||
<div class="stat-value text-primary">{roleData.permissoes.length}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Com Acesso</div>
|
||||
<div class="stat-value text-info">{roleData.permissoes.filter(p => p.podeAcessar).length}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Pode Consultar</div>
|
||||
<div class="stat-value text-success">{roleData.permissoes.filter(p => p.podeConsultar).length}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Pode Gravar</div>
|
||||
<div class="stat-value text-warning">{roleData.permissoes.filter(p => p.podeGravar).length}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if expandido[roleRow._id]}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra table-sm">
|
||||
<thead class="bg-base-200">
|
||||
<tr>
|
||||
<th class="w-1/3">Menu</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
Acessar
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
Consultar
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
Gravar
|
||||
</div>
|
||||
</th>
|
||||
<th class="w-1/3">Recurso</th>
|
||||
<th class="text-center">Ver</th>
|
||||
<th class="text-center">Listar</th>
|
||||
<th class="text-center">Criar</th>
|
||||
<th class="text-center">Editar</th>
|
||||
<th class="text-center">Excluir</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each roleData.permissoes as permissao}
|
||||
{#each catalogoQuery.data as item}
|
||||
<tr class="hover">
|
||||
<td>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold">{permissao.menuNome}</span>
|
||||
<span class="text-xs text-base-content/60">{permissao.menuPath}</span>
|
||||
<span class="font-semibold">{item.recurso}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary"
|
||||
checked={permissao.podeAcessar}
|
||||
disabled={salvando}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(
|
||||
roleData.role._id,
|
||||
permissao.menuPath,
|
||||
"podeAcessar",
|
||||
e.currentTarget.checked
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-info"
|
||||
checked={permissao.podeConsultar}
|
||||
disabled={salvando || !permissao.podeAcessar}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(
|
||||
roleData.role._id,
|
||||
permissao.menuPath,
|
||||
"podeConsultar",
|
||||
e.currentTarget.checked
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-success"
|
||||
checked={permissao.podeGravar}
|
||||
disabled={salvando || !permissao.podeConsultar}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(
|
||||
roleData.role._id,
|
||||
permissao.menuPath,
|
||||
"podeGravar",
|
||||
e.currentTarget.checked
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
{#each ["ver", "listar", "criar", "editar", "excluir"] as ac}
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary"
|
||||
checked={isConcedida(roleRow._id, item.recurso, ac)}
|
||||
disabled={salvando}
|
||||
onchange={(e) =>
|
||||
toggleAcao(
|
||||
roleRow._id,
|
||||
item.recurso,
|
||||
ac,
|
||||
e.currentTarget.checked
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -508,4 +542,3 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</ProtectedRoute>
|
||||
|
||||
|
||||
@@ -1,123 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
let matriculaBusca = $state("");
|
||||
let usuarioEncontrado = $state<any>(null);
|
||||
let buscando = $state(false);
|
||||
let salvando = $state(false);
|
||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
|
||||
|
||||
// Buscar permissões personalizadas do usuário
|
||||
const permissoesQuery = $derived(
|
||||
usuarioEncontrado
|
||||
? useQuery(api.menuPermissoes.listarPermissoesPersonalizadas, {
|
||||
matricula: usuarioEncontrado.matricula,
|
||||
})
|
||||
: null
|
||||
);
|
||||
|
||||
// Buscar menus disponíveis
|
||||
const menusQuery = useQuery(api.menuPermissoes.listarMenus, {});
|
||||
|
||||
async function buscarUsuario() {
|
||||
if (!matriculaBusca.trim()) {
|
||||
mensagem = { tipo: "error", texto: "Digite uma matrícula para buscar" };
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
buscando = true;
|
||||
const usuario = await client.query(api.menuPermissoes.buscarUsuarioPorMatricula, {
|
||||
matricula: matriculaBusca.trim(),
|
||||
});
|
||||
|
||||
if (usuario) {
|
||||
usuarioEncontrado = usuario;
|
||||
mensagem = null;
|
||||
} else {
|
||||
usuarioEncontrado = null;
|
||||
mensagem = { tipo: "error", texto: "Usuário não encontrado com esta matrícula" };
|
||||
}
|
||||
} catch (e: any) {
|
||||
mensagem = { tipo: "error", texto: e.message || "Erro ao buscar usuário" };
|
||||
} finally {
|
||||
buscando = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function atualizarPermissao(
|
||||
menuPath: string,
|
||||
campo: "podeAcessar" | "podeConsultar" | "podeGravar",
|
||||
valor: boolean
|
||||
) {
|
||||
if (!usuarioEncontrado) return;
|
||||
|
||||
try {
|
||||
salvando = true;
|
||||
|
||||
// Obter permissão atual do menu
|
||||
const permissaoAtual = permissoesQuery?.data?.find((p) => p.menuPath === menuPath);
|
||||
|
||||
let podeAcessar = valor;
|
||||
let podeConsultar = false;
|
||||
let podeGravar = false;
|
||||
|
||||
// Aplicar lógica de dependências
|
||||
if (campo === "podeGravar" && valor) {
|
||||
podeAcessar = true;
|
||||
podeConsultar = true;
|
||||
podeGravar = true;
|
||||
} else if (campo === "podeConsultar" && valor) {
|
||||
podeAcessar = true;
|
||||
podeConsultar = true;
|
||||
podeGravar = permissaoAtual?.podeGravar || false;
|
||||
} else if (campo === "podeAcessar" && !valor) {
|
||||
podeAcessar = false;
|
||||
podeConsultar = false;
|
||||
podeGravar = false;
|
||||
} else if (campo === "podeConsultar" && !valor) {
|
||||
podeAcessar = permissaoAtual?.podeAcessar !== undefined ? permissaoAtual.podeAcessar : false;
|
||||
podeConsultar = false;
|
||||
podeGravar = false;
|
||||
} else if (campo === "podeGravar" && !valor) {
|
||||
podeAcessar = permissaoAtual?.podeAcessar !== undefined ? permissaoAtual.podeAcessar : false;
|
||||
podeConsultar = permissaoAtual?.podeConsultar !== undefined ? permissaoAtual.podeConsultar : false;
|
||||
podeGravar = false;
|
||||
} else if (permissaoAtual) {
|
||||
podeAcessar = permissaoAtual.podeAcessar;
|
||||
podeConsultar = permissaoAtual.podeConsultar;
|
||||
podeGravar = permissaoAtual.podeGravar;
|
||||
}
|
||||
|
||||
await client.mutation(api.menuPermissoes.atualizarPermissaoPersonalizada, {
|
||||
matricula: usuarioEncontrado.matricula,
|
||||
menuPath,
|
||||
podeAcessar,
|
||||
podeConsultar,
|
||||
podeGravar,
|
||||
});
|
||||
|
||||
mensagem = { tipo: "success", texto: "Permissão personalizada atualizada!" };
|
||||
setTimeout(() => {
|
||||
mensagem = null;
|
||||
}, 3000);
|
||||
} catch (e: any) {
|
||||
mensagem = { tipo: "error", texto: e.message || "Erro ao atualizar permissão" };
|
||||
} finally {
|
||||
salvando = false;
|
||||
}
|
||||
}
|
||||
|
||||
function limparBusca() {
|
||||
matriculaBusca = "";
|
||||
usuarioEncontrado = null;
|
||||
mensagem = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<ProtectedRoute allowedRoles={["admin", "ti"]} maxLevel={1}>
|
||||
@@ -126,8 +9,19 @@
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/" class="text-primary hover:text-primary-focus">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
@@ -143,241 +37,71 @@
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<div class="p-3 bg-info/10 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-info" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-info"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-3xl font-bold text-base-content">Personalizar Permissões por Matrícula</h1>
|
||||
<p class="text-base-content/60 mt-1">Configure permissões específicas para usuários individuais</p>
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Funcionalidade descontinuada
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Agora as permissões são configuradas por ação em cada perfil no painel
|
||||
de permissões.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto("/ti")}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
Voltar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertas -->
|
||||
{#if mensagem}
|
||||
<div class="alert mb-6 shadow-lg" class:alert-success={mensagem.tipo === "success"} class:alert-error={mensagem.tipo === "error"}>
|
||||
{#if mensagem.tipo === "success"}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{/if}
|
||||
<span class="font-semibold">{mensagem.texto}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Card de Busca -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Buscar Usuário</h2>
|
||||
<p class="text-sm text-base-content/60">Digite a matrícula do usuário para personalizar suas permissões</p>
|
||||
|
||||
<div class="flex gap-4 mt-4">
|
||||
<div class="form-control flex-1">
|
||||
<label class="label" for="matricula-busca">
|
||||
<span class="label-text font-semibold">Matrícula</span>
|
||||
</label>
|
||||
<input
|
||||
id="matricula-busca"
|
||||
type="text"
|
||||
class="input input-bordered input-primary w-full"
|
||||
placeholder="Digite a matrícula..."
|
||||
bind:value={matriculaBusca}
|
||||
disabled={buscando}
|
||||
onkeydown={(e) => e.key === "Enter" && buscarUsuario()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end gap-2">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={buscarUsuario}
|
||||
disabled={buscando || !matriculaBusca.trim()}
|
||||
>
|
||||
{#if buscando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
{/if}
|
||||
Buscar
|
||||
</button>
|
||||
|
||||
{#if usuarioEncontrado}
|
||||
<button class="btn btn-ghost" onclick={limparBusca}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
Limpar
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
A personalização por usuário foi substituída por <strong
|
||||
>permissões por ação</strong
|
||||
>
|
||||
por perfil. Utilize o
|
||||
<a href="/ti/painel-permissoes" class="link link-primary"
|
||||
>Painel de Permissões</a
|
||||
> para configurar.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Informações do Usuário -->
|
||||
{#if usuarioEncontrado}
|
||||
<div class="card bg-gradient-to-br from-info/10 to-info/5 shadow-xl mb-6 border-2 border-info/20">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-info text-info-content rounded-full w-16">
|
||||
<span class="text-2xl font-bold">{usuarioEncontrado.nome.charAt(0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl font-bold">{usuarioEncontrado.nome}</h3>
|
||||
<div class="flex gap-4 mt-1 text-sm">
|
||||
<span class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||
</svg>
|
||||
<strong>Matrícula:</strong> {usuarioEncontrado.matricula}
|
||||
</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<strong>Email:</strong> {usuarioEncontrado.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="badge badge-primary badge-lg">
|
||||
Nível {usuarioEncontrado.role.nivel}
|
||||
</div>
|
||||
<p class="text-sm mt-1">{usuarioEncontrado.role.descricao}</p>
|
||||
<div class="badge mt-2" class:badge-success={usuarioEncontrado.ativo} class:badge-error={!usuarioEncontrado.ativo}>
|
||||
{usuarioEncontrado.ativo ? "Ativo" : "Inativo"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabela de Permissões -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Permissões Personalizadas</h2>
|
||||
<div class="alert alert-info mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm">
|
||||
<strong>Permissões personalizadas sobrepõem as permissões da função.</strong><br />
|
||||
Configure apenas os menus que deseja personalizar para este usuário.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if menusQuery.isLoading}
|
||||
<div class="flex justify-center py-12">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if menusQuery.data}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra table-sm">
|
||||
<thead class="bg-base-200">
|
||||
<tr>
|
||||
<th class="w-1/3">Menu</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
Acessar
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
Consultar
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
Gravar
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each menusQuery.data as menu}
|
||||
{@const permissao = permissoesQuery?.data?.find((p) => p.menuPath === menu.path)}
|
||||
<tr class="hover">
|
||||
<td>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold">{menu.nome}</span>
|
||||
<span class="text-xs text-base-content/60">{menu.path}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary"
|
||||
checked={permissao?.podeAcessar || false}
|
||||
disabled={salvando}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(menu.path, "podeAcessar", e.currentTarget.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-info"
|
||||
checked={permissao?.podeConsultar || false}
|
||||
disabled={salvando || !permissao?.podeAcessar}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(menu.path, "podeConsultar", e.currentTarget.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-success"
|
||||
checked={permissao?.podeGravar || false}
|
||||
disabled={salvando || !permissao?.podeConsultar}
|
||||
onchange={(e) =>
|
||||
atualizarPermissao(menu.path, "podeGravar", e.currentTarget.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{#if permissao}
|
||||
<div class="badge badge-warning badge-sm">Personalizado</div>
|
||||
{:else}
|
||||
<div class="badge badge-ghost badge-sm">Padrão da Função</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</ProtectedRoute>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user