// UsersView - Gestione utenti per admin
function UsersView() {
const { user: currentUser, isAdmin } = useAuth();
const toast = useToast();
const [users, setUsers] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [showAddModal, setShowAddModal] = React.useState(false);
const [editingUser, setEditingUser] = React.useState(null);
const [confirmModal, setConfirmModal] = React.useState(null); // { type, user }
const loadData = async () => {
setLoading(true);
// Clear existing data first
setUsers([]);
// Small delay to ensure backend has committed changes
await new Promise(resolve => setTimeout(resolve, 100));
try {
// Include inactive users in the list
const usersData = await ApiUtils.fetchUsers(true);
// Filter out the current admin user - they shouldn't see themselves in this list
const filteredUsers = usersData.filter(u => u.id !== currentUser.id);
// Force new array reference for React to detect changes
setUsers(filteredUsers.map(u => ({ ...u, _key: Date.now() })));
} catch (err) {
toast('Errore nel caricamento utenti: ' + err.message, 'error');
} finally {
setLoading(false);
}
};
React.useEffect(() => {
if (isAdmin) {
loadData();
}
}, [isAdmin]);
const getRoleBadge = (role) => {
const styles = {
admin: 'bg-red-100 text-red-700',
user: 'bg-blue-100 text-blue-700',
};
const labels = {
admin: 'Admin',
user: 'Utente',
};
return (
{labels[role] || role}
);
};
const handleDeactivate = (user) => {
if (user.id === currentUser.id) {
toast('Non puoi disattivare il tuo account', 'error');
return;
}
setConfirmModal({ type: 'deactivate', user });
};
const handleReactivate = (user) => {
setConfirmModal({ type: 'reactivate', user });
};
const executeUserAction = async () => {
if (!confirmModal) return;
const { type, user } = confirmModal;
setConfirmModal({ ...confirmModal, loading: true });
try {
if (type === 'deactivate') {
await ApiUtils.deleteUser(user.id);
toast('Utente disattivato con successo', 'success');
} else if (type === 'reactivate') {
await ApiUtils.updateUser(user.id, { is_active: true });
toast('Utente riattivato con successo', 'success');
}
setConfirmModal(null);
loadData();
} catch (err) {
const errorMsg = type === 'deactivate'
? 'Errore nella disattivazione: '
: 'Errore nella riattivazione: ';
toast(errorMsg + err.message, 'error');
setConfirmModal(null);
}
};
if (!isAdmin) {
return (
Non hai i permessi per visualizzare questa pagina.
);
}
return (
{/* Header */}
Gestione Utenti
Gestisci tutti gli utenti del sistema
{/* Users Table */}
{loading ? (
) : users.length === 0 ? (
) : (
| Utente |
Ruolo |
Stato |
Ultimo Accesso |
Azioni |
{users.map(user => (
{user.avatar_path ? (
})
) : (
{user.full_name.charAt(0).toUpperCase()}
)}
{user.full_name}
{user.email}
|
{getRoleBadge(user.role)}
|
{user.is_active ? (
Attivo
) : (
Inattivo
)}
|
{user.last_login_at
? new Date(user.last_login_at).toLocaleDateString('it-IT', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
: 'Mai'
}
|
{user.id !== currentUser.id && user.is_active && (
)}
{user.id !== currentUser.id && !user.is_active && (
)}
|
))}
)}
{/* Add User Modal */}
{showAddModal && (
setShowAddModal(false)}
onSuccess={() => {
setShowAddModal(false);
loadData();
toast('Utente creato con successo', 'success');
}}
/>
)}
{/* Edit User Modal */}
{editingUser && (
setEditingUser(null)}
onSuccess={() => {
setEditingUser(null);
loadData();
toast('Utente aggiornato con successo', 'success');
}}
/>
)}
{/* Confirmation Modal */}
{confirmModal && (
setConfirmModal(null)}
/>
)}
);
}
// Add User Modal Component
function AddUserModal({ onClose, onSuccess }) {
const [formData, setFormData] = React.useState({
email: '',
password: '',
full_name: '',
role: 'user',
// Referenti CRUD permissions
can_create_referenti: false,
can_read_referenti: false,
can_update_referenti: false,
can_delete_referenti: false,
// Projects CRUD permissions
can_create_projects: false,
can_read_projects: false,
can_update_projects: false,
can_delete_projects: false,
});
const [avatarFile, setAvatarFile] = React.useState(null);
const [avatarPreview, setAvatarPreview] = React.useState(null);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState('');
const [showPassword, setShowPassword] = React.useState(true);
const fileInputRef = React.useRef(null);
const handleAvatarChange = (e) => {
const file = e.target.files[0];
if (file) {
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
setError('Tipo file non supportato. Usa JPG, PNG, GIF o WEBP.');
return;
}
// Validate file size (5MB max)
if (file.size > 5 * 1024 * 1024) {
setError('File troppo grande. Massimo 5MB.');
return;
}
setAvatarFile(file);
setAvatarPreview(URL.createObjectURL(file));
setError('');
}
};
const handleRemoveAvatar = () => {
setAvatarFile(null);
if (avatarPreview) {
URL.revokeObjectURL(avatarPreview);
setAvatarPreview(null);
}
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
// Cleanup preview URL on unmount
React.useEffect(() => {
return () => {
if (avatarPreview) {
URL.revokeObjectURL(avatarPreview);
}
};
}, [avatarPreview]);
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setLoading(true);
try {
// Create user first
const newUser = await ApiUtils.createUser(formData);
// Upload avatar if selected
if (avatarFile && newUser.id) {
try {
await ApiUsers.uploadAvatar(newUser.id, avatarFile);
} catch (avatarErr) {
console.warn('Avatar upload failed:', avatarErr.message);
// Continue anyway - user was created successfully
}
}
onSuccess();
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
);
}
// Edit User Modal Component
function EditUserModal({ user, currentUserId, onClose, onSuccess }) {
const [formData, setFormData] = React.useState({
email: user.email,
full_name: user.full_name,
role: user.role,
password: '',
is_active: user.is_active,
// Referenti CRUD permissions
can_create_referenti: user.can_create_referenti || false,
can_read_referenti: user.can_read_referenti || false,
can_update_referenti: user.can_update_referenti || false,
can_delete_referenti: user.can_delete_referenti || false,
// Projects CRUD permissions
can_create_projects: user.can_create_projects || false,
can_read_projects: user.can_read_projects || false,
can_update_projects: user.can_update_projects || false,
can_delete_projects: user.can_delete_projects || false,
});
const [avatarFile, setAvatarFile] = React.useState(null);
const [avatarPreview, setAvatarPreview] = React.useState(
user.avatar_path ? ApiUsers.getAvatarUrl(user.avatar_path) : null
);
const [avatarDeleted, setAvatarDeleted] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState('');
const [showPassword, setShowPassword] = React.useState(true);
const fileInputRef = React.useRef(null);
const handleAvatarChange = (e) => {
const file = e.target.files[0];
if (file) {
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
setError('Tipo file non supportato. Usa JPG, PNG, GIF o WEBP.');
return;
}
// Validate file size (5MB max)
if (file.size > 5 * 1024 * 1024) {
setError('File troppo grande. Massimo 5MB.');
return;
}
setAvatarFile(file);
setAvatarPreview(URL.createObjectURL(file));
setAvatarDeleted(false);
setError('');
}
};
const handleRemoveAvatar = () => {
setAvatarFile(null);
if (avatarPreview && !avatarPreview.startsWith('/api/')) {
URL.revokeObjectURL(avatarPreview);
}
setAvatarPreview(null);
setAvatarDeleted(true);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
// Cleanup preview URL on unmount
React.useEffect(() => {
return () => {
if (avatarPreview && !avatarPreview.startsWith('/api/')) {
URL.revokeObjectURL(avatarPreview);
}
};
}, [avatarPreview]);
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setLoading(true);
try {
// Only include fields that have actually changed
const updateData = {};
if (formData.email !== user.email) {
updateData.email = formData.email;
}
if (formData.full_name !== user.full_name) {
updateData.full_name = formData.full_name;
}
if (formData.role !== user.role) {
updateData.role = formData.role;
}
// Referenti CRUD permissions
if (formData.can_create_referenti !== (user.can_create_referenti || false)) {
updateData.can_create_referenti = formData.can_create_referenti;
}
if (formData.can_read_referenti !== (user.can_read_referenti || false)) {
updateData.can_read_referenti = formData.can_read_referenti;
}
if (formData.can_update_referenti !== (user.can_update_referenti || false)) {
updateData.can_update_referenti = formData.can_update_referenti;
}
if (formData.can_delete_referenti !== (user.can_delete_referenti || false)) {
updateData.can_delete_referenti = formData.can_delete_referenti;
}
// Projects CRUD permissions
if (formData.can_create_projects !== (user.can_create_projects || false)) {
updateData.can_create_projects = formData.can_create_projects;
}
if (formData.can_read_projects !== (user.can_read_projects || false)) {
updateData.can_read_projects = formData.can_read_projects;
}
if (formData.can_update_projects !== (user.can_update_projects || false)) {
updateData.can_update_projects = formData.can_update_projects;
}
if (formData.can_delete_projects !== (user.can_delete_projects || false)) {
updateData.can_delete_projects = formData.can_delete_projects;
}
if (formData.is_active !== user.is_active) {
updateData.is_active = formData.is_active;
}
if (formData.password) {
updateData.password = formData.password;
}
// Update user data if there are changes
if (Object.keys(updateData).length > 0) {
await ApiUtils.updateUser(user.id, updateData);
}
// Handle avatar changes
if (avatarFile) {
// Upload new avatar
await ApiUsers.uploadAvatar(user.id, avatarFile);
} else if (avatarDeleted && user.avatar_path) {
// Delete existing avatar
await ApiUsers.deleteAvatar(user.id);
}
// Check if there's anything changed (including avatar)
if (Object.keys(updateData).length === 0 && !avatarFile && !avatarDeleted) {
onClose();
return;
}
onSuccess();
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const isSelf = user.id === currentUserId;
return (
);
}
window.UsersView = UsersView;