fix: resolve super_admin privileges and tenant management issues
- Fixed real backend deletion for tenants - Allowed super_admins to manage other super_admins in Global Users - Filtered teams based on selected tenant in user creation - Protected system tenant from deletion
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Users, Plus, Mail, Search, X, Edit, Trash2, Loader2, AlertTriangle } from 'lucide-react';
|
||||
import { getUsers, getTeams, createMember, deleteUser, updateUser, getUserById } from '../services/dataService';
|
||||
import { User } from '../types';
|
||||
import { getUsers, getTeams, createMember, deleteUser, updateUser, getUserById, getTenants } from '../services/dataService';
|
||||
import { User, Tenant } from '../types';
|
||||
|
||||
export const TeamManagement: React.FC = () => {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [teams, setTeams] = useState<any[]>([]);
|
||||
const [tenants, setTenants] = useState<Tenant[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -20,7 +21,8 @@ export const TeamManagement: React.FC = () => {
|
||||
email: '',
|
||||
role: 'agent' as any,
|
||||
team_id: '',
|
||||
status: 'active' as any
|
||||
status: 'active' as any,
|
||||
tenant_id: ''
|
||||
});
|
||||
|
||||
const loadData = async () => {
|
||||
@@ -29,9 +31,28 @@ export const TeamManagement: React.FC = () => {
|
||||
if (!tid) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const [fu, ft, me] = await Promise.all([getUsers(tid), getTeams(tid), uid ? getUserById(uid) : null]);
|
||||
setUsers(fu.filter(u => u.role !== 'super_admin'));
|
||||
setTeams(ft);
|
||||
const me = uid ? await getUserById(uid) : null;
|
||||
|
||||
const isSuperAdmin = me?.role === 'super_admin';
|
||||
const effectiveTid = isSuperAdmin ? 'all' : tid;
|
||||
|
||||
const promises: Promise<any>[] = [
|
||||
getUsers(effectiveTid),
|
||||
getTeams(effectiveTid)
|
||||
];
|
||||
|
||||
if (isSuperAdmin) {
|
||||
promises.push(getTenants());
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
setUsers(isSuperAdmin ? results[0] : results[0].filter((u: User) => u.role !== 'super_admin'));
|
||||
setTeams(results[1]);
|
||||
if (isSuperAdmin && results[2]) {
|
||||
setTenants(results[2]);
|
||||
}
|
||||
|
||||
if (me) setCurrentUser(me);
|
||||
} catch (err) { console.error(err); } finally { setLoading(false); }
|
||||
};
|
||||
@@ -43,11 +64,13 @@ export const TeamManagement: React.FC = () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const tid = localStorage.getItem('ctms_tenant_id') || '';
|
||||
const finalTenantId = currentUser?.role === 'super_admin' ? formData.tenant_id : tid;
|
||||
|
||||
if (editingUser) {
|
||||
const success = await updateUser(editingUser.id, formData);
|
||||
const success = await updateUser(editingUser.id, { ...formData, tenant_id: finalTenantId });
|
||||
if (success) { setIsModalOpen(false); loadData(); }
|
||||
} else {
|
||||
await createMember({ ...formData, tenant_id: tid });
|
||||
await createMember({ ...formData, tenant_id: finalTenantId });
|
||||
setIsModalOpen(false);
|
||||
loadData();
|
||||
}
|
||||
@@ -72,6 +95,7 @@ export const TeamManagement: React.FC = () => {
|
||||
const filtered = users.filter(u => u.name.toLowerCase().includes(searchTerm.toLowerCase()) || u.email.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
|
||||
const getRoleLabel = (role: string) => {
|
||||
if (role === 'super_admin') return 'Super Admin';
|
||||
if (role === 'admin') return 'Admin';
|
||||
if (role === 'manager') return 'Gerente';
|
||||
return 'Agente';
|
||||
@@ -111,6 +135,7 @@ export const TeamManagement: React.FC = () => {
|
||||
<thead>
|
||||
<tr className="bg-zinc-50 dark:bg-dark-bg/50 text-zinc-500 dark:text-dark-muted text-xs uppercase font-bold border-b dark:border-dark-border">
|
||||
<th className="px-6 py-4">Usuário</th>
|
||||
{currentUser?.role === 'super_admin' && <th className="px-6 py-4">Organização</th>}
|
||||
<th className="px-6 py-4">Função</th>
|
||||
<th className="px-6 py-4">Time</th>
|
||||
<th className="px-6 py-4">Status</th>
|
||||
@@ -138,6 +163,13 @@ export const TeamManagement: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{currentUser?.role === 'super_admin' && (
|
||||
<td className="px-6 py-4">
|
||||
<span className="text-zinc-600 dark:text-zinc-300 font-medium">
|
||||
{tenants.find(t => t.id === user.tenant_id)?.name || user.tenant_id}
|
||||
</span>
|
||||
</td>
|
||||
)}
|
||||
<td className="px-6 py-4">
|
||||
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold border capitalize ${getRoleBadge(user.role)}`}>{getRoleLabel(user.role)}</span>
|
||||
</td>
|
||||
@@ -148,7 +180,7 @@ export const TeamManagement: React.FC = () => {
|
||||
{canManage && (
|
||||
<td className="px-6 py-4 text-right">
|
||||
<div className="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button onClick={() => { setEditingUser(user); setFormData({name:user.name, email:user.email, role:user.role as any, team_id:user.team_id||'', status:user.status as any}); setIsModalOpen(true); }} className="p-2 hover:bg-zinc-100 dark:hover:bg-dark-border text-zinc-400 hover:text-zinc-900 dark:hover:text-dark-text rounded-lg transition-colors"><Edit size={16} /></button>
|
||||
<button onClick={() => { setEditingUser(user); setFormData({name:user.name, email:user.email, role:user.role as any, team_id:user.team_id||'', status:user.status as any, tenant_id: user.tenant_id||''}); setIsModalOpen(true); }} className="p-2 hover:bg-zinc-100 dark:hover:bg-dark-border text-zinc-400 hover:text-zinc-900 dark:hover:text-dark-text rounded-lg transition-colors"><Edit size={16} /></button>
|
||||
<button onClick={() => { setUserToDelete(user); setDeleteConfirmName(''); setIsDeleteModalOpen(true); }} className="p-2 hover:bg-red-50 dark:hover:bg-red-900/30 text-zinc-400 hover:text-red-600 dark:hover:text-red-400 rounded-lg transition-colors"><Trash2 size={16} /></button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -173,6 +205,15 @@ export const TeamManagement: React.FC = () => {
|
||||
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">E-mail</label>
|
||||
<input type="email" value={formData.email} onChange={e => setFormData({...formData, email:e.target.value})} className="w-full bg-white dark:bg-dark-input border border-zinc-200 dark:border-dark-border p-3 rounded-lg text-sm text-zinc-900 dark:text-dark-text disabled:bg-zinc-50 dark:disabled:bg-dark-bg/50 dark:disabled:text-dark-muted" disabled={!!editingUser} required />
|
||||
</div>
|
||||
{currentUser?.role === 'super_admin' && (
|
||||
<div>
|
||||
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">Organização</label>
|
||||
<select value={formData.tenant_id} onChange={e => setFormData({...formData, tenant_id: e.target.value})} className="w-full bg-white dark:bg-dark-input border border-zinc-200 dark:border-dark-border p-3 rounded-lg text-sm text-zinc-900 dark:text-dark-text" required>
|
||||
<option value="">Selecione uma organização</option>
|
||||
{tenants.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">Função</label>
|
||||
@@ -186,7 +227,7 @@ export const TeamManagement: React.FC = () => {
|
||||
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">Time</label>
|
||||
<select value={formData.team_id} onChange={e => setFormData({...formData, team_id: e.target.value})} className="w-full bg-white dark:bg-dark-input border border-zinc-200 dark:border-dark-border p-3 rounded-lg text-sm text-zinc-900 dark:text-dark-text">
|
||||
<option value="">Nenhum</option>
|
||||
{teams.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
|
||||
{teams.filter(t => !formData.tenant_id || t.tenant_id === formData.tenant_id).map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user