feat: consolidacao matriz de acessos, modais avancados e ocultacao dinamica de menus

This commit is contained in:
2026-09-25 14:58:08 -03:00
parent 6f595d6839
commit 81ba4e8e3e
8 changed files with 1054 additions and 70 deletions
+233
View File
@@ -0,0 +1,233 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { GRUPOS_AVANCADOS_CONFIG, GrupoAvancadoConfig } from '@/types/grupoAvancadoTypes';
export interface UserGrupoAvancado {
userId: string;
email: string;
fullName: string;
functionName: string;
privilegeName: string;
profileImageUrl: string | null;
regras: Record<string, boolean>; // regraKey -> true/false
}
export function useGrupoAvancadoPermissions(grupoKey: string | null) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [users, setUsers] = useState<UserGrupoAvancado[]>([]);
const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
const config: GrupoAvancadoConfig | undefined = grupoKey
? GRUPOS_AVANCADOS_CONFIG[grupoKey]
: undefined;
const carregarDados = useCallback(async () => {
if (!config) return;
try {
setLoading(true);
// 1. Carregar profiles com cargo e privilégio
const { data: profiles, error: profError } = await supabase
.from('profiles')
.select(`
id,
email,
full_name,
profile_image_url,
privileges:privilege_id (
id,
name
),
user_functions:function_id (
id,
name
)
`)
.order('full_name', { ascending: true });
if (profError) throw profError;
// 2. Carregar permissões das regras deste grupo na tabela user_interface_permissions
const chavesRegras = config.regras.map((r) => r.key);
const { data: permsData, error: permsError } = await supabase
.from('user_interface_permissions')
.select('user_id, resource_key, permission')
.in('resource_key', chavesRegras);
if (permsError) {
console.warn('Aviso ao carregar permissões avançadas:', permsError);
}
// Indexar permissões por user_id -> resource_key -> ativo
const userPermsMap: Record<string, Record<string, boolean>> = {};
(permsData || []).forEach((p: any) => {
if (!userPermsMap[p.user_id]) userPermsMap[p.user_id] = {};
userPermsMap[p.user_id][p.resource_key] =
p.permission === 'can_create_update_delete' ||
p.permission === 'can_admin' ||
p.permission === 'can_create_only' ||
p.permission === 'can_view_only';
});
// Mapear colaboradores
const mapped: UserGrupoAvancado[] = (profiles || []).map((p: any) => {
const privName = p.privileges?.name || 'Padrão';
const isAdmin = privName.toLowerCase().includes('admin');
const userRules: Record<string, boolean> = {};
config.regras.forEach((r) => {
if (userPermsMap[p.id] && userPermsMap[p.id][r.key] !== undefined) {
userRules[r.key] = userPermsMap[p.id][r.key];
} else if (isAdmin) {
userRules[r.key] = true;
} else {
userRules[r.key] = Boolean(r.padraoAtivo);
}
});
return {
userId: p.id,
email: p.email || '',
fullName: p.full_name || p.email?.split('@')[0] || 'Usuário',
functionName: p.user_functions?.name || 'Sem Cargo',
privilegeName: privName,
profileImageUrl: p.profile_image_url,
regras: userRules,
};
});
setUsers(mapped);
if (!selectedUserId && mapped.length > 0) {
setSelectedUserId(mapped[0].userId);
}
} catch (err: any) {
console.error('Erro ao carregar permissões avançadas do grupo:', err);
toast.error('Erro ao carregar governança avançada.');
} finally {
setLoading(false);
}
}, [config, selectedUserId]);
useEffect(() => {
if (grupoKey) {
carregarDados();
}
}, [grupoKey]);
const selectedUser = useMemo(
() => users.find((u) => u.userId === selectedUserId) || null,
[users, selectedUserId]
);
// Toggle de uma regra específica para o usuário selecionado
const toggleRegra = (regraKey: string) => {
if (!selectedUserId) return;
setUsers((prev) =>
prev.map((u) => {
if (u.userId !== selectedUserId) return u;
return {
...u,
regras: {
...u.regras,
[regraKey]: !u.regras[regraKey],
},
};
})
);
};
// Aplica um template pré-definido para o usuário selecionado
const aplicarTemplate = (templateId: string) => {
if (!config || !selectedUserId) return;
const tpl = config.templates.find((t) => t.id === templateId);
if (!tpl) return;
const novasRegras: Record<string, boolean> = {};
config.regras.forEach((r) => {
novasRegras[r.key] = tpl.regrasAtivas.includes(r.key);
});
setUsers((prev) =>
prev.map((u) => {
if (u.userId !== selectedUserId) return u;
return {
...u,
regras: novasRegras,
};
})
);
toast.info(`Template "${tpl.nome}" aplicado. Clique em Salvar para gravar.`);
};
// Salvar alterações no banco de dados
const salvarConfiguracaoUsuario = async () => {
if (!config || !selectedUser) return;
try {
setSaving(true);
// Upsert para cada regra na tabela user_interface_permissions
const promises = config.regras.map(async (r) => {
const estaAtivo = Boolean(selectedUser.regras[r.key]);
const permission = estaAtivo ? 'can_create_update_delete' : 'no_access';
// Garantir que a chave exista em interface_resources para evitar erro de Foreign Key
await supabase
.from('interface_resources')
.upsert(
{
resource_key: r.key,
resource_name: r.nome,
is_submenu: false,
},
{ onConflict: 'resource_key' }
);
return supabase
.from('user_interface_permissions')
.upsert(
{
user_id: selectedUser.userId,
resource_key: r.key,
permission: permission as any,
updated_at: new Date().toISOString(),
},
{ onConflict: 'user_id,resource_key' }
);
});
const results = await Promise.all(promises);
const hasError = results.some((res) => res.error);
if (hasError) {
throw new Error('Falha ao persistir algumas regras no banco.');
}
toast.success(`Configurações avançadas salvas para ${selectedUser.fullName}!`);
} catch (err: any) {
console.error('Erro ao salvar regras avançadas:', err);
toast.error('Erro ao salvar alterações no banco.');
} finally {
setSaving(false);
}
};
return {
config,
loading,
saving,
users,
selectedUserId,
setSelectedUserId,
selectedUser,
toggleRegra,
aplicarTemplate,
salvarConfiguracaoUsuario,
recarregar: carregarDados,
};
}