Files
memegoat/frontend/src/app/(dashboard)/admin/users/page.tsx
Mathis HERRIOT 9b714716f6 feat(admin): add edit dialogs for users, contents, and categories
- Implemented edit functionality for users, contents, and categories, including modals for updating records.
- Enhanced table actions with edit buttons alongside delete.
- Improved user, content, and category fetching with `useCallback` to optimize re-renders.
- Added skeleton loaders and UI updates for better user experience.
2026-01-21 13:19:29 +01:00

177 lines
4.9 KiB
TypeScript

"use client";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Edit, Trash2 } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { UserService } from "@/services/user.service";
import type { User } from "@/types/user";
import { UserEditDialog } from "./user-edit-dialog";
export default function AdminUsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [totalCount, setTotalCount] = useState(0);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const fetchUsers = useCallback(() => {
setLoading(true);
UserService.getUsersAdmin()
.then((res) => {
setUsers(res.data);
setTotalCount(res.totalCount);
})
.catch((err) => {
console.error(err);
})
.finally(() => setLoading(false));
}, []);
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
const handleDelete = async (uuid: string) => {
if (
!confirm(
"Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.",
)
)
return;
try {
await UserService.removeUserAdmin(uuid);
setUsers(users.filter((u) => u.uuid !== uuid));
setTotalCount((prev) => prev - 1);
} catch (error) {
console.error(error);
}
};
const handleEdit = (user: User) => {
setSelectedUser(user);
setDialogOpen(true);
};
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">
Utilisateurs ({totalCount})
</h2>
</div>
<div className="rounded-md border bg-card">
<Table>
<TableHeader>
<TableRow>
<TableHead>Utilisateur</TableHead>
<TableHead>Email</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date d'inscription</TableHead>
<TableHead className="w-[100px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
<TableRow key={i}>
<TableCell>
<Skeleton className="h-4 w-[150px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[200px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[50px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[80px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
<TableCell>
<Skeleton className="h-8 w-8 rounded-full" />
</TableCell>
</TableRow>
))
) : users.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center h-24">
Aucun utilisateur trouvé.
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.uuid}>
<TableCell className="font-medium whitespace-nowrap">
{user.displayName || user.username}
<div className="text-xs text-muted-foreground">@{user.username}</div>
</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.role === "admin" ? "default" : "secondary"}>
{user.role}
</Badge>
</TableCell>
<TableCell>
<Badge
variant={
user.status === "active"
? "success"
: user.status === "suspended"
? "destructive"
: "secondary"
}
>
{user.status}
</Badge>
</TableCell>
<TableCell className="whitespace-nowrap">
{format(new Date(user.createdAt), "PPP", { locale: fr })}
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={() => handleEdit(user)}>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(user.uuid)}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<UserEditDialog
user={selectedUser}
open={dialogOpen}
onOpenChange={setDialogOpen}
onSuccess={fetchUsers}
/>
</div>
);
}