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} -
    - - {#if mensagem.tipo === "success"} - - {:else if mensagem.tipo === "error"} - - {:else} - - {/if} - - {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} -
    - - - - - - - - - - - - - - {#each perfisQuery.data as perfil} - - - - - - - - - - {/each} - -
    NomeDescriçãoNívelUsuáriosCriado PorCriado EmAções
    -
    {perfil.nome}
    -
    -
    - {perfil.descricao} -
    -
    -
    {perfil.nivel}
    -
    -
    - {perfil.numeroUsuarios} usuário{perfil.numeroUsuarios !== 1 ? "s" : ""} -
    -
    -
    {perfil.criadorNome}
    -
    -
    {formatarData(perfil.criadoEm)}
    -
    -
    - - - - -
    -
    -
    - {/if} -
    -
    - {/if} - - - {#if modo === "criar"} -
    -
    -

    Criar Novo Perfil Customizado

    - -
    { - e.preventDefault(); - criarPerfil(); - }} - > -
    - -
    - - -
    - - -
    - - -
    - Mínimo: 3 (perfis customizados) -
    -
    - - -
    - - -
    - - -
    - - -
    - Selecione um perfil existente para copiar suas permissões -
    -
    -
    - -
    - - -
    -
    -
    -
    - {/if} - - - {#if modo === "editar" && perfilSelecionado} -
    -
    -

    Editar Perfil: {perfilSelecionado.nome}

    - -
    { - e.preventDefault(); - editarPerfil(); - }} - > -
    - -
    - - -
    - - -
    - - -
    - - -
    - - - - O nível de acesso não pode ser alterado após a criação (Nível: {formNivel}) -
    -
    - -
    - - -
    -
    -
    -
    - {/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

    -
    - - - - - - - - - - - {#each detalhesQuery.menuPermissoes as perm} - - - - - - - {/each} - -
    MenuAcessarConsultarGravar
    {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} -
    -
    - -
    -
    - {:else} -
    +
    +

    Filtros de Busca

    +
    - {/if} + Limpar Filtros + +
    - - {#if detalhesQuery.usuarios && detalhesQuery.usuarios.length > 0} -
    -
    -

    Usuários com este Perfil

    -
    - - - - - - - - - - - {#each detalhesQuery.usuarios as usuario} - - - - - - - {/each} - -
    NomeMatrículaEmailStatus
    {usuario.nome}{usuario.matricula}{usuario.email} - {#if usuario.ativo && !usuario.bloqueado} - Ativo - {:else if usuario.bloqueado} - Bloqueado - {:else} - Inativo - {/if} -
    +
    + +
    + + +
    + + +
    + + +
    +
    + +
    + 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} - - + +
    {/if} diff --git a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte index 6f2aa89..4b87a11 100644 --- a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte @@ -2,7 +2,82 @@ import { useConvexClient, useQuery } from "convex-svelte"; import { api } from "@sgse-app/backend/convex/_generated/api"; import { authStore } from "$lib/stores/auth.svelte"; + import ProtectedRoute from "$lib/components/ProtectedRoute.svelte"; import { goto } from "$app/navigation"; + import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel"; + + // Tipos baseados nos retornos das queries do backend + type Usuario = { + _id: Id<"usuarios">; + matricula: string; + nome: string; + email: string; + ativo: boolean; + bloqueado?: boolean; + motivoBloqueio?: string; + primeiroAcesso: boolean; + ultimoAcesso?: number; + criadoEm: number; + role: { + _id: Id<"roles">; + _creationTime?: number; + criadoPor?: Id<"usuarios">; + customizado?: boolean; + descricao: string; + editavel?: boolean; + nome: string; + nivel: number; + setor?: string; + erro?: boolean; + }; + funcionario?: { + _id: Id<"funcionarios">; + nome: string; + matricula?: string; + descricaoCargo?: string; + simboloTipo: "cargo_comissionado" | "funcao_gratificada"; + }; + avisos?: Array<{ + tipo: "erro" | "aviso" | "info"; + mensagem: string; + }>; + }; + + type Funcionario = { + _id: Id<"funcionarios">; + nome: string; + matricula?: string; + cpf?: string; + rg?: string; + nascimento?: string; + email?: string; + telefone?: string; + endereco?: string; + cep?: string; + cidade?: string; + uf?: string; + simboloId: Id<"simbolos">; + simboloTipo: "cargo_comissionado" | "funcao_gratificada"; + admissaoData?: string; + desligamentoData?: string; + descricaoCargo?: string; + }; + + type Gestor = Doc<"usuarios"> | null; + + type TimeComDetalhes = Doc<"times"> & { + gestor: Gestor; + totalMembros: number; + }; + + type MembroTime = Doc<"timesMembros"> & { + funcionario: Doc<"funcionarios"> | null; + }; + + type TimeComMembros = Doc<"times"> & { + gestor: Gestor; + membros: MembroTime[]; + }; const client = useConvexClient(); @@ -11,17 +86,23 @@ const usuariosQuery = useQuery(api.usuarios.listar, {}); const funcionariosQuery = useQuery(api.funcionarios.getAll, {}); - const times = $derived(timesQuery?.data || []); - const usuarios = $derived(usuariosQuery?.data || []); - const funcionarios = $derived(funcionariosQuery?.data || []); + const times = $derived((timesQuery?.data || []) as TimeComDetalhes[]); + const usuarios = $derived((usuariosQuery?.data || []) as Usuario[]); + const funcionarios = $derived((funcionariosQuery?.data || []) as Funcionario[]); + + const carregando = $derived( + timesQuery === undefined || + usuariosQuery === undefined || + funcionariosQuery === undefined + ); // Estados let modoEdicao = $state(false); - let timeEmEdicao = $state(null); + let timeEmEdicao = $state(null); let mostrarModalMembros = $state(false); - let timeParaMembros = $state(null); + let timeParaMembros = $state(null); let mostrarConfirmacaoExclusao = $state(false); - let timeParaExcluir = $state(null); + let timeParaExcluir = $state(null); let processando = $state(false); // Form @@ -32,9 +113,9 @@ // Membros let membrosDisponiveis = $derived( - funcionarios.filter((f: any) => { + funcionarios.filter((f: Funcionario) => { // Verificar se o funcionário já está em algum time ativo - const jaNaEquipe = timeParaMembros?.membros?.some((m: any) => m.funcionario?._id === f._id); + const jaNaEquipe = timeParaMembros?.membros?.some((m: MembroTime) => m.funcionario?._id === f._id); return !jaNaEquipe; }) ); @@ -60,7 +141,7 @@ formCor = coresDisponiveis[Math.floor(Math.random() * coresDisponiveis.length)]; } - function editarTime(time: any) { + function editarTime(time: TimeComDetalhes) { modoEdicao = true; timeEmEdicao = time; formNome = time.nome; @@ -91,26 +172,27 @@ id: timeEmEdicao._id, nome: formNome, descricao: formDescricao || undefined, - gestorId: formGestorId as any, + gestorId: formGestorId as Id<"usuarios">, cor: formCor, }); } else { await client.mutation(api.times.criar, { nome: formNome, descricao: formDescricao || undefined, - gestorId: formGestorId as any, + gestorId: formGestorId as Id<"usuarios">, cor: formCor, }); } cancelarEdicao(); - } catch (e: any) { - alert("Erro ao salvar: " + (e.message || e)); + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : String(e); + alert("Erro ao salvar: " + errorMessage); } finally { processando = false; } } - function confirmarExclusao(time: any) { + function confirmarExclusao(time: TimeComDetalhes) { timeParaExcluir = time; mostrarConfirmacaoExclusao = true; } @@ -123,17 +205,20 @@ await client.mutation(api.times.desativar, { id: timeParaExcluir._id }); mostrarConfirmacaoExclusao = false; timeParaExcluir = null; - } catch (e: any) { - alert("Erro ao excluir: " + (e.message || e)); + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : String(e); + alert("Erro ao excluir: " + errorMessage); } finally { processando = false; } } - async function abrirGerenciarMembros(time: any) { + async function abrirGerenciarMembros(time: TimeComDetalhes) { const detalhes = await client.query(api.times.obterPorId, { id: time._id }); - timeParaMembros = detalhes; - mostrarModalMembros = true; + if (detalhes) { + timeParaMembros = detalhes as TimeComMembros; + mostrarModalMembros = true; + } } async function adicionarMembro(funcionarioId: string) { @@ -143,14 +228,17 @@ try { await client.mutation(api.times.adicionarMembro, { timeId: timeParaMembros._id, - funcionarioId: funcionarioId as any, + funcionarioId: funcionarioId as Id<"funcionarios">, }); // Recarregar detalhes do time const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id }); - timeParaMembros = detalhes; - } catch (e: any) { - alert("Erro: " + (e.message || e)); + if (detalhes) { + timeParaMembros = detalhes as TimeComMembros; + } + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : String(e); + alert("Erro: " + errorMessage); } finally { processando = false; } @@ -161,13 +249,18 @@ processando = true; try { - await client.mutation(api.times.removerMembro, { membroId: membroId as any }); + await client.mutation(api.times.removerMembro, { membroId: membroId as Id<"timesMembros"> }); // Recarregar detalhes do time - const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id }); - timeParaMembros = detalhes; - } catch (e: any) { - alert("Erro: " + (e.message || e)); + if (timeParaMembros) { + const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id }); + if (detalhes) { + timeParaMembros = detalhes as TimeComMembros; + } + } + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : String(e); + alert("Erro: " + errorMessage); } finally { processando = false; } @@ -179,7 +272,8 @@ } -
    + +
    +
    diff --git a/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte b/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte index b10bee7..c15ba71 100644 --- a/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte @@ -1,545 +1,1171 @@ -
    - -
    -
    -

    Gestão de Usuários

    -

    Gerenciar usuários do sistema

    -
    - - - - - Criar Usuário - -
    - - - {#if stats} -
    -
    -
    Total
    -
    {stats.total}
    + +
    + +
    +
    +
    + + + +
    +
    +

    Gestão de Usuários

    +

    Administre os usuários do sistema

    +
    -
    -
    Ativos
    -
    {stats.ativos}
    -
    -
    -
    Bloqueados
    -
    {stats.bloqueados}
    -
    -
    -
    Inativos
    -
    {stats.inativos}
    +
    - {/if} - -
    -
    -
    -
    - - + {#if !carregandoUsuarios && usuariosComProblemas.length > 0} +
    + + -
    - -
    - - -
    -
    -
    -
    - - -
    -
    -

    - Usuários ({usuariosFiltrados.length}) -

    - -
    - - - - - - - - - - - - - {#each usuariosFiltrados as usuario} - - - - - - - - - {:else} - - - - {/each} - -
    MatrículaNomeEmailFuncionárioStatusAções
    {usuario.matricula}{usuario.nome}{usuario.email || "-"} - {#if usuario.funcionario?._id} -
    - - - - Associado -
    - {:else} -
    - - - - Não associado -
    + +
    +

    Atenção: Usuários com Problemas Detectados

    +
    +

    + {usuariosComProblemas.length} usuário(s) possui(em) problemas que requerem atenção: +

    +
      + {#each usuariosComProblemas.slice(0, 3) as usuario} +
    • + {usuario.nome} ({usuario.matricula}) + {#if usuario.avisos && usuario.avisos.length > 0} + - {usuario.avisos[0].mensagem} {/if} -
    - - -
    - - - - {#if usuario.bloqueado} - - {:else} - - {/if} - - -
    -
    - Nenhum usuário encontrado -
    -
    -
    -
    -
    - - -{#if modalAberto} - + {/if} - {#if modalAcao === "reset"} -
    + + {#if mensagem} +
    + {#if mensagem.tipo === "success"} + + + {:else if mensagem.tipo === "error"} + + + + {:else} + + /> - Uma senha temporária será gerada automaticamente. -
    - {/if} + {/if} + {mensagem.texto} +
    + {/if} - + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    + +
    + Mostrando {usuariosFiltrados.length} de {usuarios.length} usuário(s) +
    +
    +
    + {/if} + + + {#if carregandoUsuarios} +
    + +

    Carregando usuários...

    +
    + {:else if erroUsuarios} +
    + - Cancelar - - + +
    +

    Erro ao carregar usuários

    +
    {erroUsuarios}
    +
    + Por favor, recarregue a página ou entre em contato com o suporte técnico se o problema persistir. +
    +
    +
    + {:else if usuarios.length === 0} +
    + - {#if processando} - + + +

    Nenhum usuário encontrado

    +

    + Cadastre um usuário para começar a gestão de acessos. +

    +
    + {:else} +
    +
    +

    Usuários ({usuarios.length})

    + +
    + + + + + + + + + + + + + + + + + + {#each usuariosFiltrados as usuario} + + + + + + + + + + + + + + {/each} + +
    MatrículaNomeEmailRole/PerfilSetorFuncionário VinculadoStatusPrimeiro AcessoÚltimo AcessoData de CriaçãoAções
    {usuario.matricula}{usuario.nome}{usuario.email} +
    + {#if usuario.role.erro} +
    + + + + {usuario.role.descricao} +
    + {#if usuario.avisos && usuario.avisos.length > 0} +
    + +
    + {/if} + {:else} +
    {usuario.role.nome}
    + {/if} +
    +
    {usuario.role.setor || "-"} + {#if usuario.funcionario} +
    +
    + + + + Associado +
    +
    {usuario.funcionario.nome}
    + {#if usuario.funcionario.matricula} +
    + Mat: {usuario.funcionario.matricula} +
    + {/if} +
    + {:else} +
    + + + + Não associado +
    + {/if} +
    + + + {#if usuario.primeiroAcesso} +
    Sim
    + {:else} +
    Não
    + {/if} +
    + {formatarData(usuario.ultimoAcesso)} + + {formatarData(usuario.criadoEm)} + + +
    +
    +
    +
    + {/if} +
    + + + {#if modalAssociarAberto && usuarioSelecionado} + - - - -
    -{/if} + {/if} - -{#if modalAssociarAberto && usuarioParaAssociar} -