'use client'; import { useState, useEffect, useCallback, memo } from 'react'; import { useRouter } from 'next/navigation'; import { useNotification } from './NotificationProvider'; import { AVAILABLE_PAGES } from '@/lib/pages'; interface AdherentOption { id: string; type: 'situation' | 'prescripteur' | 'facturation' | 'forfait'; value: string; order: number; } interface OptionsByType { situation: AdherentOption[]; prescripteur: AdherentOption[]; facturation: AdherentOption[]; forfait: AdherentOption[]; } // Composant OptionCard mémorisé pour éviter les re-renders inutiles const OptionCard = memo(({ type, label, icon, options, loading, editingId, editingValue, newValue, onEdit, onSaveEdit, onCancelEdit, onDelete, onAdd, onNewValueChange, onEditingValueChange, }: { type: 'situation' | 'prescripteur' | 'facturation' | 'forfait'; label: string; icon: React.ReactNode; options: AdherentOption[]; loading: boolean; editingId: string | null; editingValue: string; newValue: string; onEdit: (option: AdherentOption) => void; onSaveEdit: (id: string, type: 'situation' | 'prescripteur' | 'facturation' | 'forfait') => void; onCancelEdit: () => void; onDelete: (id: string) => void; onAdd: (type: 'situation' | 'prescripteur' | 'facturation' | 'forfait') => void; onNewValueChange: (type: 'situation' | 'prescripteur' | 'facturation' | 'forfait', value: string) => void; onEditingValueChange: (value: string) => void; }) => { return (
{icon}

{label}

{options.length} option{options.length > 1 ? 's' : ''}
{/* Liste des options */}
{loading ? (
Chargement...
) : options.length === 0 ? (
Aucune option configurée
) : ( options.map((option) => (
{editingId === option.id ? ( <> { onEditingValueChange(e.target.value); }} className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" onKeyDown={(e) => { if (e.key === 'Enter') { onSaveEdit(option.id, type); } else if (e.key === 'Escape') { onCancelEdit(); } }} autoFocus /> ) : ( <> {option.value} )}
)) )}
{/* Formulaire d'ajout */}
{ onNewValueChange(type, e.target.value); }} placeholder={`Ajouter une nouvelle ${label.toLowerCase()}`} className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" onKeyDown={(e) => { if (e.key === 'Enter') { onAdd(type); } }} />
); }); OptionCard.displayName = 'OptionCard'; export default function ConfigurationContent() { const router = useRouter(); const { showNotification } = useNotification(); const [activeConfigSection, setActiveConfigSection] = useState<'adherents' | 'comptes' | 'roles' | null>('adherents'); const [options, setOptions] = useState({ situation: [], prescripteur: [], facturation: [], forfait: [], }); const [loading, setLoading] = useState(true); const [editingId, setEditingId] = useState(null); const [editingValue, setEditingValue] = useState(''); const [newValue, setNewValue] = useState>({ situation: '', prescripteur: '', facturation: '', forfait: '', }); const fetchOptions = useCallback(async () => { setLoading(true); try { const response = await fetch('/api/settings/adherent-options'); if (response.ok) { const data = await response.json(); setOptions({ situation: data.situation || [], prescripteur: data.prescripteur || [], facturation: data.facturation || [], forfait: data.forfait || [], }); } } catch (error) { console.error('Erreur lors du chargement des options:', error); showNotification('Erreur lors du chargement des options', 'error'); } finally { setLoading(false); } }, [showNotification]); useEffect(() => { fetchOptions(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleAdd = useCallback(async (type: 'situation' | 'prescripteur' | 'facturation' | 'forfait') => { const currentValue = newValue[type]?.trim() || ''; if (!currentValue) { showNotification('Veuillez entrer une valeur', 'error'); return; } try { const response = await fetch('/api/settings/adherent-options', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type, value: currentValue, }), }); if (response.ok) { setNewValue((prev) => ({ ...prev, [type]: '' })); await fetchOptions(); showNotification('Option ajoutée avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de l\'ajout', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de l\'ajout', 'error'); } }, [newValue, showNotification, fetchOptions]); const handleEdit = useCallback((option: AdherentOption) => { setEditingId(option.id); setEditingValue(option.value); }, []); const handleSaveEdit = useCallback(async (id: string, type: 'situation' | 'prescripteur' | 'facturation' | 'forfait') => { const value = editingValue.trim(); if (!value) { showNotification('Veuillez entrer une valeur', 'error'); return; } try { const response = await fetch(`/api/settings/adherent-options/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ value }), }); if (response.ok) { setEditingId(null); setEditingValue(''); await fetchOptions(); showNotification('Option modifiée avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la modification', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la modification', 'error'); } }, [editingValue, showNotification, fetchOptions]); const handleCancelEdit = useCallback(() => { setEditingId(null); setEditingValue(''); }, []); const handleDelete = useCallback(async (id: string) => { if (!confirm('Êtes-vous sûr de vouloir supprimer cette option ?')) { return; } try { const response = await fetch(`/api/settings/adherent-options/${id}`, { method: 'DELETE', }); if (response.ok) { await fetchOptions(); showNotification('Option supprimée avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la suppression', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la suppression', 'error'); } }, [showNotification, fetchOptions]); const handleNewValueChange = useCallback((type: 'situation' | 'prescripteur' | 'facturation' | 'forfait', value: string) => { setNewValue((prev) => ({ ...prev, [type]: value })); }, []); const handleEditingValueChange = useCallback((value: string) => { setEditingValue(value); }, []); // Composant pour la gestion des comptes const GestionComptesContent = () => { const [users, setUsers] = useState>([]); const [roles, setRoles] = useState>([]); const [loadingUsers, setLoadingUsers] = useState(true); const [selectedUser, setSelectedUser] = useState(null); const [showPasswordModal, setShowPasswordModal] = useState(false); const [newPassword, setNewPassword] = useState(null); useEffect(() => { fetchUsers(); fetchRoles(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const fetchRoles = async () => { try { const response = await fetch('/api/roles'); if (response.ok) { const data = await response.json(); setRoles(data); } } catch (error) { console.error('Erreur lors du chargement des rôles:', error); } }; const fetchUsers = async () => { setLoadingUsers(true); try { const response = await fetch('/api/users'); if (response.ok) { const data = await response.json(); setUsers(data); } } catch (error) { console.error('Erreur lors du chargement des utilisateurs:', error); showNotification('Erreur lors du chargement des utilisateurs', 'error'); } finally { setLoadingUsers(false); } }; const handleDeleteUser = async (userId: string) => { if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.')) { return; } try { const response = await fetch(`/api/users/${userId}`, { method: 'DELETE', }); if (response.ok) { await fetchUsers(); showNotification('Utilisateur supprimé avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la suppression', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la suppression', 'error'); } }; const handleResetPassword = async (userId: string) => { try { const response = await fetch(`/api/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'reset-password' }), }); if (response.ok) { const data = await response.json(); setSelectedUser(userId); setNewPassword(data.newPassword); setShowPasswordModal(true); showNotification('Mot de passe réinitialisé avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la réinitialisation', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la réinitialisation', 'error'); } }; const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString('fr-FR', { day: '2-digit', month: 'long', year: 'numeric', }); }; const getUserInitials = (name: string | null, email: string) => { if (name) { const names = name.split(' '); if (names.length >= 2) { return `${names[0].charAt(0)}${names[1].charAt(0)}`.toUpperCase(); } return name.charAt(0).toUpperCase(); } return email.charAt(0).toUpperCase(); }; return ( <>

Gestion des comptes

Gérez tous les comptes utilisateurs de la plateforme

{loadingUsers ? (
Chargement...
) : users.length === 0 ? (
Aucun utilisateur trouvé
) : (
{users.map((user) => (
{/* Avatar */}
{getUserInitials(user.name, user.email)}
{/* Informations */}

{user.name || 'Sans nom'}

{user.role && ( {user.role.name} )}

{user.email}

Inscrit le {formatDate(user.createdAt)}

{/* Actions */}
))}
)}
{/* Modal pour afficher le nouveau mot de passe */} {showPasswordModal && newPassword && (

Nouveau mot de passe généré

Le nouveau mot de passe pour {users.find(u => u.id === selectedUser)?.email} est :

{newPassword}

⚠️ Copiez ce mot de passe maintenant, il ne sera plus affiché après la fermeture de cette fenêtre.

)} ); }; // Composant pour la gestion des rôles const GestionRolesContent = () => { const [roles, setRoles] = useState; _count: { users: number; }; }>>([]); const [loading, setLoading] = useState(true); const [showRoleForm, setShowRoleForm] = useState(false); const [editingRole, setEditingRole] = useState(null); const [roleFormData, setRoleFormData] = useState({ name: '', description: '', pageRoutes: [] as string[], }); useEffect(() => { fetchRoles(); }, []); const fetchRoles = async () => { setLoading(true); try { const response = await fetch('/api/roles'); if (response.ok) { const data = await response.json(); setRoles(data); } } catch (error) { console.error('Erreur lors du chargement des rôles:', error); showNotification('Erreur lors du chargement des rôles', 'error'); } finally { setLoading(false); } }; const handleCreateRole = async () => { if (!roleFormData.name.trim()) { showNotification('Le nom du rôle est requis', 'error'); return; } try { // Créer les permissions pour les pages sélectionnées si elles n'existent pas const permissionIds: string[] = []; for (const route of roleFormData.pageRoutes) { const page = AVAILABLE_PAGES.find(p => p.route === route); if (page) { // Vérifier si la permission existe, sinon la créer const permResponse = await fetch('/api/permissions'); if (permResponse.ok) { const existingPerms = await permResponse.json(); let perm = existingPerms.find((p: any) => p.name === route); if (!perm) { // Créer la permission const createPermResponse = await fetch('/api/permissions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: route, description: page.description, }), }); if (createPermResponse.ok) { perm = await createPermResponse.json(); } } if (perm) { permissionIds.push(perm.id); } } } } const response = await fetch('/api/roles', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: roleFormData.name, description: roleFormData.description || null, permissionIds, }), }); if (response.ok) { setShowRoleForm(false); setRoleFormData({ name: '', description: '', pageRoutes: [] }); await fetchRoles(); showNotification('Rôle créé avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la création', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la création', 'error'); } }; const handleEditRole = (role: typeof roles[0]) => { setEditingRole(role.id); setRoleFormData({ name: role.name, description: role.description || '', pageRoutes: role.permissions.map(p => p.permission.name).filter(name => AVAILABLE_PAGES.some(page => page.route === name) ), }); setShowRoleForm(true); }; const handleUpdateRole = async () => { if (!editingRole || !roleFormData.name.trim()) { showNotification('Le nom du rôle est requis', 'error'); return; } try { // Créer les permissions pour les pages sélectionnées si elles n'existent pas const permissionIds: string[] = []; for (const route of roleFormData.pageRoutes) { const page = AVAILABLE_PAGES.find(p => p.route === route); if (page) { // Vérifier si la permission existe, sinon la créer const permResponse = await fetch('/api/permissions'); if (permResponse.ok) { const existingPerms = await permResponse.json(); let perm = existingPerms.find((p: any) => p.name === route); if (!perm) { // Créer la permission const createPermResponse = await fetch('/api/permissions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: route, description: page.description, }), }); if (createPermResponse.ok) { perm = await createPermResponse.json(); } } if (perm) { permissionIds.push(perm.id); } } } } const response = await fetch(`/api/roles/${editingRole}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: roleFormData.name, description: roleFormData.description || null, permissionIds, }), }); if (response.ok) { setShowRoleForm(false); setEditingRole(null); setRoleFormData({ name: '', description: '', pageRoutes: [] }); await fetchRoles(); showNotification('Rôle modifié avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la modification', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la modification', 'error'); } }; const handleDeleteRole = async (roleId: string) => { if (!confirm('Êtes-vous sûr de vouloir supprimer ce rôle ?')) { return; } try { const response = await fetch(`/api/roles/${roleId}`, { method: 'DELETE', }); if (response.ok) { await fetchRoles(); showNotification('Rôle supprimé avec succès', 'success'); } else { const error = await response.json(); showNotification(error.error || 'Erreur lors de la suppression', 'error'); } } catch (error) { console.error('Erreur:', error); showNotification('Erreur lors de la suppression', 'error'); } }; const handleCancelForm = () => { setShowRoleForm(false); setEditingRole(null); setRoleFormData({ name: '', description: '', pageRoutes: [] }); }; return ( <>

Gestion des rôles

Créez et gérez les rôles avec leurs permissions

{loading ? (
Chargement...
) : roles.length === 0 ? (
Aucun rôle créé. Cliquez sur "Nouveau rôle" pour commencer.
) : (
{roles.map((role) => (

{role.name}

{role._count.users} utilisateur{role._count.users > 1 ? 's' : ''}
{role.description && (

{role.description}

)}
{role.permissions.length === 0 ? ( Aucune page accessible ) : ( role.permissions .filter(rp => AVAILABLE_PAGES.some(page => page.route === rp.permission.name)) .map((rp) => { const page = AVAILABLE_PAGES.find(p => p.route === rp.permission.name); return ( {page?.label || rp.permission.name} ); }) )}
))}
)}
{/* Modal formulaire rôle */} {showRoleForm && (

{editingRole ? 'Modifier le rôle' : 'Nouveau rôle'}

{editingRole ? 'Modifiez les informations du rôle' : 'Créez un nouveau rôle et sélectionnez les pages accessibles'}

setRoleFormData({ ...roleFormData, name: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" placeholder="Ex: Administrateur, Éditeur..." />