Boulangerie_Website/frontend/src/pages/AdminUsersPage.jsx
Louis Mazin a5b5c6def8 test
2026-09-19 22:29:29 +02:00

356 lines
14 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { userApi } from '../api/users';
import { Button } from '@/components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import ConfirmDialog from '@/components/ConfirmDialog';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { useAuth } from '../context/useAuth';
const AdminUsersPage = () => {
const { currentUser, logout } = useAuth();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [inviteLink, setInviteLink] = useState('');
const [isCopied, setIsCopied] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const [editForm, setEditForm] = useState({
first_name: '',
last_name: '',
email: '',
color: '#0000ff',
is_active: true,
is_staff: false,
is_superuser: false,
});
const generateInviteLink = async () => {
try {
setError('');
const invitation = await userApi.createInvitation();
const newInviteLink = `${window.location.origin}/register?invite=${invitation.token}`;
setInviteLink(newInviteLink);
setIsCopied(false);
} catch (error) {
setError('Erreur lors de la génération du lien d\'invitation');
console.error(error);
}
};
const copyToClipboard = () => {
if (inviteLink) {
navigator.clipboard.writeText(inviteLink).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
}).catch((error) => {
setError('Impossible de copier le lien.');
console.error(error);
});
}
};
useEffect(() => {
const loadUsers = async () => {
try {
setLoading(true);
const users = await userApi.getUsers();
setUsers(users);
} catch (requestError) {
setError('Erreur lors du chargement des utilisateurs');
console.error(requestError);
} finally {
setLoading(false);
}
};
loadUsers();
}, []);
const handleDeleteUser = async (userId) => {
try {
await userApi.deleteUser(userId);
setUsers(users.filter(user => user.id !== userId));
if(userId===currentUser.id){
logout();
}
} catch (err) {
setError('Erreur lors de la suppression de l\'utilisateur');
console.error(err);
}
};
const handleMakeAdmin = async (userId) => {
// Remplacer window.confirm par le composant ConfirmDialog
const confirmed = await new Promise((resolve) => {
resolve(true); // On résout directement pour l'exemple
});
if (!confirmed) {
return;
}
try {
await userApi.promoteUser(userId);
setUsers(users.map(user =>
user.id === userId ? { ...user, is_admin: true } : user
));
} catch (err) {
setError('Erreur lors de la promotion de l\'utilisateur');
console.error(err);
}
};
const handleEditUser = async (userId) => {
if (!/^#[0-9A-Fa-f]{6}$/.test(editForm.color)) {
setError('La couleur doit être au format hexadécimal, par exemple #2563eb.');
return;
}
try {
const updatedUser = await userApi.updateUser(userId, editForm);
const updatedUsers = users.map(user =>
user.id === userId ? updatedUser : user
);
setUsers(updatedUsers);
setEditingUser(null);
setEditForm({
first_name: '',
last_name: '',
email: '',
color: '#0000ff',
is_active: true,
is_staff: false,
is_superuser: false,
});
} catch (err) {
setError('Erreur lors de la mise à jour de l\'utilisateur');
console.error(err);
}
};
if (loading) {
return (
<Card className="page-container page-layout admin-page">
<CardHeader className="border-b">
<CardTitle>Gestion des comptes</CardTitle>
</CardHeader>
<CardContent>
<p>Chargement des utilisateurs...</p>
</CardContent>
</Card>
);
}
return (
<Card className="page-container page-layout admin-page flex h-full w-full">
<CardHeader className="border-b">
<CardTitle>Gestion des comptes</CardTitle>
</CardHeader>
<CardContent>
{error && (
<Alert variant="destructive" className="mb-4">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="overflow-x-auto mb-6">
<Table>
<TableHeader>
<TableRow>
<TableHead>Nom</TableHead>
<TableHead>Couleur</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Inscrit le</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.first_name} {user.last_name}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: user.color || '#0000ff' }}></div>
<span>{user.color || 'Bleu'}</span>
</div>
</TableCell>
<TableCell>
<Badge variant={user.is_admin ? "default" : "secondary"}>
{user.is_admin ? 'Administrateur' : 'Utilisateur'}
</Badge>
</TableCell>
<TableCell>{new Date(user.date_joined).toLocaleDateString('fr-FR')}</TableCell>
<TableCell>
<div className="flex gap-2">
<Dialog open={editingUser === user.id} onOpenChange={(open) => {
if (!open) {
setEditingUser(null);
} else {
setEditingUser(user.id);
setEditForm({
first_name: user.first_name || '',
last_name: user.last_name || '',
email: user.email || '',
color: user.color || '#0000ff',
is_active: user.is_active,
is_staff: user.is_staff || user.is_admin,
is_superuser: user.is_superuser,
});
}
}}>
<DialogTrigger asChild>
<Button variant="outline" size="sm" onClick={() => setEditingUser(user.id)}>
Éditer
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="border-b">
<DialogTitle>Modifier l'utilisateur</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor={`first-name-${user.id}`}>Prénom</Label>
<Input
id={`first-name-${user.id}`}
value={editForm.first_name}
onChange={(event) => setEditForm({ ...editForm, first_name: event.target.value })}
/>
</div>
<div className="grid gap-2">
<Label htmlFor={`last-name-${user.id}`}>Nom</Label>
<Input
id={`last-name-${user.id}`}
value={editForm.last_name}
onChange={(event) => setEditForm({ ...editForm, last_name: event.target.value })}
/>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor={`email-${user.id}`}>E-mail</Label>
<Input
id={`email-${user.id}`}
type="email"
value={editForm.email}
onChange={(event) => setEditForm({ ...editForm, email: event.target.value })}
/>
</div>
<div className="grid gap-2">
<Label htmlFor={`color-${user.id}`}>Couleur</Label>
<div className="flex items-center gap-3">
<Input
id={`color-${user.id}`}
type="color"
value={/^#[0-9A-Fa-f]{6}$/.test(editForm.color) ? editForm.color : '#000000'}
onChange={(event) => setEditForm({ ...editForm, color: event.target.value })}
className="h-10 w-14 cursor-pointer p-1"
/>
<Input
aria-label="Valeur hexadécimale de la couleur"
value={editForm.color}
pattern="^#[0-9A-Fa-f]{6}$"
onChange={(event) => setEditForm({ ...editForm, color: event.target.value })}
className="max-w-36 font-mono uppercase"
/>
</div>
</div>
<label className="flex items-center gap-3 text-sm">
<Checkbox
checked={editForm.is_active}
onCheckedChange={(checked) => setEditForm({ ...editForm, is_active: checked })}
/>
Compte actif
</label>
<label className="flex items-center gap-3 text-sm">
<Checkbox
checked={editForm.is_staff}
onCheckedChange={(checked) => setEditForm({
...editForm,
is_staff: checked,
is_superuser: checked ? editForm.is_superuser : false,
})}
/>
Administrateur
</label>
<p className="text-sm text-muted-foreground">Le mot de passe ne peut pas être modifié ici.</p>
</div>
<Button onClick={() => handleEditUser(user.id)}>
Sauvegarder
</Button>
</DialogContent>
</Dialog>
{!user.is_admin && (
<ConfirmDialog
title="Promouvoir l'utilisateur"
description="Êtes-vous sûr de vouloir promouvoir cet utilisateur ?"
onConfirm={() => handleMakeAdmin(user.id)}
>
<Button variant="outline" size="sm">
Promouvoir
</Button>
</ConfirmDialog>
)}
<ConfirmDialog
title="Supprimer l'utilisateur"
description="Êtes-vous sûr de vouloir supprimer cet utilisateur ?"
onConfirm={() => handleDeleteUser(user.id)}
>
<Button variant="destructive" size="sm">
Supprimer
</Button>
</ConfirmDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex flex-col gap-4">
{inviteLink ? (
<div className="flex flex-col gap-2">
<Button onClick={generateInviteLink}>
Inviter un utilisateur
</Button>
<div className="flex gap-2">
<Input
type="text"
value={inviteLink}
readOnly
className="flex-1"
/>
<Button
onClick={copyToClipboard}
variant="outline"
>
{isCopied ? 'Copié!' : 'Copier'}
</Button>
</div>
</div>
) : (
<Button onClick={generateInviteLink}>
Inviter un utilisateur
</Button>
)}
</div>
</CardContent>
</Card>
);
};
export default AdminUsersPage;