refactor: enhance role management UI and integrate profile management features
- Introduced a modal for managing user profiles, allowing for the creation and editing of profiles with improved state management. - Updated the role filtering logic to enhance type safety and readability. - Refactored UI components for better user experience, including improved button states and loading indicators. - Removed outdated code related to permissions and streamlined the overall structure for maintainability.
This commit is contained in:
@@ -1,436 +1 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { registrarAtividade } from "./logsAtividades";
|
||||
import { api } from "./_generated/api";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
/**
|
||||
* Listar todos os perfis customizados
|
||||
*/
|
||||
export const listarPerfisCustomizados = query({
|
||||
args: {},
|
||||
// Tipo inferido automaticamente pelo Convex
|
||||
handler: async (ctx) => {
|
||||
const perfis = await ctx.db.query("perfisCustomizados").collect();
|
||||
|
||||
// Buscar role correspondente para cada perfil
|
||||
const perfisComDetalhes = await Promise.all(
|
||||
perfis.map(async (perfil) => {
|
||||
const role = await ctx.db.get(perfil.roleId);
|
||||
const criador = await ctx.db.get(perfil.criadoPor);
|
||||
|
||||
// Contar usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
...perfil,
|
||||
roleNome: role?.nome || "Desconhecido",
|
||||
criadorNome: criador?.nome || "Desconhecido",
|
||||
numeroUsuarios: usuarios.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return perfisComDetalhes;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter perfil com permissões detalhadas
|
||||
*/
|
||||
export const obterPerfilComPermissoes = query({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
perfil: v.any(), // Doc<"perfisCustomizados"> não pode ser validado diretamente
|
||||
role: v.any(), // Doc<"roles"> não pode ser validado diretamente
|
||||
permissoes: v.array(v.any()), // Doc<"permissoes">[] não pode ser validado diretamente
|
||||
menuPermissoes: v.array(v.any()), // Doc<"menuPermissoes">[] não pode ser validado diretamente
|
||||
usuarios: v.array(v.any()), // Doc<"usuarios">[] não pode ser validado diretamente
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const role = await ctx.db.get(perfil.roleId);
|
||||
if (!role) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Buscar permissões do role
|
||||
const rolePermissoes = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
const permissoes = await Promise.all(
|
||||
rolePermissoes.map(async (rp) => {
|
||||
return await ctx.db.get(rp.permissaoId);
|
||||
})
|
||||
);
|
||||
|
||||
// Buscar permissões de menu
|
||||
const menuPermissoes = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
// Buscar usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
perfil,
|
||||
role,
|
||||
permissoes: permissoes.filter((p) => p !== null),
|
||||
menuPermissoes,
|
||||
usuarios,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Criar perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const criarPerfilCustomizado = mutation({
|
||||
args: {
|
||||
nome: v.string(),
|
||||
descricao: v.string(),
|
||||
nivel: v.number(), // >= 3
|
||||
clonarDeRoleId: v.optional(v.id("roles")), // role para copiar permissões
|
||||
criadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
}),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Validar nível (deve ser >= 3)
|
||||
if (args.nivel < 3) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Perfis customizados devem ter nível >= 3",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se nome já existe
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
const nomeExiste = roles.some(
|
||||
(r) => r.nome.toLowerCase() === args.nome.toLowerCase()
|
||||
);
|
||||
if (nomeExiste) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Já existe um perfil com este nome",
|
||||
};
|
||||
}
|
||||
|
||||
// Criar role correspondente
|
||||
const roleId = await ctx.db.insert("roles", {
|
||||
nome: args.nome.toLowerCase().replace(/\s+/g, "_"),
|
||||
descricao: args.descricao,
|
||||
nivel: args.nivel,
|
||||
customizado: true,
|
||||
criadoPor: args.criadoPorId,
|
||||
editavel: true,
|
||||
});
|
||||
|
||||
// Copiar permissões se especificado
|
||||
if (args.clonarDeRoleId) {
|
||||
// Copiar permissões gerais
|
||||
const permissoesClonar = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!))
|
||||
.collect();
|
||||
|
||||
for (const perm of permissoesClonar) {
|
||||
await ctx.db.insert("rolePermissoes", {
|
||||
roleId,
|
||||
permissaoId: perm.permissaoId,
|
||||
});
|
||||
}
|
||||
|
||||
// Copiar permissões de menu
|
||||
const menuPermsClonar = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!))
|
||||
.collect();
|
||||
|
||||
for (const menuPerm of menuPermsClonar) {
|
||||
await ctx.db.insert("menuPermissoes", {
|
||||
roleId,
|
||||
menuPath: menuPerm.menuPath,
|
||||
podeAcessar: menuPerm.podeAcessar,
|
||||
podeConsultar: menuPerm.podeConsultar,
|
||||
podeGravar: menuPerm.podeGravar,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Criar perfil customizado
|
||||
const perfilId = await ctx.db.insert("perfisCustomizados", {
|
||||
nome: args.nome,
|
||||
descricao: args.descricao,
|
||||
nivel: args.nivel,
|
||||
roleId,
|
||||
criadoPor: args.criadoPorId,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.criadoPorId,
|
||||
"criar",
|
||||
"perfis",
|
||||
JSON.stringify({ perfilId, nome: args.nome, nivel: args.nivel }),
|
||||
perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const, perfilId };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Editar perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const editarPerfilCustomizado = mutation({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
nome: v.optional(v.string()),
|
||||
descricao: v.optional(v.string()),
|
||||
editadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return { sucesso: false as const, erro: "Perfil não encontrado" };
|
||||
}
|
||||
|
||||
// Atualizar perfil
|
||||
const updates: Partial<Doc<"perfisCustomizados">> & { atualizadoEm: number } = {
|
||||
atualizadoEm: Date.now(),
|
||||
};
|
||||
|
||||
if (args.nome !== undefined) updates.nome = args.nome;
|
||||
if (args.descricao !== undefined) updates.descricao = args.descricao;
|
||||
|
||||
await ctx.db.patch(args.perfilId, updates);
|
||||
|
||||
// Atualizar role correspondente se nome mudou
|
||||
if (args.nome !== undefined) {
|
||||
await ctx.db.patch(perfil.roleId, {
|
||||
nome: args.nome.toLowerCase().replace(/\s+/g, "_"),
|
||||
});
|
||||
}
|
||||
|
||||
if (args.descricao !== undefined) {
|
||||
await ctx.db.patch(perfil.roleId, {
|
||||
descricao: args.descricao,
|
||||
});
|
||||
}
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.editadoPorId,
|
||||
"editar",
|
||||
"perfis",
|
||||
JSON.stringify(updates),
|
||||
args.perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Excluir perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const excluirPerfilCustomizado = mutation({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
excluidoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return { sucesso: false as const, erro: "Perfil não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se existem usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
if (usuarios.length > 0) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Não é possível excluir. ${usuarios.length} usuário(s) ainda usa(m) este perfil.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Remover permissões associadas ao role
|
||||
const rolePermissoes = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
for (const rp of rolePermissoes) {
|
||||
await ctx.db.delete(rp._id);
|
||||
}
|
||||
|
||||
// Remover permissões de menu
|
||||
const menuPermissoes = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
for (const mp of menuPermissoes) {
|
||||
await ctx.db.delete(mp._id);
|
||||
}
|
||||
|
||||
// Excluir role
|
||||
await ctx.db.delete(perfil.roleId);
|
||||
|
||||
// Excluir perfil
|
||||
await ctx.db.delete(args.perfilId);
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.excluidoPorId,
|
||||
"excluir",
|
||||
"perfis",
|
||||
JSON.stringify({ perfilId: args.perfilId, nome: perfil.nome }),
|
||||
args.perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Clonar perfil existente
|
||||
*/
|
||||
export const clonarPerfil = mutation({
|
||||
args: {
|
||||
perfilOrigemId: v.id("perfisCustomizados"),
|
||||
novoNome: v.string(),
|
||||
novaDescricao: v.string(),
|
||||
criadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
}),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfilOrigem = await ctx.db.get(args.perfilOrigemId);
|
||||
if (!perfilOrigem) {
|
||||
return { sucesso: false as const, erro: "Perfil origem não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se nome já existe
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
const nomeExiste = roles.some(
|
||||
(r) => r.nome.toLowerCase() === args.novoNome.toLowerCase()
|
||||
);
|
||||
if (nomeExiste) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Já existe um perfil com este nome",
|
||||
};
|
||||
}
|
||||
|
||||
// Criar role correspondente
|
||||
const roleId = await ctx.db.insert("roles", {
|
||||
nome: args.novoNome.toLowerCase().replace(/\s+/g, "_"),
|
||||
descricao: args.novaDescricao,
|
||||
nivel: perfilOrigem.nivel,
|
||||
customizado: true,
|
||||
criadoPor: args.criadoPorId,
|
||||
editavel: true,
|
||||
});
|
||||
|
||||
// Copiar permissões gerais do perfil de origem
|
||||
const permissoesClonar = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId))
|
||||
.collect();
|
||||
for (const perm of permissoesClonar) {
|
||||
await ctx.db.insert("rolePermissoes", {
|
||||
roleId,
|
||||
permissaoId: perm.permissaoId,
|
||||
});
|
||||
}
|
||||
|
||||
// Copiar permissões de menu
|
||||
const menuPermsClonar = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId))
|
||||
.collect();
|
||||
for (const menuPerm of menuPermsClonar) {
|
||||
await ctx.db.insert("menuPermissoes", {
|
||||
roleId,
|
||||
menuPath: menuPerm.menuPath,
|
||||
podeAcessar: menuPerm.podeAcessar,
|
||||
podeConsultar: menuPerm.podeConsultar,
|
||||
podeGravar: menuPerm.podeGravar,
|
||||
});
|
||||
}
|
||||
|
||||
// Criar perfil customizado
|
||||
const perfilId = await ctx.db.insert("perfisCustomizados", {
|
||||
nome: args.novoNome,
|
||||
descricao: args.novaDescricao,
|
||||
nivel: perfilOrigem.nivel,
|
||||
roleId,
|
||||
criadoPor: args.criadoPorId,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.criadoPorId,
|
||||
"criar",
|
||||
"perfis",
|
||||
JSON.stringify({
|
||||
perfilId,
|
||||
nome: args.novoNome,
|
||||
nivel: perfilOrigem.nivel,
|
||||
}),
|
||||
perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const, perfilId };
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user