2025-10-18 19:57:52 +00:00
|
|
|
import { Table } from "@tanstack/react-table";
|
2025-10-16 11:18:55 +00:00
|
|
|
import {
|
2025-10-18 19:57:52 +00:00
|
|
|
ChevronLeftIcon,
|
|
|
|
|
ChevronRightIcon,
|
|
|
|
|
ChevronsLeftIcon,
|
|
|
|
|
ChevronsRightIcon
|
|
|
|
|
} from "lucide-react";
|
2025-10-16 11:18:55 +00:00
|
|
|
|
|
|
|
|
import {
|
2025-10-18 19:57:52 +00:00
|
|
|
Pagination, PaginationContent,
|
|
|
|
|
PaginationItem, PaginationLink,
|
|
|
|
|
Select,
|
2025-10-16 11:18:55 +00:00
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue
|
2025-10-18 19:57:52 +00:00
|
|
|
} from '@repo/shadcn-ui/components';
|
|
|
|
|
import { cn } from '@repo/shadcn-ui/lib/utils';
|
|
|
|
|
import { useTranslation } from '../../locales/i18n.ts';
|
|
|
|
|
import { DataTableMeta } from './data-table.tsx';
|
2025-10-16 11:18:55 +00:00
|
|
|
|
|
|
|
|
interface DataTablePaginationProps<TData> {
|
2025-10-18 19:57:52 +00:00
|
|
|
table: Table<TData>;
|
|
|
|
|
className?: string;
|
2025-10-16 11:18:55 +00:00
|
|
|
}
|
|
|
|
|
|
2025-10-18 19:57:52 +00:00
|
|
|
export function DataTablePagination<TData>({ table, className }: DataTablePaginationProps<TData>) {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
const { pageIndex, pageSize } = table.getState().pagination;
|
|
|
|
|
const pageCount = table.getPageCount() || 1;
|
|
|
|
|
const totalRows = (table.options.meta as DataTableMeta<TData>)?.totalItems ?? table.getFilteredRowModel().rows.length;
|
|
|
|
|
const hasSelected = table.getFilteredSelectedRowModel().rows.length > 0;
|
|
|
|
|
|
|
|
|
|
// Calcula rango visible (inicio-fin)
|
|
|
|
|
const start = totalRows === 0 ? 0 : pageIndex * pageSize + 1;
|
|
|
|
|
const end = Math.min((pageIndex + 1) * pageSize, totalRows);
|
|
|
|
|
|
2025-10-16 11:18:55 +00:00
|
|
|
return (
|
2025-10-18 19:57:52 +00:00
|
|
|
<div className={cn(
|
|
|
|
|
"flex items-center justify-between",
|
|
|
|
|
className
|
|
|
|
|
)}>
|
|
|
|
|
{/* Información izquierda */}
|
|
|
|
|
<div className="flex flex-col sm:flex-row items-center gap-4 flex-1 text-sm text-muted-foreground">
|
|
|
|
|
{/* Rango visible */}
|
|
|
|
|
<span aria-live="polite">
|
|
|
|
|
{t("components.datatable.pagination.showing_range", {
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
total: totalRows,
|
|
|
|
|
})}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Selección de filas */}
|
|
|
|
|
{hasSelected && (
|
|
|
|
|
<span aria-live="polite">
|
|
|
|
|
{t("components.datatable.pagination.rows_selected", {
|
|
|
|
|
count: table.getFilteredSelectedRowModel().rows.length,
|
|
|
|
|
total: table.getFilteredRowModel().rows.length,
|
|
|
|
|
})}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="text-sm text-muted-foreground text-nowrap">
|
|
|
|
|
{t("components.datatable.pagination.rows_per_page")}
|
|
|
|
|
</span>
|
2025-10-16 11:18:55 +00:00
|
|
|
<Select
|
2025-10-18 19:57:52 +00:00
|
|
|
value={String(pageSize)}
|
|
|
|
|
onValueChange={(value) => table.setPageSize(Number(value))}
|
2025-10-16 11:18:55 +00:00
|
|
|
>
|
2025-10-18 19:57:52 +00:00
|
|
|
<SelectTrigger className="w-20 h-8 bg-white border-gray-200">
|
|
|
|
|
<SelectValue placeholder={String(pageSize)} />
|
2025-10-16 11:18:55 +00:00
|
|
|
</SelectTrigger>
|
2025-10-18 19:57:52 +00:00
|
|
|
<SelectContent>
|
|
|
|
|
{[10, 20, 25, 30, 40, 50].map((size) => (
|
|
|
|
|
<SelectItem key={size} value={String(size)}>
|
|
|
|
|
{size}
|
2025-10-16 11:18:55 +00:00
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
2025-10-18 19:57:52 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Controles derecha */}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Pagination>
|
|
|
|
|
<PaginationContent>
|
|
|
|
|
{/* Primera página */}
|
|
|
|
|
<PaginationItem>
|
|
|
|
|
<PaginationLink
|
|
|
|
|
aria-label={t("components.datatable.pagination.goto_first_page")}
|
|
|
|
|
onClick={() => table.setPageIndex(0)}
|
|
|
|
|
isActive={!table.getCanPreviousPage()}
|
|
|
|
|
size="sm"
|
|
|
|
|
className="px-2.5"
|
|
|
|
|
>
|
|
|
|
|
<ChevronsLeftIcon className="size-4" />
|
|
|
|
|
</PaginationLink>
|
|
|
|
|
</PaginationItem>
|
|
|
|
|
|
|
|
|
|
{/* Anterior */}
|
|
|
|
|
<PaginationItem>
|
|
|
|
|
<PaginationLink
|
|
|
|
|
aria-label={t("components.datatable.pagination.goto_previous_page")}
|
|
|
|
|
onClick={() => table.previousPage()}
|
|
|
|
|
isActive={!table.getCanPreviousPage()}
|
|
|
|
|
size="sm"
|
|
|
|
|
className="px-2.5"
|
|
|
|
|
>
|
|
|
|
|
<ChevronLeftIcon className="size-4" />
|
|
|
|
|
</PaginationLink>
|
|
|
|
|
</PaginationItem>
|
|
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
className="text-sm text-muted-foreground px-2"
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
>
|
|
|
|
|
{t("components.datatable.pagination.page_of", {
|
|
|
|
|
page: pageIndex + 1,
|
|
|
|
|
of: pageCount || 1,
|
|
|
|
|
})}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Siguiente */}
|
|
|
|
|
<PaginationItem>
|
|
|
|
|
<PaginationLink
|
|
|
|
|
aria-label={t("components.datatable.pagination.goto_next_page")}
|
|
|
|
|
onClick={() => table.nextPage()}
|
|
|
|
|
isActive={!table.getCanNextPage()}
|
|
|
|
|
size="sm"
|
|
|
|
|
className="px-2.5"
|
|
|
|
|
>
|
|
|
|
|
<ChevronRightIcon className="size-4" />
|
|
|
|
|
</PaginationLink>
|
|
|
|
|
</PaginationItem>
|
|
|
|
|
|
|
|
|
|
{/* Última página */}
|
|
|
|
|
<PaginationItem>
|
|
|
|
|
<PaginationLink
|
|
|
|
|
aria-label={t("components.datatable.pagination.goto_last_page")}
|
|
|
|
|
onClick={() => table.setPageIndex(pageCount - 1)}
|
|
|
|
|
isActive={!table.getCanNextPage()}
|
|
|
|
|
size="sm"
|
|
|
|
|
className="px-2.5"
|
|
|
|
|
>
|
|
|
|
|
<ChevronsRightIcon className="size-4" />
|
|
|
|
|
</PaginationLink>
|
|
|
|
|
</PaginationItem>
|
|
|
|
|
</PaginationContent>
|
|
|
|
|
</Pagination>
|
2025-10-16 11:18:55 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-10-18 19:57:52 +00:00
|
|
|
);
|
|
|
|
|
}
|