/** * User Management Page * * Manage organization users, roles, and invitations */ 'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { listUsers, updateUser, deleteUser, canInviteUser } from '@/lib/api'; import { createInvitation } from '@/lib/api/invitations'; import { useAuth } from '@/lib/context/auth-context'; import Link from 'next/link'; import ExportButton from '@/components/ExportButton'; export default function UsersManagementPage() { const router = useRouter(); const queryClient = useQueryClient(); const { user: currentUser } = useAuth(); const [showInviteModal, setShowInviteModal] = useState(false); const [openMenuId, setOpenMenuId] = useState(null); const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null); const [inviteForm, setInviteForm] = useState({ email: '', firstName: '', lastName: '', role: 'USER' as 'MANAGER' | 'USER' | 'VIEWER', }); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const { data: users, isLoading } = useQuery({ queryKey: ['users'], queryFn: () => listUsers(), }); // Check license availability const { data: licenseStatus } = useQuery({ queryKey: ['canInvite'], queryFn: () => canInviteUser(), }); const inviteMutation = useMutation({ mutationFn: (data: typeof inviteForm) => { return createInvitation({ email: data.email, firstName: data.firstName, lastName: data.lastName, role: data.role, }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); queryClient.invalidateQueries({ queryKey: ['canInvite'] }); setSuccess('Invitation envoyée avec succès ! L\'utilisateur recevra un email avec un lien d\'inscription.'); setShowInviteModal(false); setInviteForm({ email: '', firstName: '', lastName: '', role: 'USER', }); setTimeout(() => setSuccess(''), 5000); }, onError: (err: any) => { setError(err.response?.data?.message || 'Échec de l\'envoi de l\'invitation'); setTimeout(() => setError(''), 5000); }, }); const changeRoleMutation = useMutation({ mutationFn: ({ id, role }: { id: string; role: 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER' }) => { return updateUser(id, { role }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); setSuccess('Rôle mis à jour avec succès'); setTimeout(() => setSuccess(''), 3000); }, onError: (err: any) => { setError(err.response?.data?.message || 'Échec de la mise à jour du rôle'); setTimeout(() => setError(''), 5000); }, }); const toggleActiveMutation = useMutation({ mutationFn: ({ id, isActive }: { id: string; isActive: boolean }) => { return updateUser(id, { isActive: !isActive }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); queryClient.invalidateQueries({ queryKey: ['canInvite'] }); setSuccess('Statut de l\'utilisateur mis à jour avec succès'); setTimeout(() => setSuccess(''), 3000); }, onError: (err: any) => { setError(err.response?.data?.message || 'Échec de la mise à jour du statut'); setTimeout(() => setError(''), 5000); }, }); const deleteMutation = useMutation({ mutationFn: (id: string) => deleteUser(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); queryClient.invalidateQueries({ queryKey: ['canInvite'] }); setSuccess('Utilisateur supprimé avec succès'); setTimeout(() => setSuccess(''), 3000); }, onError: (err: any) => { setError(err.response?.data?.message || 'Échec de la suppression de l\'utilisateur'); setTimeout(() => setError(''), 5000); }, }); // Restrict access to ADMIN and MANAGER only useEffect(() => { if (currentUser && currentUser.role !== 'ADMIN' && currentUser.role !== 'MANAGER') { router.push('/dashboard'); } }, [currentUser, router]); // Don't render until we've checked permissions if (!currentUser || (currentUser.role !== 'ADMIN' && currentUser.role !== 'MANAGER')) { return (
); } const handleInvite = (e: React.FormEvent) => { e.preventDefault(); setError(''); inviteMutation.mutate(inviteForm); }; const handleRoleChange = (userId: string, newRole: string) => { changeRoleMutation.mutate({ id: userId, role: newRole as 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER' }); }; const handleToggleActive = (userId: string, isActive: boolean) => { if ( window.confirm(`Êtes-vous sûr de vouloir ${isActive ? 'désactiver' : 'activer'} cet utilisateur ?`) ) { toggleActiveMutation.mutate({ id: userId, isActive }); } }; const handleDelete = (userId: string) => { if ( window.confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.') ) { deleteMutation.mutate(userId); } }; const getRoleBadgeColor = (role: string) => { const colors: Record = { ADMIN: 'bg-red-100 text-red-800', MANAGER: 'bg-blue-100 text-blue-800', USER: 'bg-green-100 text-green-800', VIEWER: 'bg-gray-100 text-gray-800', }; return colors[role] || 'bg-gray-100 text-gray-800'; }; return (
{/* License Warning */} {licenseStatus && !licenseStatus.canInvite && (

Limite de licences atteinte

Votre organisation a utilisé toutes les licences disponibles ({licenseStatus.usedLicenses}/{licenseStatus.maxLicenses}). Mettez à niveau votre abonnement pour inviter plus d'utilisateurs.

Mettre à niveau l'abonnement
)} {/* License Usage Info */} {licenseStatus && licenseStatus.canInvite && licenseStatus.availableLicenses <= 2 && licenseStatus.maxLicenses !== -1 && (
{licenseStatus.availableLicenses} licence{licenseStatus.availableLicenses !== 1 ? 's' : ''} restante{licenseStatus.availableLicenses !== 1 ? 's' : ''} ({licenseStatus.usedLicenses}/{licenseStatus.maxLicenses} utilisées)
Gérer l'abonnement
)} {/* Header */}

Gestion des Utilisateurs

Gérez les membres de l'équipe et leurs permissions

{ const labels: Record = { ADMIN: 'Administrateur', MANAGER: 'Manager', USER: 'Utilisateur', VIEWER: 'Lecteur', }; return labels[v] || v; }}, { key: 'isActive', label: 'Statut', format: (v) => v ? 'Actif' : 'Inactif' }, { key: 'createdAt', label: 'Date de création', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, ]} /> {licenseStatus?.canInvite ? ( ) : ( + Mettre à niveau )}
{success && (
{success}
)} {error && (
{error}
)} {/* Users Table */}
{isLoading ? (
Chargement des utilisateurs...
) : users?.users && users.users.length > 0 ? (
{users.users.map(user => ( ))}
Utilisateur Email Rôle Statut Date de création Actions
{user.firstName[0]} {user.lastName[0]}
{user.firstName} {user.lastName}
{user.email}
{user.email}
{user.isActive ? 'Actif' : 'Inactif'} {new Date(user.createdAt).toLocaleDateString()}
) : (

Aucun utilisateur

Commencez par inviter un membre de l'équipe

{licenseStatus?.canInvite ? ( ) : ( + Upgrade to Invite )}
)}
{/* Actions Menu Modal */} {openMenuId && menuPosition && ( <>
{ setOpenMenuId(null); setMenuPosition(null); }} />
)} {/* Invite Modal */} {showInviteModal && (
setShowInviteModal(false)} />

Inviter un utilisateur

setInviteForm({ ...inviteForm, firstName: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" />
setInviteForm({ ...inviteForm, lastName: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" />
setInviteForm({ ...inviteForm, email: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" />
{currentUser?.role !== 'ADMIN' && (

Seuls les administrateurs peuvent attribuer le rôle ADMIN

)}
)}
); }