- Improved table layout by hiding specific columns on smaller screens. - Replaced action buttons with `DropdownMenu` for a cleaner and more accessible UI. - Updated skeleton loaders to align with the revised table structure.
195 lines
5.8 KiB
TypeScript
195 lines
5.8 KiB
TypeScript
"use client";
|
|
|
|
import { format } from "date-fns";
|
|
import { fr } from "date-fns/locale";
|
|
import { Edit, MoreHorizontal, Trash2 } from "lucide-react";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
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 className="hidden md:table-cell">Email</TableHead>
|
|
<TableHead>Rôle</TableHead>
|
|
<TableHead className="hidden sm:table-cell">Status</TableHead>
|
|
<TableHead className="hidden lg:table-cell">
|
|
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 className="hidden md:table-cell">
|
|
<Skeleton className="h-4 w-[200px]" />
|
|
</TableCell>
|
|
<TableCell>
|
|
<Skeleton className="h-4 w-[50px]" />
|
|
</TableCell>
|
|
<TableCell className="hidden sm:table-cell">
|
|
<Skeleton className="h-4 w-[80px]" />
|
|
</TableCell>
|
|
<TableCell className="hidden lg:table-cell">
|
|
<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 className="hidden md:table-cell">{user.email}</TableCell>
|
|
<TableCell>
|
|
<Badge variant={user.role === "admin" ? "default" : "secondary"}>
|
|
{user.role}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="hidden sm:table-cell">
|
|
<Badge
|
|
variant={
|
|
user.status === "active"
|
|
? "success"
|
|
: user.status === "suspended"
|
|
? "destructive"
|
|
: "secondary"
|
|
}
|
|
>
|
|
{user.status}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="hidden lg:table-cell whitespace-nowrap">
|
|
{format(new Date(user.createdAt), "PPP", { locale: fr })}
|
|
</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
<span className="sr-only">Actions</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => handleEdit(user)}>
|
|
<Edit className="mr-2 h-4 w-4" /> Modifier
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => handleDelete(user.uuid)}
|
|
className="text-destructive focus:text-destructive"
|
|
>
|
|
<Trash2 className="mr-2 h-4 w-4" /> Supprimer
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
<UserEditDialog
|
|
user={selectedUser}
|
|
open={dialogOpen}
|
|
onOpenChange={setDialogOpen}
|
|
onSuccess={fetchUsers}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|