'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { useAuth } from '../../../../contexts/AuthContext'; import ProtectedRoute from '../../../../components/auth/ProtectedRoute'; import { Card } from 'primereact/card'; import { Button } from 'primereact/button'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { InputText } from 'primereact/inputtext'; import { Dialog } from 'primereact/dialog'; import { Dropdown } from 'primereact/dropdown'; import { Tag } from 'primereact/tag'; import { ToggleButton } from 'primereact/togglebutton'; import { Password } from 'primereact/password'; import { Checkbox } from 'primereact/checkbox'; import { Calendar } from 'primereact/calendar'; import { Avatar } from 'primereact/avatar'; import { Badge } from 'primereact/badge'; import { Chip } from 'primereact/chip'; import { Chart } from 'primereact/chart'; import { TabView, TabPanel } from 'primereact/tabview'; import { ActionButtonGroup, ViewButton, EditButton, DeleteButton, ActionButton } from '../../../../components/ui/ActionButton'; interface Utilisateur { id: string; nom: string; prenom: string; email: string; telephone: string; role: 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER'; departement: string; statut: 'ACTIF' | 'INACTIF' | 'SUSPENDU' | 'CONGE'; actif: boolean; derniereConnexion: Date; dateCreation: Date; dateModification: Date; permissions: string[]; avatar?: string; adresse: string; dateNaissance?: Date; numeroEmploye: string; manager?: string; tentativesConnexion: number; derniereMiseAJourMotDePasse: Date; motDePasseExpire: boolean; deuxFacteursActive: boolean; sessionActive: boolean; heuresConnexion: number; dernierAction: string; preferences: { theme: 'light' | 'dark'; langue: string; notifications: boolean; timezone: string; }; } interface ActiviteUtilisateur { id: string; utilisateurId: string; action: string; module: string; timestamp: Date; details: string; adresseIP: string; navigateur: string; } const UtilisateursPage = () => { const [utilisateurs, setUtilisateurs] = useState([]); const [activites, setActivites] = useState([]); const [selectedUtilisateurs, setSelectedUtilisateurs] = useState([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [utilisateurDialog, setUtilisateurDialog] = useState(false); const [activiteDialog, setActiviteDialog] = useState(false); const [deleteDialog, setDeleteDialog] = useState(false); const [resetPasswordDialog, setResetPasswordDialog] = useState(false); const [activeIndex, setActiveIndex] = useState(0); const [utilisateur, setUtilisateur] = useState({ id: '', nom: '', prenom: '', email: '', telephone: '', role: 'USER', departement: '', statut: 'ACTIF', actif: true, derniereConnexion: new Date(), dateCreation: new Date(), dateModification: new Date(), permissions: [], adresse: '', numeroEmploye: '', tentativesConnexion: 0, derniereMiseAJourMotDePasse: new Date(), motDePasseExpire: false, deuxFacteursActive: false, sessionActive: false, heuresConnexion: 0, dernierAction: '', preferences: { theme: 'light', langue: 'fr', notifications: true, timezone: 'Europe/Paris' } }); const [submitted, setSubmitted] = useState(false); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const toast = useRef(null); const dt = useRef>(null); const roles = [ { label: 'Administrateur', value: 'ADMIN' }, { label: 'Manager', value: 'MANAGER' }, { label: 'Utilisateur', value: 'USER' }, { label: 'Lecture seule', value: 'VIEWER' } ]; const departements = [ { label: 'Direction', value: 'direction' }, { label: 'Commercial', value: 'commercial' }, { label: 'Technique', value: 'technique' }, { label: 'Administratif', value: 'administratif' }, { label: 'Chantier', value: 'chantier' }, { label: 'Maintenance', value: 'maintenance' } ]; const statuts = [ { label: 'Actif', value: 'ACTIF' }, { label: 'Inactif', value: 'INACTIF' }, { label: 'Suspendu', value: 'SUSPENDU' }, { label: 'Congé', value: 'CONGE' } ]; const availablePermissions = [ 'dashboard.read', 'clients.read', 'clients.write', 'clients.delete', 'chantiers.read', 'chantiers.write', 'chantiers.delete', 'devis.read', 'devis.write', 'devis.delete', 'factures.read', 'factures.write', 'factures.delete', 'stock.read', 'stock.write', 'stock.delete', 'planning.read', 'planning.write', 'rapports.read', 'rapports.write', 'administration.read', 'administration.write', 'utilisateurs.read', 'utilisateurs.write', 'utilisateurs.delete', 'backup.read', 'backup.write' ]; useEffect(() => { loadUtilisateurs(); }, []); const loadUtilisateurs = async () => { try { setLoading(true); // TODO: Remplacer par un appel API réel pour charger les utilisateurs depuis le backend // Exemple: const response = await fetch('/api/admin/users'); // const utilisateursData = await response.json(); const mockUtilisateurs: Utilisateur[] = []; // TODO: Remplacer par un appel API réel pour charger l'activité des utilisateurs // Exemple: const response = await fetch('/api/admin/user-activities'); // const activitesData = await response.json(); const mockActivites: ActiviteUtilisateur[] = []; setUtilisateurs(mockUtilisateurs); setActivites(mockActivites); } catch (error) { console.error('Erreur lors du chargement:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les utilisateurs', life: 3000 }); } finally { setLoading(false); } }; const openNew = () => { setUtilisateur({ id: '', nom: '', prenom: '', email: '', telephone: '', role: 'USER', departement: '', statut: 'ACTIF', actif: true, derniereConnexion: new Date(), dateCreation: new Date(), dateModification: new Date(), permissions: [], adresse: '', numeroEmploye: '', tentativesConnexion: 0, derniereMiseAJourMotDePasse: new Date(), motDePasseExpire: false, deuxFacteursActive: false, sessionActive: false, heuresConnexion: 0, dernierAction: '', preferences: { theme: 'light', langue: 'fr', notifications: true, timezone: 'Europe/Paris' } }); setPassword(''); setConfirmPassword(''); setSubmitted(false); setUtilisateurDialog(true); }; const editUtilisateur = (utilisateur: Utilisateur) => { setUtilisateur({ ...utilisateur }); setPassword(''); setConfirmPassword(''); setUtilisateurDialog(true); }; const confirmDelete = (utilisateur: Utilisateur) => { setUtilisateur(utilisateur); setDeleteDialog(true); }; const deleteUtilisateur = () => { const updatedUtilisateurs = utilisateurs.filter(u => u.id !== utilisateur.id); setUtilisateurs(updatedUtilisateurs); setDeleteDialog(false); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Utilisateur supprimé', life: 3000 }); }; const saveUtilisateur = () => { setSubmitted(true); if (utilisateur.nom.trim() && utilisateur.prenom.trim() && utilisateur.email.trim()) { if (!utilisateur.id && (!password || password !== confirmPassword)) { toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Mot de passe requis et doit correspondre', life: 3000 }); return; } let updatedUtilisateurs = [...utilisateurs]; if (utilisateur.id) { // Mise à jour const index = utilisateurs.findIndex(u => u.id === utilisateur.id); updatedUtilisateurs[index] = { ...utilisateur, dateModification: new Date() }; toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Utilisateur mis à jour', life: 3000 }); } else { // Création const newUtilisateur = { ...utilisateur, id: Date.now().toString(), numeroEmploye: `EMP${String(utilisateurs.length + 1).padStart(3, '0')}`, dateCreation: new Date(), dateModification: new Date() }; updatedUtilisateurs.push(newUtilisateur); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Utilisateur créé', life: 3000 }); } setUtilisateurs(updatedUtilisateurs); setUtilisateurDialog(false); } }; const resetPassword = (utilisateur: Utilisateur) => { setUtilisateur(utilisateur); setPassword(''); setConfirmPassword(''); setResetPasswordDialog(true); }; const confirmResetPassword = () => { if (password && password === confirmPassword) { const updatedUtilisateurs = utilisateurs.map(u => u.id === utilisateur.id ? { ...u, derniereMiseAJourMotDePasse: new Date(), motDePasseExpire: false, tentativesConnexion: 0 } : u ); setUtilisateurs(updatedUtilisateurs); setResetPasswordDialog(false); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Mot de passe réinitialisé', life: 3000 }); } else { toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Les mots de passe ne correspondent pas', life: 3000 }); } }; const suspendUser = (utilisateur: Utilisateur) => { const updatedUtilisateurs = utilisateurs.map(u => u.id === utilisateur.id ? { ...u, statut: u.statut === 'SUSPENDU' ? 'ACTIF' : 'SUSPENDU' } as Utilisateur : u ); setUtilisateurs(updatedUtilisateurs); toast.current?.show({ severity: 'info', summary: 'Statut modifié', detail: `Utilisateur ${utilisateur.statut === 'SUSPENDU' ? 'réactivé' : 'suspendu'}`, life: 3000 }); }; const exportCSV = () => { dt.current?.exportCSV(); }; const leftToolbarTemplate = () => { return (
); }; const rightToolbarTemplate = () => { return (
); }; const actionBodyTemplate = (rowData: Utilisateur) => { return ( editUtilisateur(rowData)} tooltip="Modifier" /> resetPassword(rowData)} tooltip="Réinitialiser mot de passe" /> suspendUser(rowData)} tooltip={rowData.statut === 'SUSPENDU' ? 'Réactiver' : 'Suspendre'} /> confirmDelete(rowData)} tooltip="Supprimer" /> ); }; const statusBodyTemplate = (rowData: Utilisateur) => { let severity: "success" | "warning" | "danger" | "info" = 'success'; let label: string = rowData.statut; switch (rowData.statut) { case 'ACTIF': severity = 'success'; label = 'Actif'; break; case 'INACTIF': severity = 'warning'; label = 'Inactif'; break; case 'SUSPENDU': severity = 'danger'; label = 'Suspendu'; break; case 'CONGE': severity = 'info'; label = 'Congé'; break; } return ; }; const roleBodyTemplate = (rowData: Utilisateur) => { const roleLabels = { 'ADMIN': 'Administrateur', 'MANAGER': 'Manager', 'USER': 'Utilisateur', 'VIEWER': 'Lecture seule' }; return roleLabels[rowData.role] || rowData.role; }; const sessionBodyTemplate = (rowData: Utilisateur) => { return (
{rowData.sessionActive ? 'En ligne' : 'Hors ligne'}
); }; const securityBodyTemplate = (rowData: Utilisateur) => { return (
{rowData.deuxFacteursActive && } {rowData.motDePasseExpire && } {rowData.tentativesConnexion > 0 && }
); }; const avatarBodyTemplate = (rowData: Utilisateur) => { return (
{rowData.prenom} {rowData.nom}
{rowData.numeroEmploye}
); }; const dernierActionBodyTemplate = (rowData: Utilisateur) => { const diffTime = Math.abs(new Date().getTime() - rowData.derniereConnexion.getTime()); const diffHours = Math.floor(diffTime / (1000 * 60 * 60)); const diffDays = Math.floor(diffHours / 24); const timeAgo = diffDays > 0 ? `Il y a ${diffDays}j` : `Il y a ${diffHours}h`; return (
{rowData.dernierAction}
{timeAgo}
); }; const header = (
Gestion des Utilisateurs
u.sessionActive).length} className="mr-2" /> utilisateurs en ligne
); // Statistiques pour le dashboard const totalUtilisateurs = utilisateurs.length; const utilisateursActifs = utilisateurs.filter(u => u.statut === 'ACTIF').length; const utilisateursEnLigne = utilisateurs.filter(u => u.sessionActive).length; const utilisateursAvec2FA = utilisateurs.filter(u => u.deuxFacteursActive).length; const rolesData = { labels: ['Admin', 'Manager', 'User', 'Viewer'], datasets: [ { data: [ utilisateurs.filter(u => u.role === 'ADMIN').length, utilisateurs.filter(u => u.role === 'MANAGER').length, utilisateurs.filter(u => u.role === 'USER').length, utilisateurs.filter(u => u.role === 'VIEWER').length ], backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'], hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0'] } ] }; const departementsData = { labels: departements.map(d => d.label), datasets: [ { label: 'Utilisateurs', data: departements.map(dept => utilisateurs.filter(u => u.departement === dept.value).length), backgroundColor: '#36A2EB', borderColor: '#36A2EB', borderWidth: 1 } ] }; return (
setActiveIndex(e.index)}> setSelectedUtilisateurs(e.value)} selectionMode="multiple" dataKey="id" paginator rows={10} rowsPerPageOptions={[5, 10, 25]} className="datatable-responsive" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} utilisateurs" globalFilter={globalFilter} emptyMessage="Aucun utilisateur trouvé." header={header} loading={loading} >
{/* Indicateurs principaux */}
{totalUtilisateurs}
Total Utilisateurs
{utilisateursActifs}
Utilisateurs Actifs
{utilisateursEnLigne}
En Ligne
{utilisateursAvec2FA}
Avec 2FA
{/* Graphiques */}
{/* Dialog utilisateur */}
} onHide={() => setUtilisateurDialog(false)} >
setUtilisateur({...utilisateur, prenom: e.target.value})} required className={submitted && !utilisateur.prenom ? 'p-invalid' : ''} /> {submitted && !utilisateur.prenom && Le prénom est requis.}
setUtilisateur({...utilisateur, nom: e.target.value})} required className={submitted && !utilisateur.nom ? 'p-invalid' : ''} /> {submitted && !utilisateur.nom && Le nom est requis.}
setUtilisateur({...utilisateur, email: e.target.value})} required className={submitted && !utilisateur.email ? 'p-invalid' : ''} /> {submitted && !utilisateur.email && L'email est requis.}
setUtilisateur({...utilisateur, telephone: e.target.value})} />
setUtilisateur({...utilisateur, role: e.value})} placeholder="Sélectionnez un rôle" />
setUtilisateur({...utilisateur, departement: e.value})} placeholder="Sélectionnez un département" />
setUtilisateur({...utilisateur, statut: e.value})} />
setUtilisateur({...utilisateur, adresse: e.target.value})} />
setUtilisateur({...utilisateur, dateNaissance: e.value || undefined})} showIcon />
setUtilisateur({...utilisateur, manager: e.target.value})} />
{!utilisateur.id && ( <>
setPassword(e.target.value)} required className={submitted && !password ? 'p-invalid' : ''} /> {submitted && !password && Le mot de passe est requis.}
setConfirmPassword(e.target.value)} required className={submitted && password !== confirmPassword ? 'p-invalid' : ''} /> {submitted && password !== confirmPassword && Les mots de passe ne correspondent pas.}
)}
{availablePermissions.map(permission => (
{ const newPermissions = e.checked ? [...utilisateur.permissions, permission] : utilisateur.permissions.filter(p => p !== permission); setUtilisateur({...utilisateur, permissions: newPermissions}); }} />
))}
setUtilisateur({...utilisateur, deuxFacteursActive: e.checked || false})} />
setUtilisateur({...utilisateur, motDePasseExpire: e.checked || false})} />
{/* Dialog réinitialisation mot de passe */}
} onHide={() => setResetPasswordDialog(false)} >

Réinitialiser le mot de passe pour {utilisateur.prenom} {utilisateur.nom}

setPassword(e.target.value)} />
setConfirmPassword(e.target.value)} />
{/* Dialog suppression */} {/* Dialog activité */} setActiviteDialog(false)} /> } onHide={() => setActiviteDialog(false)} > rowData.timestamp.toLocaleString()} sortable /> { const user = utilisateurs.find(u => u.id === rowData.utilisateurId); return user ? `${user.prenom} ${user.nom}` : 'Utilisateur inconnu'; }} /> ); }; const ProtectedUtilisateursPage = () => { return ( ); }; export default ProtectedUtilisateursPage;