conversation clan
This commit is contained in:
@@ -10,7 +10,9 @@
|
|||||||
"Bash(git push)",
|
"Bash(git push)",
|
||||||
"Bash(npm install:*)",
|
"Bash(npm install:*)",
|
||||||
"Bash(node debug-search.js:*)",
|
"Bash(node debug-search.js:*)",
|
||||||
"Bash(node update-referent.js:*)"
|
"Bash(node update-referent.js:*)",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(curl:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ export async function GET(
|
|||||||
const limit = parseInt(searchParams.get("limit") || "20");
|
const limit = parseInt(searchParams.get("limit") || "20");
|
||||||
const filter = JSON.parse(searchParams.get("filter") || "{}");
|
const filter = JSON.parse(searchParams.get("filter") || "{}");
|
||||||
|
|
||||||
|
// Debug logging pour messages
|
||||||
|
if (collection === "messages") {
|
||||||
|
console.log("[API Messages] Filter reçu:", JSON.stringify(filter));
|
||||||
|
console.log("[API Messages] Raw filter param:", searchParams.get("filter"));
|
||||||
|
}
|
||||||
|
|
||||||
// Gestion spéciale pour la collection users avec recherche par email ou id
|
// Gestion spéciale pour la collection users avec recherche par email ou id
|
||||||
if (collection === "users") {
|
if (collection === "users") {
|
||||||
const email = searchParams.get("email");
|
const email = searchParams.get("email");
|
||||||
@@ -141,6 +147,11 @@ export async function GET(
|
|||||||
db.collection(collection).countDocuments(filter),
|
db.collection(collection).countDocuments(filter),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Debug logging pour messages
|
||||||
|
if (collection === "messages") {
|
||||||
|
console.log("[API Messages] Résultats:", data.length, "messages, total:", total);
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
data,
|
data,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@@ -3,14 +3,6 @@
|
|||||||
import { useState, useMemo, useCallback, useRef, useEffect, Fragment } from "react";
|
import { useState, useMemo, useCallback, useRef, useEffect, Fragment } from "react";
|
||||||
import { useQuery, keepPreviousData } from "@tanstack/react-query";
|
import { useQuery, keepPreviousData } from "@tanstack/react-query";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
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 { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -51,6 +43,13 @@ interface ExtendedMessage extends LibreChatMessage {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type pour les groupes d'utilisateurs
|
||||||
|
interface UserGroup {
|
||||||
|
userId: string;
|
||||||
|
conversations: LibreChatConversation[];
|
||||||
|
totalMessages: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetcher générique
|
// Fetcher générique
|
||||||
async function fetchCollection<T>(
|
async function fetchCollection<T>(
|
||||||
collection: string,
|
collection: string,
|
||||||
@@ -63,36 +62,44 @@ async function fetchCollection<T>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fetch(`/api/collections/${collection}?${searchParams}`);
|
const url = `/api/collections/${collection}?${searchParams}`;
|
||||||
|
console.log("[fetchCollection]", collection, "URL:", url);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
if (!response.ok) throw new Error(`Erreur lors du chargement de ${collection}`);
|
if (!response.ok) throw new Error(`Erreur lors du chargement de ${collection}`);
|
||||||
return response.json();
|
const data = await response.json();
|
||||||
|
console.log("[fetchCollection]", collection, "Results:", data.data?.length, "items");
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConversationsTable() {
|
export function ConversationsTable() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [searchInput, setSearchInput] = useState("");
|
const [searchInput, setSearchInput] = useState("");
|
||||||
const [selectedConversationId, setSelectedConversationId] = useState<string | null>(null);
|
const [expandedUsers, setExpandedUsers] = useState<Set<string>>(new Set());
|
||||||
|
const [expandedConversation, setExpandedConversation] = useState<string | null>(null);
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const limit = 20;
|
const usersPerPage = 10; // Nombre de groupes d'utilisateurs par page
|
||||||
const debouncedSearch = useDebounce(searchInput, 250);
|
const debouncedSearch = useDebounce(searchInput, 250);
|
||||||
|
|
||||||
// Reset page quand la recherche change
|
// Reset page et expanded states quand la recherche change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPage(1);
|
setPage(1);
|
||||||
|
setExpandedUsers(new Set());
|
||||||
|
setExpandedConversation(null);
|
||||||
}, [debouncedSearch]);
|
}, [debouncedSearch]);
|
||||||
|
|
||||||
// Query conversations avec TanStack Query
|
// Query conversations avec TanStack Query - limite élevée pour groupement client
|
||||||
const {
|
const {
|
||||||
data: conversationsData,
|
data: conversationsData,
|
||||||
isLoading: conversationsLoading,
|
isLoading: conversationsLoading,
|
||||||
isFetching: conversationsFetching,
|
isFetching: conversationsFetching,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["conversations", page, limit, debouncedSearch],
|
queryKey: ["conversations", debouncedSearch],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchCollection<LibreChatConversation>("conversations", {
|
fetchCollection<LibreChatConversation>("conversations", {
|
||||||
page,
|
page: 1,
|
||||||
limit,
|
limit: 1000, // Charger beaucoup pour grouper côté client
|
||||||
search: debouncedSearch,
|
search: debouncedSearch,
|
||||||
}),
|
}),
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
@@ -108,25 +115,79 @@ export function ConversationsTable() {
|
|||||||
|
|
||||||
// Query messages de la conversation sélectionnée
|
// Query messages de la conversation sélectionnée
|
||||||
const { data: messagesData, isLoading: messagesLoading } = useQuery({
|
const { data: messagesData, isLoading: messagesLoading } = useQuery({
|
||||||
queryKey: ["messages", selectedConversationId],
|
queryKey: ["messages", expandedConversation],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchCollection<LibreChatMessage>("messages", {
|
fetchCollection<LibreChatMessage>("messages", {
|
||||||
limit: 500,
|
limit: 500,
|
||||||
filter: JSON.stringify({ conversationId: selectedConversationId }),
|
filter: JSON.stringify({ conversationId: expandedConversation }),
|
||||||
}),
|
}),
|
||||||
enabled: !!selectedConversationId,
|
enabled: !!expandedConversation,
|
||||||
staleTime: 30000,
|
staleTime: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const conversations = conversationsData?.data ?? [];
|
const conversations = conversationsData?.data ?? [];
|
||||||
const total = conversationsData?.total ?? 0;
|
const total = conversationsData?.total ?? 0;
|
||||||
const totalPages = conversationsData?.totalPages ?? 0;
|
|
||||||
const users = usersData?.data ?? [];
|
const users = usersData?.data ?? [];
|
||||||
const messages = messagesData?.data ?? [];
|
const messages = messagesData?.data ?? [];
|
||||||
|
|
||||||
// Map des users pour lookup rapide
|
// Map des users pour lookup rapide
|
||||||
const userMap = useMemo(() => new Map(users.map((u) => [u._id, u])), [users]);
|
const userMap = useMemo(() => new Map(users.map((u) => [u._id, u])), [users]);
|
||||||
|
|
||||||
|
// Grouper les conversations par utilisateur
|
||||||
|
const groupedByUser = useMemo((): UserGroup[] => {
|
||||||
|
const groups: Record<string, LibreChatConversation[]> = {};
|
||||||
|
conversations.forEach((conv) => {
|
||||||
|
const uId = String(conv.user);
|
||||||
|
if (!groups[uId]) groups[uId] = [];
|
||||||
|
groups[uId].push(conv);
|
||||||
|
});
|
||||||
|
return Object.entries(groups)
|
||||||
|
.map(([userId, convs]) => ({
|
||||||
|
userId,
|
||||||
|
conversations: convs.sort(
|
||||||
|
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
),
|
||||||
|
totalMessages: convs.reduce((sum, c) => sum + (c.messages?.length || 0), 0),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Trier par date de dernière conversation
|
||||||
|
const aDate = new Date(a.conversations[0]?.updatedAt || 0).getTime();
|
||||||
|
const bDate = new Date(b.conversations[0]?.updatedAt || 0).getTime();
|
||||||
|
return bDate - aDate;
|
||||||
|
});
|
||||||
|
}, [conversations]);
|
||||||
|
|
||||||
|
// Pagination sur les groupes d'utilisateurs
|
||||||
|
const totalUserGroups = groupedByUser.length;
|
||||||
|
const totalPages = Math.ceil(totalUserGroups / usersPerPage);
|
||||||
|
const paginatedGroups = groupedByUser.slice(
|
||||||
|
(page - 1) * usersPerPage,
|
||||||
|
page * usersPerPage
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle user group expansion
|
||||||
|
const toggleUserExpanded = useCallback((userId: string) => {
|
||||||
|
setExpandedUsers((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(userId)) {
|
||||||
|
next.delete(userId);
|
||||||
|
// Fermer aussi la conversation si elle appartient à cet utilisateur
|
||||||
|
setExpandedConversation(null);
|
||||||
|
} else {
|
||||||
|
next.add(userId);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Toggle conversation expansion
|
||||||
|
const toggleConversationExpanded = useCallback((conversationId: string) => {
|
||||||
|
console.log("[Conversations] Toggle conversation:", conversationId);
|
||||||
|
setExpandedConversation((prev) =>
|
||||||
|
prev === conversationId ? null : conversationId
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const getUserInfo = useCallback(
|
const getUserInfo = useCallback(
|
||||||
(userId: string) => {
|
(userId: string) => {
|
||||||
const user = userMap.get(userId);
|
const user = userMap.get(userId);
|
||||||
@@ -236,164 +297,168 @@ export function ConversationsTable() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="rounded-md border">
|
{/* Liste des groupes d'utilisateurs */}
|
||||||
<Table>
|
<div className="space-y-2">
|
||||||
<TableHeader>
|
{paginatedGroups.map((group) => {
|
||||||
<TableRow>
|
const userInfo = getUserInfo(group.userId);
|
||||||
<TableHead className="w-52">Utilisateur</TableHead>
|
const isUserExpanded = expandedUsers.has(group.userId);
|
||||||
<TableHead>Titre</TableHead>
|
|
||||||
<TableHead className="w-24">Endpoint</TableHead>
|
|
||||||
<TableHead className="w-20 text-center">Msgs</TableHead>
|
|
||||||
<TableHead className="w-24">Statut</TableHead>
|
|
||||||
<TableHead className="w-28">Date</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{conversations.map((conv) => {
|
|
||||||
const userInfo = getUserInfo(String(conv.user));
|
|
||||||
const msgCount = conv.messages?.length || 0;
|
|
||||||
const isSelected = selectedConversationId === conv.conversationId;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={conv._id}>
|
<div key={group.userId} className="border rounded-lg overflow-hidden">
|
||||||
<TableRow
|
{/* Header du groupe utilisateur */}
|
||||||
className={`cursor-pointer hover:bg-muted/50 transition-colors ${isSelected ? "bg-muted/50" : ""}`}
|
<button
|
||||||
onClick={() =>
|
onClick={() => toggleUserExpanded(group.userId)}
|
||||||
setSelectedConversationId(
|
className="w-full flex items-center gap-3 p-4 bg-muted/30 hover:bg-muted/50 transition-colors text-left"
|
||||||
isSelected ? null : conv.conversationId
|
>
|
||||||
)
|
<ChevronDown
|
||||||
}
|
className={`h-5 w-5 text-muted-foreground transition-transform flex-shrink-0 ${
|
||||||
>
|
isUserExpanded ? "rotate-0" : "-rotate-90"
|
||||||
<TableCell>
|
}`}
|
||||||
<div className="flex items-center gap-2">
|
/>
|
||||||
<ChevronDown
|
<div className="flex-1 min-w-0">
|
||||||
className={`h-4 w-4 text-muted-foreground transition-transform ${
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
isSelected ? "rotate-0" : "-rotate-90"
|
<span className="font-semibold">{userInfo.name}</span>
|
||||||
|
{userInfo.email && (
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
({userInfo.email})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
|
<Badge variant="secondary">
|
||||||
|
{group.conversations.length} conversation{group.conversations.length > 1 ? "s" : ""}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{group.totalMessages} msg{group.totalMessages > 1 ? "s" : ""}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Liste des conversations de l'utilisateur */}
|
||||||
|
{isUserExpanded && (
|
||||||
|
<div className="border-t">
|
||||||
|
{group.conversations.map((conv) => {
|
||||||
|
const msgCount = conv.messages?.length || 0;
|
||||||
|
const isConvExpanded = expandedConversation === conv.conversationId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={conv._id}>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleConversationExpanded(conv.conversationId)}
|
||||||
|
className={`w-full flex items-center gap-3 p-3 pl-10 hover:bg-muted/30 transition-colors text-left border-b last:border-b-0 ${
|
||||||
|
isConvExpanded ? "bg-muted/20" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
>
|
||||||
<div className="flex flex-col">
|
<ChevronDown
|
||||||
<span className="font-medium truncate max-w-[180px]">
|
className={`h-4 w-4 text-muted-foreground transition-transform flex-shrink-0 ${
|
||||||
{userInfo.name}
|
isConvExpanded ? "rotate-0" : "-rotate-90"
|
||||||
</span>
|
}`}
|
||||||
{userInfo.email && (
|
/>
|
||||||
<span className="text-xs text-muted-foreground truncate max-w-[180px]">
|
<div className="flex-1 min-w-0">
|
||||||
{userInfo.email}
|
<span className="truncate block text-sm font-medium">
|
||||||
|
{String(conv.title) || "Sans titre"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
</div>
|
<Badge variant="outline" className="text-xs font-mono">
|
||||||
</TableCell>
|
{String(conv.endpoint).slice(0, 8)}
|
||||||
<TableCell>
|
</Badge>
|
||||||
<span className="truncate block max-w-xs" title={String(conv.title)}>
|
<Badge variant="secondary" className="text-xs">
|
||||||
{String(conv.title) || "Sans titre"}
|
{msgCount} msg{msgCount > 1 ? "s" : ""}
|
||||||
</span>
|
</Badge>
|
||||||
</TableCell>
|
<Badge
|
||||||
<TableCell>
|
variant={conv.isArchived ? "outline" : "default"}
|
||||||
<Badge variant="outline" className="text-xs font-mono">
|
className="text-xs"
|
||||||
{String(conv.endpoint).slice(0, 10)}
|
>
|
||||||
</Badge>
|
{conv.isArchived ? "Archivée" : "Active"}
|
||||||
</TableCell>
|
</Badge>
|
||||||
<TableCell className="text-center">
|
<span className="text-xs text-muted-foreground">
|
||||||
<Badge variant="secondary" className="text-xs">
|
{formatDate(conv.updatedAt)}
|
||||||
{msgCount}
|
</span>
|
||||||
</Badge>
|
</div>
|
||||||
</TableCell>
|
</button>
|
||||||
<TableCell>
|
|
||||||
<Badge
|
|
||||||
variant={conv.isArchived ? "outline" : "default"}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
{conv.isArchived ? "Archivée" : "Active"}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formatDate(conv.updatedAt)}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
{/* Messages inline sous la row */}
|
{/* Messages de la conversation */}
|
||||||
{isSelected && (
|
{isConvExpanded && (
|
||||||
<TableRow key={`${conv._id}-messages`}>
|
<div className="bg-slate-50 p-4 pl-14 border-b">
|
||||||
<TableCell colSpan={6} className="p-0 border-b">
|
{messagesLoading ? (
|
||||||
<div className="bg-slate-50 p-4">
|
<div className="flex items-center justify-center py-8 gap-2 text-muted-foreground">
|
||||||
{messagesLoading ? (
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
<div className="flex items-center justify-center py-8 gap-2 text-muted-foreground">
|
<span>Chargement des messages...</span>
|
||||||
<Loader2 className="h-5 w-5 animate-spin" />
|
</div>
|
||||||
<span>Chargement des messages...</span>
|
) : messages.length === 0 ? (
|
||||||
</div>
|
<p className="text-center py-8 text-muted-foreground">
|
||||||
) : messages.length === 0 ? (
|
Aucun message dans cette conversation
|
||||||
<p className="text-center py-8 text-muted-foreground">
|
</p>
|
||||||
Aucun message dans cette conversation
|
) : (
|
||||||
</p>
|
<div className="space-y-3 max-h-[400px] overflow-y-auto">
|
||||||
) : (
|
{messages
|
||||||
<div className="space-y-3 max-h-[400px] overflow-y-auto">
|
.sort(
|
||||||
{messages
|
(a, b) =>
|
||||||
.sort(
|
new Date(a.createdAt).getTime() -
|
||||||
(a, b) =>
|
new Date(b.createdAt).getTime()
|
||||||
new Date(a.createdAt).getTime() -
|
)
|
||||||
new Date(b.createdAt).getTime()
|
.map((msg) => {
|
||||||
)
|
const content = getMessageContent(msg);
|
||||||
.map((msg) => {
|
const isUser = msg.isCreatedByUser;
|
||||||
const content = getMessageContent(msg);
|
|
||||||
const isUser = msg.isCreatedByUser;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={msg._id}
|
key={msg._id}
|
||||||
className={`flex gap-3 p-3 rounded-lg border-l-4 ${
|
className={`flex gap-3 p-3 rounded-lg border-l-4 ${
|
||||||
isUser
|
isUser
|
||||||
? "bg-blue-50 border-l-blue-500"
|
? "bg-blue-50 border-l-blue-500"
|
||||||
: "bg-white border-l-gray-300"
|
: "bg-white border-l-gray-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0 mt-0.5">
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
<User className="h-4 w-4 text-blue-600" />
|
<User className="h-4 w-4 text-blue-600" />
|
||||||
) : (
|
) : (
|
||||||
<Bot className="h-4 w-4 text-gray-500" />
|
<Bot className="h-4 w-4 text-gray-500" />
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
|
||||||
<span className="text-xs font-medium">
|
|
||||||
{isUser ? "Utilisateur" : "Assistant"}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formatDate(msg.createdAt)}
|
|
||||||
</span>
|
|
||||||
{msg.tokenCount > 0 && (
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
({msg.tokenCount} tokens)
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm whitespace-pre-wrap break-words">
|
<div className="flex-1 min-w-0">
|
||||||
{content}
|
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||||
|
<span className="text-xs font-medium">
|
||||||
|
{isUser ? "Utilisateur" : "Assistant"}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{formatDate(msg.createdAt)}
|
||||||
|
</span>
|
||||||
|
{msg.tokenCount > 0 && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
({msg.tokenCount} tokens)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm whitespace-pre-wrap break-words">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</TableCell>
|
</Fragment>
|
||||||
</TableRow>
|
);
|
||||||
)}
|
})}
|
||||||
</Fragment>
|
</div>
|
||||||
);
|
)}
|
||||||
})}
|
</div>
|
||||||
</TableBody>
|
);
|
||||||
</Table>
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex items-center justify-between mt-4">
|
<div className="flex items-center justify-between mt-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Page {page} / {totalPages}
|
Page {page} / {totalPages} ({totalUserGroups} utilisateurs)
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -421,7 +486,6 @@ export function ConversationsTable() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user