Files
ComFi/components/UserManagementView.tsx
MMrp89 1a57ac7754 feat: Initialize ComFi project with Vite
Setup project structure, dependencies, and basic configuration for the ComFi application. Includes initial setup for Vite, React, TypeScript, Tailwind CSS, and essential development tools. Defines core types and provides a basic README for local development.
2026-02-09 20:28:37 -03:00

310 lines
15 KiB
TypeScript

import React, { useState } from 'react';
import { AppUser, ViewState } from '../types';
import {
Plus, Search, Shield, ShieldAlert, CheckCircle2,
XCircle, Edit2, Trash2, X, Save, User as UserIcon, Lock
} from 'lucide-react';
interface UserManagementViewProps {
users: AppUser[];
setUsers: (users: AppUser[]) => void;
availableModules: { id: ViewState; label: string }[];
currentUser: AppUser;
}
const ToggleSwitch: React.FC<{ checked: boolean, onChange: (val: boolean) => void, label: string }> = ({ checked, onChange, label }) => (
<div className="flex items-center justify-between py-3 border-b border-slate-50 last:border-0">
<span className="text-sm font-medium text-slate-700">{label}</span>
<button
onClick={() => onChange(!checked)}
className={`relative w-11 h-6 rounded-full transition-colors duration-200 ease-in-out focus:outline-none ${
checked ? 'bg-primary-500' : 'bg-slate-200'
}`}
>
<span
className={`inline-block w-4 h-4 transform bg-white rounded-full shadow transition-transform duration-200 ease-in-out mt-1 ml-1 ${
checked ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
</div>
);
export const UserManagementView: React.FC<UserManagementViewProps> = ({ users, setUsers, availableModules, currentUser }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingUser, setEditingUser] = useState<Partial<AppUser>>({
role: 'user',
active: true,
permissions: []
});
const handleSaveUser = () => {
if (!editingUser.name || !editingUser.email) return;
if (editingUser.id) {
// Editar existente
setUsers(users.map(u => u.id === editingUser.id ? { ...u, ...editingUser } as AppUser : u));
} else {
// Criar novo
const newUser: AppUser = {
...editingUser,
id: Math.random().toString(36).substr(2, 9),
avatar: `https://ui-avatars.com/api/?name=${editingUser.name}&background=random`,
permissions: editingUser.role === 'super_admin' ? availableModules.map(m => m.id) : (editingUser.permissions || [])
} as AppUser;
setUsers([...users, newUser]);
}
setIsModalOpen(false);
setEditingUser({ role: 'user', active: true, permissions: [] });
};
const handleDeleteUser = (id: string) => {
if (id === currentUser.id) {
alert("Você não pode excluir a si mesmo.");
return;
}
if (window.confirm("Tem certeza que deseja remover este usuário?")) {
setUsers(users.filter(u => u.id !== id));
}
};
const togglePermission = (moduleId: ViewState) => {
const currentPermissions = editingUser.permissions || [];
if (currentPermissions.includes(moduleId)) {
setEditingUser({ ...editingUser, permissions: currentPermissions.filter(p => p !== moduleId) });
} else {
setEditingUser({ ...editingUser, permissions: [...currentPermissions, moduleId] });
}
};
const openModal = (user?: AppUser) => {
if (user) {
setEditingUser(user);
} else {
setEditingUser({ role: 'user', active: true, permissions: [], name: '', email: '' });
}
setIsModalOpen(true);
};
const inputClass = "w-full p-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-primary-200 outline-none text-slate-800 text-sm";
const labelClass = "block text-xs font-bold text-slate-700 mb-1 uppercase tracking-wide";
return (
<div className="space-y-6 animate-fade-in">
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-800">Gerenciamento de Usuários</h1>
<p className="text-slate-500">Controle de acesso e permissões do sistema.</p>
</div>
<button
onClick={() => openModal()}
className="flex items-center gap-2 px-5 py-3 bg-primary-500 text-white rounded-xl shadow-lg shadow-primary-200/50 hover:bg-primary-600 font-bold transition-all"
>
<Plus size={20} /> Novo Usuário
</button>
</div>
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead className="bg-slate-50/50 border-b border-slate-100">
<tr>
<th className="p-6 text-xs font-bold uppercase text-slate-500">Usuário</th>
<th className="p-6 text-xs font-bold uppercase text-slate-500">Função</th>
<th className="p-6 text-xs font-bold uppercase text-slate-500">Status</th>
<th className="p-6 text-xs font-bold uppercase text-slate-500">Permissões</th>
<th className="p-6 text-right"></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{users.map(user => (
<tr key={user.id} className="hover:bg-slate-50 transition-colors">
<td className="p-6">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-slate-200 overflow-hidden">
<img src={user.avatar} alt={user.name} className="w-full h-full object-cover" />
</div>
<div>
<div className="font-bold text-slate-800">{user.name}</div>
<div className="text-xs text-slate-400">{user.email}</div>
</div>
</div>
</td>
<td className="p-6">
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold uppercase ${
user.role === 'super_admin' ? 'bg-indigo-50 text-indigo-600' : 'bg-slate-100 text-slate-600'
}`}>
{user.role === 'super_admin' ? <ShieldAlert size={14}/> : <UserIcon size={14}/>}
{user.role === 'super_admin' ? 'Super Admin' : 'Usuário'}
</span>
</td>
<td className="p-6">
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold uppercase ${
user.active ? 'bg-green-50 text-green-600' : 'bg-red-50 text-red-600'
}`}>
{user.active ? <CheckCircle2 size={14}/> : <XCircle size={14}/>}
{user.active ? 'Ativo' : 'Inativo'}
</span>
</td>
<td className="p-6">
{user.role === 'super_admin' ? (
<span className="text-xs text-indigo-500 font-bold">Acesso Total</span>
) : (
<div className="flex flex-wrap gap-1 max-w-xs">
{user.permissions.length === 0 && <span className="text-xs text-slate-400">Sem acesso</span>}
{user.permissions.slice(0, 3).map(p => (
<span key={p} className="px-2 py-0.5 bg-slate-100 border border-slate-200 rounded text-[10px] text-slate-500 capitalize">
{availableModules.find(m => m.id === p)?.label || p}
</span>
))}
{user.permissions.length > 3 && (
<span className="px-2 py-0.5 bg-slate-100 border border-slate-200 rounded text-[10px] text-slate-500">
+{user.permissions.length - 3}
</span>
)}
</div>
)}
</td>
<td className="p-6 text-right">
<div className="flex justify-end gap-2">
<button onClick={() => openModal(user)} className="p-2 text-slate-400 hover:text-primary-500 hover:bg-primary-50 rounded-lg transition-colors">
<Edit2 size={18} />
</button>
{user.id !== currentUser.id && (
<button onClick={() => handleDeleteUser(user.id)} className="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors">
<Trash2 size={18} />
</button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Modal User Edit/Create */}
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm" onClick={() => setIsModalOpen(false)}></div>
<div className="relative bg-white rounded-2xl shadow-xl w-full max-w-2xl overflow-hidden animate-scale-up flex flex-col max-h-[90vh]">
<div className="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50">
<h3 className="font-bold text-slate-800 text-lg flex items-center gap-2">
{editingUser.id ? 'Editar Usuário' : 'Novo Usuário'}
</h3>
<button onClick={() => setIsModalOpen(false)} className="text-slate-400 hover:text-slate-600"><X size={20}/></button>
</div>
<div className="p-8 overflow-y-auto flex-1">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div className="col-span-2 md:col-span-1">
<label className={labelClass}>Nome Completo</label>
<input
type="text"
className={inputClass}
value={editingUser.name || ''}
onChange={e => setEditingUser({...editingUser, name: e.target.value})}
placeholder="Ex: João Silva"
/>
</div>
<div className="col-span-2 md:col-span-1">
<label className={labelClass}>Email</label>
<input
type="email"
className={inputClass}
value={editingUser.email || ''}
onChange={e => setEditingUser({...editingUser, email: e.target.value})}
placeholder="joao@empresa.com"
/>
</div>
<div className="col-span-2 md:col-span-1">
<label className={labelClass}>Nível de Acesso</label>
<div className="flex gap-2">
<button
onClick={() => setEditingUser({...editingUser, role: 'user'})}
className={`flex-1 py-2 px-3 rounded-xl border text-sm font-medium transition-all flex items-center justify-center gap-2 ${
editingUser.role === 'user' ? 'bg-primary-50 border-primary-200 text-primary-700' : 'bg-white border-slate-200 text-slate-500'
}`}
>
<UserIcon size={16}/> Usuário
</button>
<button
onClick={() => setEditingUser({...editingUser, role: 'super_admin'})}
className={`flex-1 py-2 px-3 rounded-xl border text-sm font-medium transition-all flex items-center justify-center gap-2 ${
editingUser.role === 'super_admin' ? 'bg-indigo-50 border-indigo-200 text-indigo-700' : 'bg-white border-slate-200 text-slate-500'
}`}
>
<Shield size={16}/> Admin
</button>
</div>
</div>
<div className="col-span-2 md:col-span-1">
<label className={labelClass}>Status da Conta</label>
<div className="flex gap-2">
<button
onClick={() => setEditingUser({...editingUser, active: true})}
className={`flex-1 py-2 px-3 rounded-xl border text-sm font-medium transition-all flex items-center justify-center gap-2 ${
editingUser.active ? 'bg-green-50 border-green-200 text-green-700' : 'bg-white border-slate-200 text-slate-500'
}`}
>
<CheckCircle2 size={16}/> Ativo
</button>
<button
onClick={() => setEditingUser({...editingUser, active: false})}
className={`flex-1 py-2 px-3 rounded-xl border text-sm font-medium transition-all flex items-center justify-center gap-2 ${
!editingUser.active ? 'bg-red-50 border-red-200 text-red-700' : 'bg-white border-slate-200 text-slate-500'
}`}
>
<XCircle size={16}/> Bloqueado
</button>
</div>
</div>
</div>
{/* Permissions Area */}
<div className="bg-slate-50 rounded-2xl p-6 border border-slate-100">
<h4 className="font-bold text-slate-800 mb-4 flex items-center gap-2">
<Lock size={18} className="text-slate-400"/>
Permissões de Acesso
</h4>
{editingUser.role === 'super_admin' ? (
<div className="text-center py-6 text-indigo-600 bg-indigo-50 rounded-xl border border-indigo-100">
<ShieldAlert size={32} className="mx-auto mb-2"/>
<p className="font-bold">Acesso Irrestrito</p>
<p className="text-xs opacity-75">Super Admins têm acesso a todos os módulos.</p>
</div>
) : (
<div className="space-y-1">
{availableModules.map(module => (
<ToggleSwitch
key={module.id}
label={module.label}
checked={(editingUser.permissions || []).includes(module.id)}
onChange={() => togglePermission(module.id)}
/>
))}
</div>
)}
</div>
</div>
<div className="px-8 py-5 bg-slate-50 border-t border-slate-100 flex justify-end gap-3">
<button onClick={() => setIsModalOpen(false)} className="px-4 py-2 text-slate-600 font-medium hover:bg-slate-200 rounded-xl">Cancelar</button>
<button onClick={handleSaveUser} className="px-6 py-2 bg-primary-500 text-white font-bold rounded-xl hover:bg-primary-600 flex items-center gap-2 shadow-lg shadow-primary-200">
<Save size={18} /> Salvar Usuário
</button>
</div>
</div>
</div>
)}
</div>
);
};