Files
Dashboard/components/collections/users-table.tsx
2025-10-05 16:10:35 +02:00

170 lines
5.2 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import { useCollection } from "@/hooks/useCollection";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { formatDate } from "@/lib/utils";
import { LibreChatUser, LibreChatBalance } from "@/lib/types";
export function UsersTable() {
const [page, setPage] = useState(1);
const limit = 20;
// Charger les utilisateurs
const {
data: users = [],
total = 0,
loading: usersLoading,
} = useCollection<LibreChatUser>("users", {
page,
limit,
});
// Charger tous les balances pour associer les crédits
const { data: balances = [] } = useCollection<LibreChatBalance>("balances", {
limit: 1000, // Charger tous les balances
});
// Créer une map des crédits par utilisateur
const creditsMap = useMemo(() => {
const map = new Map<string, number>();
balances.forEach((balance) => {
map.set(balance.user, balance.tokenCredits || 0);
});
return map;
}, [balances]);
const totalPages = Math.ceil(total / limit);
const handlePrevPage = () => {
setPage((prev) => Math.max(1, prev - 1));
};
const handleNextPage = () => {
setPage((prev) => Math.min(totalPages, prev + 1));
};
if (usersLoading) {
return (
<Card>
<CardHeader>
<CardTitle>Liste des utilisateurs</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="h-16 bg-muted animate-pulse rounded" />
))}
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Liste des utilisateurs ({total})</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Nom</TableHead>
<TableHead>Email</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Crédits</TableHead>
<TableHead>Statut</TableHead>
<TableHead>Créé le</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => {
const userCredits = creditsMap.get(user._id) || 0;
const isActive = new Date(user.updatedAt || user.createdAt) >
new Date(Date.now() - 5 * 60 * 1000); // 5 minutes en millisecondes
return (
<TableRow key={user._id}>
<TableCell>
<span className="font-mono text-xs">
{user._id.slice(-8)}
</span>
</TableCell>
<TableCell>
<span className="font-medium">{user.name}</span>
</TableCell>
<TableCell>
<span className="text-sm">{user.email}</span>
</TableCell>
<TableCell>
<Badge variant={user.role === 'ADMIN' ? 'default' : 'secondary'}>
{user.role}
</Badge>
</TableCell>
<TableCell>
<span className="font-semibold">
{userCredits.toLocaleString()} crédits
</span>
</TableCell>
<TableCell>
<Badge variant={isActive ? 'default' : 'destructive'}>
{isActive ? 'Actif' : 'Inactif'}
</Badge>
</TableCell>
<TableCell>
<span className="text-sm text-muted-foreground">
{formatDate(new Date(user.createdAt))}
</span>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="flex items-center justify-between space-x-2 py-4">
<div className="text-sm text-muted-foreground">
Page {page} sur {totalPages} ({total} éléments au total)
</div>
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={handlePrevPage}
disabled={page <= 1}
>
<ChevronLeft className="h-4 w-4" />
Précédent
</Button>
<Button
variant="outline"
size="sm"
onClick={handleNextPage}
disabled={page >= totalPages}
>
Suivant
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
);
}