Files
tracksteel_app/src/hooks/useUserManagement.tsx
T

509 lines
14 KiB
TypeScript

import { useState, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { useAuth } from './useAuth';
import { toast } from 'sonner';
import { useInterfaceResources } from './useInterfaceResources';
export interface UserProfile {
id: string;
email: string;
full_name: string | null;
profile_image_url: string | null;
function_id: string | null;
privilege_id: string | null;
status: 'pending' | 'active' | 'inactive' | 'rejected';
requested_at: string;
created_at: string;
updated_at: string;
functions?: {
name: string;
description: string;
};
privileges?: {
name: string;
description: string;
permissions: Record<string, unknown>;
};
}
export interface UserFunction {
id: string;
name: string;
description: string | null;
created_at: string;
updated_at: string;
}
export interface UserPrivilege {
id: string;
name: string;
description: string | null;
permissions: Record<string, unknown>;
created_at: string;
updated_at: string;
}
export interface UserDependency {
table_name: string;
dependency_type: string;
count: number;
description: string;
}
export function useUserManagement() {
const deleteUserAndLogtoAccount = async (userId: string, email: string) => {
try {
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
// 1. Deleta do Logto primeiro
const response = await fetch(`${supabaseUrl}/functions/v1/delete-logto-user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
if (!response.ok) {
console.error("Failed to delete from Logto", await response.text());
toast.error("Erro ao excluir do Logto. A exclusão no sistema continuará.");
}
// 2. Deleta do Supabase
return await deleteUser(userId, true);
} catch (err) {
console.error(err);
toast.error("Erro na comunicação com o servidor de autenticação");
return false;
}
};
const { user } = useAuth();
const [users, setUsers] = useState<UserProfile[]>([]);
const [pendingUsers, setPendingUsers] = useState<UserProfile[]>([]);
const [functions, setFunctions] = useState<UserFunction[]>([]);
const [privileges, setPrivileges] = useState<UserPrivilege[]>([]);
const [loading, setLoading] = useState(true);
const { updatePrivilegeResources } = useInterfaceResources();
// Fetch all users
const fetchUsers = async () => {
try {
const { data, error } = await supabase
.from('profiles')
.select(`
*,
functions (name, description),
privileges (name, description, permissions)
`)
.neq('status', 'pending')
.order('created_at', { ascending: false });
if (error) throw error;
setUsers(data || []);
} catch (error) {
console.error('Error fetching users:', error);
toast.error('Erro ao carregar usuários');
}
};
// Fetch pending users
const fetchPendingUsers = async () => {
try {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('status', 'pending')
.order('requested_at', { ascending: false });
if (error) throw error;
setPendingUsers(data || []);
} catch (error) {
console.error('Error fetching pending users:', error);
toast.error('Erro ao carregar solicitações pendentes');
}
};
// Fetch functions
const fetchFunctions = async () => {
try {
const { data, error } = await supabase
.from('functions')
.select('*')
.order('name');
if (error) throw error;
setFunctions(data || []);
} catch (error) {
console.error('Error fetching functions:', error);
toast.error('Erro ao carregar funções');
}
};
// Fetch privileges
const fetchPrivileges = async () => {
try {
const { data, error } = await supabase
.from('privileges')
.select('*')
.order('name');
if (error) throw error;
setPrivileges(data || []);
} catch (error) {
console.error('Error fetching privileges:', error);
toast.error('Erro ao carregar privilégios');
}
};
// Get user dependencies
const getUserDependencies = async (userId: string): Promise<UserDependency[]> => {
try {
const { data, error } = await supabase.rpc('get_user_dependencies', {
_user_id: userId
});
if (error) throw error;
return data || [];
} catch (error) {
console.error('Error getting user dependencies:', error);
return [];
}
};
// Replace user with UserDel
const replaceUserWithDeleted = async (userId: string): Promise<boolean> => {
try {
const { error } = await supabase.rpc('replace_user_with_deleted', {
_user_id: userId
});
if (error) throw error;
return true;
} catch (error) {
console.error('Error replacing user with deleted:', error);
toast.error('Erro ao substituir referências do usuário');
return false;
}
};
// Create new user (Admin only)
const createUser = async (data: {
email: string;
full_name?: string;
function_id?: string;
privilege_id?: string;
}) => {
try {
const { data: result, error } = await supabase.rpc('admin_create_user', {
user_email: data.email,
_caller_id: user?.id,
user_full_name: data.full_name || null,
user_function_id: data.function_id || null,
user_privilege_id: data.privilege_id || null
});
if (error) throw error;
toast.success('Usuário criado com sucesso! Senha padrão: 1234');
fetchUsers();
return result;
} catch (error: unknown) {
console.error('Error creating user:', error);
const err = error as Error;
if (err.message?.includes('User with this email already exists')) {
toast.error('Este e-mail já está em uso');
} else if (error.message?.includes('Only admins can create new users')) {
toast.error('Apenas administradores podem criar usuários');
} else {
toast.error('Erro ao criar usuário');
}
throw error;
}
};
// Check if user can be deleted
const canDeleteUser = async (userId: string): Promise<boolean> => {
try {
const { data, error } = await supabase.rpc('can_delete_user', {
_user_id: userId
});
if (error) throw error;
return data;
} catch (error) {
console.error('Error checking if user can be deleted:', error);
return false;
}
};
// Delete user (Admin only)
const deleteUser = async (userId: string, replaceReferences: boolean = false) => {
try {
// If replaceReferences is true, replace user references first
if (replaceReferences) {
const replaced = await replaceUserWithDeleted(userId);
if (!replaced) {
return false;
}
}
// First check if user can be deleted
const canDelete = await canDeleteUser(userId);
if (!canDelete) {
toast.error('Este usuário não pode ser excluído pois possui dados vinculados no sistema');
return false;
}
const { data, error } = await supabase.rpc('admin_delete_user', {
_caller_id: user?.id,
_user_id: userId
});
if (error) throw error;
toast.success('Usuário excluído com sucesso!');
fetchUsers();
fetchPendingUsers();
return true;
} catch (error: unknown) {
console.error('Error deleting user:', error);
const err = error as Error;
if (err.message?.includes('Only admins can delete users')) {
toast.error('Apenas administradores podem excluir usuários');
} else if (err.message?.includes('User cannot be deleted due to existing dependencies')) {
toast.error('Este usuário não pode ser excluído pois possui dados vinculados no sistema');
} else {
toast.error('Erro ao excluir usuário');
}
return false;
}
};
// Approve user
const approveUser = async (userId: string) => {
try {
const { error } = await supabase
.from('profiles')
.update({ status: 'active' })
.eq('id', userId);
if (error) throw error;
toast.success('Usuário aprovado com sucesso!');
fetchUsers();
fetchPendingUsers();
} catch (error) {
console.error('Error approving user:', error);
toast.error('Erro ao aprovar usuário');
}
};
// Reject user
const rejectUser = async (userId: string) => {
try {
const { error } = await supabase
.from('profiles')
.update({ status: 'rejected' })
.eq('id', userId);
if (error) throw error;
toast.success('Usuário rejeitado');
fetchUsers();
fetchPendingUsers();
} catch (error) {
console.error('Error rejecting user:', error);
toast.error('Erro ao rejeitar usuário');
}
};
// Update user
const updateUser = async (userId: string, data: Partial<UserProfile>) => {
try {
const { error } = await supabase
.from('profiles')
.update(data)
.eq('id', userId);
if (error) throw error;
toast.success('Usuário atualizado com sucesso!');
fetchUsers();
} catch (error) {
console.error('Error updating user:', error);
toast.error('Erro ao atualizar usuário');
}
};
const toggleUserStatus = async (userId: string, currentStatus: string) => {
const newStatus = currentStatus === 'active' ? 'inactive' : 'active';
await updateUser(userId, { status: newStatus as UserProfile['status'] });
};
// CRUD functions for Functions table
const createFunction = async (data: { name: string; description?: string }) => {
try {
const { error } = await supabase
.from('functions')
.insert(data);
if (error) throw error;
toast.success('Função criada com sucesso!');
fetchFunctions();
} catch (error) {
console.error('Error creating function:', error);
toast.error('Erro ao criar função');
}
};
const updateFunction = async (id: string, data: { name: string; description?: string }) => {
try {
const { error } = await supabase
.from('functions')
.update(data)
.eq('id', id);
if (error) throw error;
toast.success('Função atualizada com sucesso!');
fetchFunctions();
} catch (error) {
console.error('Error updating function:', error);
toast.error('Erro ao atualizar função');
}
};
const deleteFunction = async (id: string) => {
try {
const { error } = await supabase
.from('functions')
.delete()
.eq('id', id);
if (error) throw error;
toast.success('Função removida com sucesso!');
fetchFunctions();
} catch (error) {
console.error('Error deleting function:', error);
toast.error('Erro ao remover função');
}
};
// CRUD functions for Privileges table - Updated to handle interface resources
const createPrivilege = async (data: { name: string; description?: string; permissions?: Record<string, unknown> }, resourceKeys: string[] = []) => {
try {
const { data: newPrivilege, error } = await supabase
.from('privileges')
.insert({
...data,
permissions: data.permissions || {}
})
.select()
.single();
if (error) throw error;
// Update interface resources if provided
if (resourceKeys.length > 0 && newPrivilege) {
await updatePrivilegeResources(newPrivilege.id, resourceKeys);
}
toast.success('Privilégio criado com sucesso!');
fetchPrivileges();
return newPrivilege;
} catch (error) {
console.error('Error creating privilege:', error);
toast.error('Erro ao criar privilégio');
throw error;
}
};
const updatePrivilege = async (id: string, data: { name: string; description?: string; permissions?: Record<string, unknown> }) => {
try {
const { error } = await supabase
.from('privileges')
.update(data)
.eq('id', id);
if (error) throw error;
toast.success('Privilégio atualizado com sucesso!');
fetchPrivileges();
} catch (error) {
console.error('Error updating privilege:', error);
toast.error('Erro ao atualizar privilégio');
}
};
const deletePrivilege = async (id: string) => {
try {
const { error } = await supabase
.from('privileges')
.delete()
.eq('id', id);
if (error) throw error;
toast.success('Privilégio removido com sucesso!');
fetchPrivileges();
} catch (error) {
console.error('Error deleting privilege:', error);
toast.error('Erro ao remover privilégio');
}
};
useEffect(() => {
if (user) {
const loadData = async () => {
setLoading(true);
await Promise.all([
fetchUsers(),
fetchPendingUsers(),
fetchFunctions(),
fetchPrivileges()
]);
setLoading(false);
};
loadData();
}
}, [user]);
return {
users,
pendingUsers,
functions,
privileges,
loading,
createUser,
approveUser,
rejectUser,
updateUser,
toggleUserStatus,
deleteUser,
deleteUserAndLogtoAccount,
canDeleteUser,
getUserDependencies,
replaceUserWithDeleted,
createFunction,
updateFunction,
deleteFunction,
createPrivilege,
updatePrivilege,
deletePrivilege,
refetchData: () => {
fetchUsers();
fetchPendingUsers();
fetchFunctions();
fetchPrivileges();
}
};
}