fix(ui): Fix pagination for documents

#320
This commit is contained in:
sanwalsulehri 2025-09-21 09:54:38 +05:00
parent df665a0424
commit 40732faba0

View file

@ -1,16 +1,16 @@
"use client"; "use client";
import { import {
type ColumnDef, type ColumnDef,
type ColumnFiltersState, type ColumnFiltersState,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
type SortingState, type SortingState,
useReactTable, useReactTable,
type VisibilityState, type VisibilityState,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { ArrowUpDown, Calendar, FileText, Search } from "lucide-react"; import { ArrowUpDown, Calendar, FileText, Search } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
@ -18,432 +18,462 @@ import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { Document, DocumentType } from "@/hooks/use-documents"; import type { Document, DocumentType } from "@/hooks/use-documents";
interface DocumentsDataTableProps { interface DocumentsDataTableProps {
documents: Document[]; documents: Document[];
onSelectionChange: (documents: Document[]) => void; onSelectionChange: (documents: Document[]) => void;
onDone: () => void; onDone: () => void;
initialSelectedDocuments?: Document[]; initialSelectedDocuments?: Document[];
pageIndex?: number; pageIndex?: number;
pageSize?: number; pageSize?: number;
onPageIndexChange?: (pageIndex: number) => void; onPageIndexChange?: (pageIndex: number) => void;
onPageSizeChange?: (pageSize: number) => void; onPageSizeChange?: (pageSize: number) => void;
canNext?: boolean; canNext?: boolean;
} }
// Combine EnumConnectorName with additional document types // Combine EnumConnectorName with additional document types
const DOCUMENT_TYPES: (string | "ALL")[] = [ const DOCUMENT_TYPES: (string | "ALL")[] = [
"ALL", "ALL",
"FILE", "FILE",
"EXTENSION", "EXTENSION",
"CRAWLED_URL", "CRAWLED_URL",
"YOUTUBE_VIDEO", "YOUTUBE_VIDEO",
...Object.values(EnumConnectorName), ...Object.values(EnumConnectorName),
]; ];
const columns: ColumnDef<Document>[] = [ const columns: ColumnDef<Document>[] = [
{ {
id: "select", id: "select",
header: ({ table }) => ( header: ({ table }) => (
<Checkbox <Checkbox
checked={ checked={
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate") table.getIsAllPageRowsSelected() ||
} (table.getIsSomePageRowsSelected() && "indeterminate")
onCheckedChange={(value) => { }
table.toggleAllPageRowsSelected(!!value); onCheckedChange={(value) => {
}} table.toggleAllPageRowsSelected(!!value);
aria-label="Select all" }}
/> aria-label="Select all"
), />
cell: ({ row }) => ( ),
<Checkbox cell: ({ row }) => (
checked={row.getIsSelected()} <Checkbox
onCheckedChange={(value) => { checked={row.getIsSelected()}
row.toggleSelected(!!value); onCheckedChange={(value) => {
}} row.toggleSelected(!!value);
aria-label="Select row" }}
/> aria-label="Select row"
), />
enableSorting: false, ),
enableHiding: false, enableSorting: false,
size: 40, enableHiding: false,
}, size: 40,
{ },
accessorKey: "title", {
header: ({ column }) => ( accessorKey: "title",
<Button header: ({ column }) => (
variant="ghost" <Button
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} variant="ghost"
className="h-8 px-1 sm:px-2 font-medium text-left justify-start" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
> className="h-8 px-1 sm:px-2 font-medium text-left justify-start"
<FileText className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" /> >
<span className="hidden sm:inline">Title</span> <FileText className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
<span className="sm:hidden">Doc</span> <span className="hidden sm:inline">Title</span>
<ArrowUpDown className="ml-1 sm:ml-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" /> <span className="sm:hidden">Doc</span>
</Button> <ArrowUpDown className="ml-1 sm:ml-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
), </Button>
cell: ({ row }) => { ),
const title = row.getValue("title") as string; cell: ({ row }) => {
return ( const title = row.getValue("title") as string;
<div return (
className="font-medium max-w-[120px] sm:max-w-[250px] truncate text-xs sm:text-sm" <div
title={title} className="font-medium max-w-[120px] sm:max-w-[250px] truncate text-xs sm:text-sm"
> title={title}
{title} >
</div> {title}
); </div>
}, );
}, },
{ },
accessorKey: "document_type", {
header: "Type", accessorKey: "document_type",
cell: ({ row }) => { header: "Type",
const type = row.getValue("document_type") as DocumentType; cell: ({ row }) => {
return ( const type = row.getValue("document_type") as DocumentType;
<div className="flex items-center gap-2" title={type}> return (
<span className="text-primary">{getConnectorIcon(type)}</span> <div className="flex items-center gap-2" title={type}>
</div> <span className="text-primary">{getConnectorIcon(type)}</span>
); </div>
}, );
size: 80, },
meta: { size: 80,
className: "hidden sm:table-cell", meta: {
}, className: "hidden sm:table-cell",
}, },
{ },
accessorKey: "content", {
header: "Preview", accessorKey: "content",
cell: ({ row }) => { header: "Preview",
const content = row.getValue("content") as string; cell: ({ row }) => {
return ( const content = row.getValue("content") as string;
<div return (
className="text-muted-foreground max-w-[150px] sm:max-w-[350px] truncate text-[10px] sm:text-sm" <div
title={content} className="text-muted-foreground max-w-[150px] sm:max-w-[350px] truncate text-[10px] sm:text-sm"
> title={content}
<span className="sm:hidden">{content.substring(0, 30)}...</span> >
<span className="hidden sm:inline">{content.substring(0, 100)}...</span> <span className="sm:hidden">{content.substring(0, 30)}...</span>
</div> <span className="hidden sm:inline">
); {content.substring(0, 100)}...
}, </span>
enableSorting: false, </div>
meta: { );
className: "hidden md:table-cell", },
}, enableSorting: false,
}, meta: {
{ className: "hidden md:table-cell",
accessorKey: "created_at", },
header: ({ column }) => ( },
<Button {
variant="ghost" accessorKey: "created_at",
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} header: ({ column }) => (
className="h-8 px-1 sm:px-2 font-medium" <Button
> variant="ghost"
<Calendar className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" /> onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
<span className="hidden sm:inline">Created</span> className="h-8 px-1 sm:px-2 font-medium"
<span className="sm:hidden">Date</span> >
<ArrowUpDown className="ml-1 sm:ml-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" /> <Calendar className="mr-1 sm:mr-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
</Button> <span className="hidden sm:inline">Created</span>
), <span className="sm:hidden">Date</span>
cell: ({ row }) => { <ArrowUpDown className="ml-1 sm:ml-2 h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
const date = new Date(row.getValue("created_at")); </Button>
return ( ),
<div className="text-xs sm:text-sm whitespace-nowrap"> cell: ({ row }) => {
<span className="hidden sm:inline"> const date = new Date(row.getValue("created_at"));
{date.toLocaleDateString("en-US", { return (
month: "short", <div className="text-xs sm:text-sm whitespace-nowrap">
day: "numeric", <span className="hidden sm:inline">
year: "numeric", {date.toLocaleDateString("en-US", {
})} month: "short",
</span> day: "numeric",
<span className="sm:hidden"> year: "numeric",
{date.toLocaleDateString("en-US", { })}
month: "numeric", </span>
day: "numeric", <span className="sm:hidden">
})} {date.toLocaleDateString("en-US", {
</span> month: "numeric",
</div> day: "numeric",
); })}
}, </span>
size: 80, </div>
}, );
},
size: 80,
},
]; ];
export function DocumentsDataTable({ export function DocumentsDataTable({
documents, documents,
onSelectionChange, onSelectionChange,
onDone, onDone,
initialSelectedDocuments = [], initialSelectedDocuments = [],
pageIndex = 0, pageIndex = 0,
pageSize = 100, pageSize = 100,
onPageIndexChange, onPageIndexChange,
onPageSizeChange, onPageSizeChange,
canNext = false, canNext = false,
}: DocumentsDataTableProps) { }: DocumentsDataTableProps) {
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}); const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [documentTypeFilter, setDocumentTypeFilter] = useState<string | "ALL">("ALL"); const [documentTypeFilter, setDocumentTypeFilter] = useState<string | "ALL">(
"ALL"
);
// Memoize initial row selection to prevent infinite loops // Memoize initial row selection to prevent infinite loops
const initialRowSelection = useMemo(() => { const initialRowSelection = useMemo(() => {
if (!documents.length || !initialSelectedDocuments.length) return {}; if (!documents.length || !initialSelectedDocuments.length) return {};
const selection: Record<string, boolean> = {}; const selection: Record<string, boolean> = {};
initialSelectedDocuments.forEach((selectedDoc) => { initialSelectedDocuments.forEach((selectedDoc) => {
// Find the document in the current documents array to get the correct row ID // Find the document in the current documents array to get the correct row ID
const docInCurrentList = documents.find(doc => doc.id === selectedDoc.id); const docInCurrentList = documents.find(
if (docInCurrentList) { (doc) => doc.id === selectedDoc.id
selection[docInCurrentList.id.toString()] = true; );
} if (docInCurrentList) {
}); selection[docInCurrentList.id.toString()] = true;
return selection; }
}, [documents, initialSelectedDocuments]); });
return selection;
}, [documents, initialSelectedDocuments]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(() => initialRowSelection); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
() => initialRowSelection
);
// Update row selection when initial selection changes // Update row selection when initial selection changes
useEffect(() => { useEffect(() => {
setRowSelection(initialRowSelection); setRowSelection(initialRowSelection);
}, [initialRowSelection]); }, [initialRowSelection]);
const filteredDocuments = useMemo(() => { const filteredDocuments = useMemo(() => {
if (documentTypeFilter === "ALL") return documents; if (documentTypeFilter === "ALL") return documents;
return documents.filter((doc) => doc.document_type === documentTypeFilter); return documents.filter((doc) => doc.document_type === documentTypeFilter);
}, [documents, documentTypeFilter]); }, [documents, documentTypeFilter]);
const table = useReactTable({ const table = useReactTable({
data: filteredDocuments, data: filteredDocuments,
columns, columns,
getRowId: (row) => row.id.toString(), getRowId: (row) => row.id.toString(),
onSortingChange: setSorting, onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters, onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility, onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
// Disable internal pagination since we're handling it at the parent level // Disable internal pagination since we're handling it at the parent level
manualPagination: true, manualPagination: true,
pageCount: -1, pageCount: -1,
enableRowSelection: true, enableRowSelection: true,
state: { sorting, columnFilters, columnVisibility, rowSelection }, state: { sorting, columnFilters, columnVisibility, rowSelection },
}); });
useEffect(() => { useEffect(() => {
const selectedRows = table.getFilteredSelectedRowModel().rows; const selectedRows = table.getFilteredSelectedRowModel().rows;
const selectedDocuments = selectedRows.map((row) => row.original); const selectedDocuments = selectedRows.map((row) => row.original);
onSelectionChange(selectedDocuments); onSelectionChange(selectedDocuments);
}, [rowSelection, table]); }, [rowSelection, table]);
const handleClearAll = () => setRowSelection({}); const handleClearAll = () => setRowSelection({});
const handleSelectPage = () => { const handleSelectPage = () => {
const currentPageRows = table.getRowModel().rows; const currentPageRows = table.getRowModel().rows;
const newSelection = { ...rowSelection }; const newSelection = { ...rowSelection };
currentPageRows.forEach((row) => { currentPageRows.forEach((row) => {
newSelection[row.id] = true; newSelection[row.id] = true;
}); });
setRowSelection(newSelection); setRowSelection(newSelection);
}; };
const handleSelectAllFiltered = () => { const handleSelectAllFiltered = () => {
const allFilteredRows = table.getFilteredRowModel().rows; const allFilteredRows = table.getFilteredRowModel().rows;
const newSelection: Record<string, boolean> = {}; const newSelection: Record<string, boolean> = {};
allFilteredRows.forEach((row) => { allFilteredRows.forEach((row) => {
newSelection[row.id] = true; newSelection[row.id] = true;
}); });
setRowSelection(newSelection); setRowSelection(newSelection);
}; };
const selectedCount = table.getFilteredSelectedRowModel().rows.length; const selectedCount = table.getFilteredSelectedRowModel().rows.length;
const totalFiltered = table.getFilteredRowModel().rows.length; const totalFiltered = table.getFilteredRowModel().rows.length;
return (
<div className="flex flex-col h-full space-y-3 md:space-y-4">
{/* Header Controls */}
<div className="space-y-3 md:space-y-4 flex-shrink-0">
{/* Search and Filter Row */}
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
<div className="relative flex-1 max-w-full sm:max-w-sm">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search documents..."
value={
(table.getColumn("title")?.getFilterValue() as string) ?? ""
}
onChange={(event) =>
table.getColumn("title")?.setFilterValue(event.target.value)
}
className="pl-10 text-sm"
/>
</div>
<Select
value={documentTypeFilter}
onValueChange={(value) =>
setDocumentTypeFilter(value as string | "ALL")
}
>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{DOCUMENT_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{type === "ALL" ? "All Types" : type.replace(/_/g, " ")}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
return ( {/* Action Controls Row */}
<div className="flex flex-col h-full space-y-3 md:space-y-4"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
{/* Header Controls */} <div className="flex flex-col sm:flex-row sm:items-center gap-2">
<div className="space-y-3 md:space-y-4 flex-shrink-0"> <span className="text-sm text-muted-foreground whitespace-nowrap">
{/* Search and Filter Row */} {selectedCount} of {totalFiltered} selected
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4"> </span>
<div className="relative flex-1 max-w-full sm:max-w-sm"> <div className="hidden sm:block h-4 w-px bg-border mx-2" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <div className="flex items-center gap-2 flex-wrap">
<Input <Button
placeholder="Search documents..." variant="ghost"
value={(table.getColumn("title")?.getFilterValue() as string) ?? ""} size="sm"
onChange={(event) => table.getColumn("title")?.setFilterValue(event.target.value)} onClick={handleClearAll}
className="pl-10 text-sm" disabled={selectedCount === 0}
/> className="text-xs sm:text-sm"
</div> >
<Select Clear All
value={documentTypeFilter} </Button>
onValueChange={(value) => setDocumentTypeFilter(value as string | "ALL")} <Button
> variant="ghost"
<SelectTrigger className="w-full sm:w-[180px]"> size="sm"
<SelectValue /> onClick={handleSelectPage}
</SelectTrigger> className="text-xs sm:text-sm"
<SelectContent> >
{DOCUMENT_TYPES.map((type) => ( Select Page
<SelectItem key={type} value={type}> </Button>
{type === "ALL" ? "All Types" : type.replace(/_/g, " ")} <Button
</SelectItem> variant="ghost"
))} size="sm"
</SelectContent> onClick={handleSelectAllFiltered}
</Select> className="text-xs sm:text-sm hidden sm:inline-flex"
</div> >
Select All Filtered
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleSelectAllFiltered}
className="text-xs sm:hidden"
>
Select All
</Button>
</div>
</div>
<Button
onClick={onDone}
disabled={selectedCount === 0}
className="w-full sm:w-auto sm:min-w-[100px]"
>
Done ({selectedCount})
</Button>
</div>
</div>
{/* Action Controls Row */} {/* Table Container */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"> <div className="border rounded-lg flex-1 min-h-0 overflow-hidden bg-background">
<div className="flex flex-col sm:flex-row sm:items-center gap-2"> <div className="overflow-auto h-full">
<span className="text-sm text-muted-foreground whitespace-nowrap"> <Table>
{selectedCount} of {totalFiltered} selected <TableHeader className="sticky top-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 z-10">
</span> {table.getHeaderGroups().map((headerGroup) => (
<div className="hidden sm:block h-4 w-px bg-border mx-2" /> <TableRow key={headerGroup.id} className="border-b">
<div className="flex items-center gap-2 flex-wrap"> {headerGroup.headers.map((header) => (
<Button <TableHead
variant="ghost" key={header.id}
size="sm" className="h-12 text-xs sm:text-sm"
onClick={handleClearAll} >
disabled={selectedCount === 0} {header.isPlaceholder
className="text-xs sm:text-sm" ? null
> : flexRender(
Clear All header.column.columnDef.header,
</Button> header.getContext()
<Button )}
variant="ghost" </TableHead>
size="sm" ))}
onClick={handleSelectPage} </TableRow>
className="text-xs sm:text-sm" ))}
> </TableHeader>
Select Page <TableBody>
</Button> {table.getRowModel().rows?.length ? (
<Button table.getRowModel().rows.map((row) => (
variant="ghost" <TableRow
size="sm" key={row.id}
onClick={handleSelectAllFiltered} data-state={row.getIsSelected() && "selected"}
className="text-xs sm:text-sm hidden sm:inline-flex" className="hover:bg-muted/30"
> >
Select All Filtered {row.getVisibleCells().map((cell) => (
</Button> <TableCell
<Button key={cell.id}
variant="ghost" className="py-3 text-xs sm:text-sm"
size="sm" >
onClick={handleSelectAllFiltered} {flexRender(
className="text-xs sm:hidden" cell.column.columnDef.cell,
> cell.getContext()
Select All )}
</Button> </TableCell>
</div> ))}
</div> </TableRow>
<Button ))
onClick={onDone} ) : (
disabled={selectedCount === 0} <TableRow>
className="w-full sm:w-auto sm:min-w-[100px]" <TableCell
> colSpan={columns.length}
Done ({selectedCount}) className="h-32 text-center text-muted-foreground text-sm"
</Button> >
</div> No documents found.
</div> </TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
{/* Table Container */} {/* Footer Pagination */}
<div className="border rounded-lg flex-1 min-h-0 overflow-hidden bg-background"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-xs sm:text-sm text-muted-foreground border-t pt-3 md:pt-4 flex-shrink-0">
<div className="overflow-auto h-full"> <div className="text-center sm:text-left">
<Table> Showing {pageIndex * pageSize + 1} to{" "}
<TableHeader className="sticky top-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 z-10"> {Math.min(
{table.getHeaderGroups().map((headerGroup) => ( (pageIndex + 1) * pageSize,
<TableRow key={headerGroup.id} className="border-b"> pageIndex * pageSize + documents.length
{headerGroup.headers.map((header) => ( )}{" "}
<TableHead key={header.id} className="h-12 text-xs sm:text-sm"> of{" "}
{header.isPlaceholder {canNext
? null ? `${(pageIndex + 1) * pageSize}+`
: flexRender(header.column.columnDef.header, header.getContext())} : `${pageIndex * pageSize + documents.length}`}{" "}
</TableHead> documents
))} </div>
</TableRow> <div className="flex items-center justify-center sm:justify-end space-x-2">
))} <Button
</TableHeader> variant="outline"
<TableBody> size="sm"
{table.getRowModel().rows?.length ? ( onClick={() => onPageIndexChange?.(pageIndex - 1)}
table.getRowModel().rows.map((row) => ( disabled={pageIndex === 0}
<TableRow className="text-xs sm:text-sm"
key={row.id} >
data-state={row.getIsSelected() && "selected"} Previous
className="hover:bg-muted/30" </Button>
> <div className="flex items-center space-x-1 text-xs sm:text-sm">
{row.getVisibleCells().map((cell) => ( <span>Page</span>
<TableCell key={cell.id} className="py-3 text-xs sm:text-sm"> <strong>{pageIndex + 1}</strong>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {canNext && <span>+</span>}
</TableCell> </div>
))} <Button
</TableRow> variant="outline"
)) size="sm"
) : ( onClick={() => onPageIndexChange?.(pageIndex + 1)}
<TableRow> disabled={!canNext}
<TableCell className="text-xs sm:text-sm"
colSpan={columns.length} >
className="h-32 text-center text-muted-foreground text-sm" Next
> </Button>
No documents found. </div>
</TableCell> </div>
</TableRow> </div>
)} );
</TableBody>
</Table>
</div>
</div>
{/* Footer Pagination */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-xs sm:text-sm text-muted-foreground border-t pt-3 md:pt-4 flex-shrink-0">
<div className="text-center sm:text-left">
Showing {pageIndex * pageSize + 1} to{" "}
{Math.min((pageIndex + 1) * pageSize, pageIndex * pageSize + documents.length)} of{" "}
{canNext
? `${(pageIndex + 1) * pageSize}+`
: `${pageIndex * pageSize + documents.length}`}{" "}
documents
</div>
<div className="flex items-center justify-center sm:justify-end space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => onPageIndexChange?.(pageIndex - 1)}
disabled={pageIndex === 0}
className="text-xs sm:text-sm"
>
Previous
</Button>
<div className="flex items-center space-x-1 text-xs sm:text-sm">
<span>Page</span>
<strong>{pageIndex + 1}</strong>
{canNext && <span>+</span>}
</div>
<Button
variant="outline"
size="sm"
onClick={() => onPageIndexChange?.(pageIndex + 1)}
disabled={!canNext}
className="text-xs sm:text-sm"
>
Next
</Button>
</div>
</div>
</div>
);
} }