Files
sgse-app/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte

950 lines
34 KiB
Svelte

<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 { authStore } from "$lib/stores/auth.svelte";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
const client = useConvexClient();
// Queries
const perfisQuery = useQuery(api.perfisCustomizados.listarPerfisCustomizados, {});
const rolesQuery = useQuery(api.roles.listar, {});
// Estados
let modo = $state<"listar" | "criar" | "editar" | "detalhes">("listar");
let perfilSelecionado = $state<any>(null);
let processando = $state(false);
let mensagem = $state<{ tipo: "success" | "error" | "warning"; texto: string } | null>(null);
let modalExcluir = $state(false);
let perfilParaExcluir = $state<any>(null);
// Formulário
let formNome = $state("");
let formDescricao = $state("");
let formNivel = $state(3);
let formClonarDeRoleId = $state<string>("");
// Detalhes do perfil
let detalhesQuery = $state<any>(null);
function mostrarMensagem(tipo: "success" | "error" | "warning", texto: string) {
mensagem = { tipo, texto };
setTimeout(() => {
mensagem = null;
}, 5000);
}
function abrirCriar() {
modo = "criar";
formNome = "";
formDescricao = "";
formNivel = 3;
formClonarDeRoleId = "";
}
function abrirEditar(perfil: any) {
modo = "editar";
perfilSelecionado = perfil;
formNome = perfil.nome;
formDescricao = perfil.descricao;
formNivel = perfil.nivel;
}
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 formatarData(timestamp: number): string {
return new Date(timestamp).toLocaleString("pt-BR");
}
</script>
<ProtectedRoute allowedRoles={["ti_master", "admin"]} maxLevel={1}>
<div class="container mx-auto px-4 py-6 max-w-7xl">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<div class="flex items-center gap-4">
<div class="p-3 bg-secondary/10 rounded-xl">
<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="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Gerenciar Perfis Customizados</h1>
<p class="text-base-content/60 mt-1">
Crie e gerencie perfis de acesso personalizados para os usuários
</p>
</div>
</div>
<div class="flex gap-2">
{#if modo !== "listar"}
<button class="btn btn-ghost gap-2" onclick={voltar} disabled={processando}>
<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>
{/if}
{#if modo === "listar"}
<a href="/ti" class="btn btn-outline btn-primary gap-2">
<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="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>
Voltar para TI
</a>
<button class="btn btn-primary gap-2" onclick={abrirCriar} disabled={processando}>
<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="M12 4v16m8-8H4"
/>
</svg>
Novo Perfil
</button>
{/if}
</div>
</div>
<!-- Mensagens -->
{#if mensagem}
<div
class="alert mb-6"
class:alert-success={mensagem.tipo === "success"}
class:alert-error={mensagem.tipo === "error"}
class:alert-warning={mensagem.tipo === "warning"}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
{#if mensagem.tipo === "success"}
<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"
/>
{:else if mensagem.tipo === "error"}
<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"
/>
{:else}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
{/if}
</svg>
<span>{mensagem.texto}</span>
</div>
{/if}
<!-- Modo: Listar -->
{#if modo === "listar"}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
{#if !perfisQuery}
<div class="flex justify-center items-center py-20">
<span class="loading loading-spinner loading-lg text-primary"></span>
</div>
{:else if perfisQuery.data && perfisQuery.data.length === 0}
<div class="text-center py-20">
<div class="text-6xl mb-4">📋</div>
<h3 class="text-2xl font-bold mb-2">Nenhum perfil customizado</h3>
<p class="text-base-content/60 mb-6">
Crie seu primeiro perfil personalizado clicando no botão acima
</p>
</div>
{:else if perfisQuery.data}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Nome</th>
<th>Descrição</th>
<th>Nível</th>
<th>Usuários</th>
<th>Criado Por</th>
<th>Criado Em</th>
<th class="text-right">Ações</th>
</tr>
</thead>
<tbody>
{#each perfisQuery.data as perfil}
<tr>
<td>
<div class="font-bold">{perfil.nome}</div>
</td>
<td>
<div class="text-sm opacity-70 max-w-xs truncate">
{perfil.descricao}
</div>
</td>
<td>
<div class="badge badge-primary">{perfil.nivel}</div>
</td>
<td>
<div class="badge badge-ghost">
{perfil.numeroUsuarios} usuário{perfil.numeroUsuarios !== 1 ? "s" : ""}
</div>
</td>
<td>
<div class="text-sm">{perfil.criadorNome}</div>
</td>
<td>
<div class="text-sm">{formatarData(perfil.criadoEm)}</div>
</td>
<td>
<div class="flex gap-2 justify-end">
<button
type="button"
class="btn btn-sm btn-info btn-square tooltip"
data-tip="Ver Detalhes"
aria-label="Ver Detalhes"
onclick={() => abrirDetalhes(perfil)}
disabled={processando}
>
<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="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>
</button>
<button
type="button"
class="btn btn-sm btn-warning btn-square tooltip"
data-tip="Editar"
aria-label="Editar"
onclick={() => abrirEditar(perfil)}
disabled={processando}
>
<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="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>
</button>
<button
type="button"
class="btn btn-sm btn-success btn-square tooltip"
data-tip="Clonar"
aria-label="Clonar"
onclick={() => clonarPerfil(perfil)}
disabled={processando}
>
<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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</button>
<button
type="button"
class="btn btn-sm btn-error btn-square tooltip"
data-tip={perfil.numeroUsuarios > 0 ? "Não pode excluir - Perfil em uso" : "Excluir"}
aria-label="Excluir"
onclick={() => abrirModalExcluir(perfil)}
disabled={processando || perfil.numeroUsuarios > 0}
>
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
{/if}
<!-- Modo: Criar -->
{#if modo === "criar"}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-6">Criar Novo Perfil Customizado</h2>
<form
onsubmit={(e) => {
e.preventDefault();
criarPerfil();
}}
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome -->
<div class="form-control">
<label class="label" for="nome">
<span class="label-text font-semibold">Nome do Perfil *</span>
</label>
<input
id="nome"
type="text"
placeholder="Ex: Coordenador de Esportes"
class="input input-bordered"
bind:value={formNome}
required
disabled={processando}
/>
</div>
<!-- Nível -->
<div class="form-control">
<label class="label" for="nivel">
<span class="label-text font-semibold">Nível de Acesso *</span>
</label>
<input
id="nivel"
type="number"
min="3"
class="input input-bordered"
bind:value={formNivel}
required
disabled={processando}
/>
<div class="label">
<span class="label-text-alt">Mínimo: 3 (perfis customizados)</span>
</div>
</div>
<!-- Descrição -->
<div class="form-control md:col-span-2">
<label class="label" for="descricao">
<span class="label-text font-semibold">Descrição *</span>
</label>
<textarea
id="descricao"
placeholder="Descreva as responsabilidades deste perfil..."
class="textarea textarea-bordered h-24"
bind:value={formDescricao}
required
disabled={processando}
></textarea>
</div>
<!-- Clonar Permissões -->
<div class="form-control md:col-span-2">
<label class="label" for="clonar">
<span class="label-text font-semibold">Clonar Permissões de (Opcional)</span>
</label>
<select
id="clonar"
class="select select-bordered"
bind:value={formClonarDeRoleId}
disabled={processando || !rolesQuery?.data}
>
<option value="">Não clonar (perfil vazio)</option>
{#if rolesQuery?.data}
{#each rolesQuery.data as role}
<option value={role._id}>{role.nome} - {role.descricao}</option>
{/each}
{/if}
</select>
<div class="label">
<span class="label-text-alt"
>Selecione um perfil existente para copiar suas permissões</span
>
</div>
</div>
</div>
<div class="card-actions justify-end mt-6">
<button
type="button"
class="btn btn-ghost"
onclick={voltar}
disabled={processando}
>
Cancelar
</button>
<button type="submit" class="btn btn-primary" disabled={processando}>
{#if processando}
<span class="loading loading-spinner"></span>
{/if}
Criar Perfil
</button>
</div>
</form>
</div>
</div>
{/if}
<!-- Modo: Editar -->
{#if modo === "editar" && perfilSelecionado}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-6">Editar Perfil: {perfilSelecionado.nome}</h2>
<form
onsubmit={(e) => {
e.preventDefault();
editarPerfil();
}}
>
<div class="grid grid-cols-1 gap-6">
<!-- Nome -->
<div class="form-control">
<label class="label" for="edit-nome">
<span class="label-text font-semibold">Nome do Perfil *</span>
</label>
<input
id="edit-nome"
type="text"
placeholder="Ex: Coordenador de Esportes"
class="input input-bordered"
bind:value={formNome}
required
disabled={processando}
/>
</div>
<!-- Descrição -->
<div class="form-control">
<label class="label" for="edit-descricao">
<span class="label-text font-semibold">Descrição *</span>
</label>
<textarea
id="edit-descricao"
placeholder="Descreva as responsabilidades deste perfil..."
class="textarea textarea-bordered h-24"
bind:value={formDescricao}
required
disabled={processando}
></textarea>
</div>
<!-- Info sobre nível -->
<div class="alert alert-info">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<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"
></path>
</svg>
<span>O nível de acesso não pode ser alterado após a criação (Nível: {formNivel})</span>
</div>
</div>
<div class="card-actions justify-end mt-6">
<button
type="button"
class="btn btn-ghost"
onclick={voltar}
disabled={processando}
>
Cancelar
</button>
<button type="submit" class="btn btn-primary" disabled={processando}>
{#if processando}
<span class="loading loading-spinner"></span>
{/if}
Salvar Alterações
</button>
</div>
</form>
</div>
</div>
{/if}
<!-- Modo: Detalhes -->
{#if modo === "detalhes" && perfilSelecionado}
<div class="space-y-6">
<!-- Informações Básicas -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">{perfilSelecionado.nome}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-sm font-semibold text-base-content/60">Descrição</p>
<p class="text-base-content">{perfilSelecionado.descricao}</p>
</div>
<div>
<p class="text-sm font-semibold text-base-content/60">Nível de Acesso</p>
<p class="text-base-content">
<span class="badge badge-primary">{perfilSelecionado.nivel}</span>
</p>
</div>
<div>
<p class="text-sm font-semibold text-base-content/60">Criado Por</p>
<p class="text-base-content">{perfilSelecionado.criadorNome}</p>
</div>
<div>
<p class="text-sm font-semibold text-base-content/60">Criado Em</p>
<p class="text-base-content">{formatarData(perfilSelecionado.criadoEm)}</p>
</div>
<div>
<p class="text-sm font-semibold text-base-content/60">Usuários com este Perfil</p>
<p class="text-base-content">
<span class="badge badge-ghost"
>{perfilSelecionado.numeroUsuarios} usuário{perfilSelecionado.numeroUsuarios !==
1
? "s"
: ""}</span
>
</p>
</div>
</div>
</div>
</div>
<!-- Permissões -->
{#if !detalhesQuery}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex justify-center items-center py-20">
<span class="loading loading-spinner loading-lg text-primary"></span>
</div>
</div>
</div>
{:else}
<!-- Permissões de Menu -->
{#if detalhesQuery.menuPermissoes && detalhesQuery.menuPermissoes.length > 0}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h3 class="card-title text-xl mb-4">Permissões de Menu</h3>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Menu</th>
<th>Acessar</th>
<th>Consultar</th>
<th>Gravar</th>
</tr>
</thead>
<tbody>
{#each detalhesQuery.menuPermissoes as perm}
<tr>
<td class="font-medium">{perm.menuPath}</td>
<td>
{#if perm.podeAcessar}
<span class="badge badge-success badge-sm">Sim</span>
{:else}
<span class="badge badge-ghost badge-sm">Não</span>
{/if}
</td>
<td>
{#if perm.podeConsultar}
<span class="badge badge-success badge-sm">Sim</span>
{:else}
<span class="badge badge-ghost badge-sm">Não</span>
{/if}
</td>
<td>
{#if perm.podeGravar}
<span class="badge badge-success badge-sm">Sim</span>
{:else}
<span class="badge badge-ghost badge-sm">Não</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div class="card-actions justify-end mt-4">
<a href="/ti/painel-permissoes" class="btn btn-sm btn-primary">
Editar Permissões
</a>
</div>
</div>
</div>
{:else}
<div class="alert alert-warning">
<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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<div>
<h3 class="font-bold">Sem permissões de menu configuradas</h3>
<div class="text-sm">
Configure as permissões de menu no <a
href="/ti/painel-permissoes"
class="link">Painel de Permissões</a
>
</div>
</div>
</div>
{/if}
<!-- Usuários com este Perfil -->
{#if detalhesQuery.usuarios && detalhesQuery.usuarios.length > 0}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h3 class="card-title text-xl mb-4">Usuários com este Perfil</h3>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Nome</th>
<th>Matrícula</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{#each detalhesQuery.usuarios as usuario}
<tr>
<td>{usuario.nome}</td>
<td>{usuario.matricula}</td>
<td>{usuario.email}</td>
<td>
{#if usuario.ativo && !usuario.bloqueado}
<span class="badge badge-success badge-sm">Ativo</span>
{:else if usuario.bloqueado}
<span class="badge badge-error badge-sm">Bloqueado</span>
{:else}
<span class="badge badge-ghost badge-sm">Inativo</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
{/if}
{/if}
</div>
{/if}
</div>
<!-- Modal de Confirmação de Exclusão -->
{#if modalExcluir && perfilParaExcluir}
<dialog class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg flex items-center gap-2">
<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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
Confirmar Exclusão
</h3>
<p class="py-4">
Tem certeza que deseja excluir o perfil <strong>"{perfilParaExcluir.nome}"</strong>?
</p>
<div class="alert alert-warning">
<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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<span>Esta ação não pode ser desfeita!</span>
</div>
<div class="modal-action">
<button type="button" class="btn btn-ghost" onclick={fecharModalExcluir} disabled={processando}>
Cancelar
</button>
<button type="button" class="btn btn-error" onclick={confirmarExclusao} disabled={processando}>
{#if processando}
<span class="loading loading-spinner loading-sm"></span>
{/if}
Excluir Perfil
</button>
</div>
</div>
<form method="dialog" class="modal-backdrop" onclick={fecharModalExcluir}>
<button>close</button>
</form>
</dialog>
{/if}
</ProtectedRoute>