Boulangerie_Website/frontend/src/pages/AdminUsersPage.jsx
Louis Mazin d424689e12 big maj
2026-09-24 19:35:37 +02:00

420 lines
15 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { userApi } from '../api/users';
import LoadingCard from '@/components/LoadingCard';
import MainCard from '@/components/MainCard';
import { Button } from '@/components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table';
import { Input } from '@/components/ui/input';
import { CardContent } 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 } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { useAuth } from '../context/useAuth';
import { Users} from 'lucide-react';
const AdminUsersPage = () => {
const { user: currentUser } = 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 => String(user.id) !== String(userId)));
} catch (err) {
setError(err.message);
console.error(err);
}
};
const handleMakeAdmin = async (userId) => {
try {
await userApi.promoteUser(userId);
window.location.reload();
} catch (err) {
setError(err.message);
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 payload = { ...editForm };
if (!currentUser.is_superuser) delete payload.is_superuser;
await userApi.updateUser(userId, payload);
window.location.reload();
} catch (err) {
setError(err.message);
console.error(err);
}
};
if (loading) {
return (
<LoadingCard
icon={Users}
title="Gestion des utilisateurs"
message="Chargement des utilisateurs..."
/>
);
}
return (
<MainCard title="Gestion des utilisateurs" icon={Users}>
<CardContent className="overflow-y-auto">
{error && (
<Alert variant="destructive" className="mb-4">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="flex h-9 w-full min-w-0 items-center gap-2">
<Button
onClick={generateInviteLink}
size="sm"
className="shrink-0"
>
Inviter un utilisateur
</Button>
{inviteLink && (
<>
<div className="min-w-0 flex-1">
<Input
type="text"
value={inviteLink}
readOnly
className="h-9 w-full"
/>
</div>
<Button
onClick={copyToClipboard}
variant="outline"
size="sm"
className="h-9 shrink-0"
>
{isCopied ? 'Copié !' : 'Copier'}
</Button>
</>
)}
</div>
<div className="overflow-x-auto mb-6">
<Table className="w-full">
<TableHeader>
<TableRow>
<TableHead className="text-center">Nom</TableHead>
<TableHead className="text-center">Couleur</TableHead>
<TableHead className="text-center">Rôle</TableHead>
<TableHead className="text-center">Inscrit le</TableHead>
<TableHead className="text-center">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell className="text-center">
{user.first_name} {user.last_name}
</TableCell>
<TableCell>
<div className="flex items-center justify-center gap-2">
<div
className="h-4 w-4 rounded-full"
style={{
backgroundColor: user.color || '#0000ff'
}}
/>
<span>{user.color || 'Bleu'}</span>
</div>
</TableCell>
<TableCell className="text-center">
<Badge variant={user.is_admin ? 'default' : 'secondary'}>
{user.is_admin ? 'Administrateur' : 'Utilisateur'}
</Badge>
</TableCell>
<TableCell className="text-center">
{new Date(user.date_joined).toLocaleDateString('fr-FR')}
</TableCell>
<TableCell>
<div className="flex justify-center 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,
});
}
}}
>
<Button
variant="outline"
size="sm"
disabled={user.is_superuser && !currentUser.is_superuser}
onClick={() => {
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,
});
}}
>
Éditer
</Button>
<DialogContent>
<DialogHeader className="border-b">
<DialogTitle>Modifier l'utilisateur</DialogTitle>
</DialogHeader>
{error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
<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" disabled={String(user.id) === String(currentUser.id) || (user.is_superuser && !currentUser.is_superuser)}>
Supprimer
</Button>
</ConfirmDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</MainCard>
);
};
export default AdminUsersPage;