diff --git a/frontend/src/components/ui/data-table.tsx b/frontend/src/components/ui/data-table.tsx index a311ab5..b12d10b 100644 --- a/frontend/src/components/ui/data-table.tsx +++ b/frontend/src/components/ui/data-table.tsx @@ -6,7 +6,6 @@ import { type OnChangeFn, type PaginationState, type RowSelectionState, - type Table as TableInstance, type VisibilityState, flexRender, getCoreRowModel, @@ -18,6 +17,7 @@ import { Columns3 } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; +import { TablePagination } from "@/components/ui/table-pagination"; import { Table, TableBody, @@ -34,13 +34,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; export interface DataTableProps { columns: ColumnDef[]; @@ -253,90 +246,19 @@ export function DataTable({ {enablePagination && ( - )} ); } -interface PaginationProps { - table: TableInstance; - pageSizeOptions: number[]; - pageCount: number; - manual: boolean; - rowCount?: number; -} - -function DataTablePagination({ - table, - pageSizeOptions, - pageCount, - manual, - rowCount, -}: PaginationProps) { - const pageIndex = table.getState().pagination.pageIndex; - const pageSize = table.getState().pagination.pageSize; - const visibleRows = table.getRowModel().rows.length; - const totalRows = manual ? (rowCount ?? 0) : visibleRows; - - return ( -
-
- {`${totalRows} row${totalRows === 1 ? "" : "s"}`} -
-
-
- Rows per page - -
- - Page {pageIndex + 1} of {pageCount} - -
- - -
-
-
- ); -} +// DataTablePagination was extracted into the shared TablePagination component +// (frontend/src/components/ui/table-pagination.tsx). Both the desktop DataTable +// and the Media mobile card list consume it. diff --git a/frontend/src/components/ui/table-pagination.tsx b/frontend/src/components/ui/table-pagination.tsx new file mode 100644 index 0000000..40de358 --- /dev/null +++ b/frontend/src/components/ui/table-pagination.tsx @@ -0,0 +1,122 @@ +import type { OnChangeFn } from "@tanstack/react-table"; +import type { PaginationState } from "@tanstack/react-table"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +/** + * Shared pagination footer for table-style views. + * + * Renders the rows count, page-size select, page indicator, and prev/next + * buttons. Works off the raw {@link PaginationState} primitives so it can back + * both a TanStack `Table` instance (via a thin adapter) and standalone card + * layouts that drive pagination directly (e.g. MediaMobilePagination). + * + * The Desktop DataTable and the Media mobile card list both consume this to + * avoid the duplication flagged in + * `openspec/changes/mobile-responsive-parity/verify-report.md` residual risk #5. + */ +export interface TablePaginationProps { + pageIndex: number; + pageSize: number; + pageSizeOptions: number[]; + totalRows: number; + pageCount: number; + onPaginationChange: OnChangeFn; + /** Optional extra className on the outer container (e.g. "p-4"). */ + className?: string; +} + +export function TablePagination({ + pageIndex, + pageSize, + pageSizeOptions, + totalRows, + pageCount, + onPaginationChange, + className, +}: TablePaginationProps) { + return ( +
+
+ {`${totalRows} row${totalRows === 1 ? "" : "s"}`} +
+
+
+ Rows per page + +
+ + Page {pageIndex + 1} of {pageCount} + +
+ + +
+
+
+ ); +} diff --git a/frontend/src/pages/Media.tsx b/frontend/src/pages/Media.tsx index c367664..cdf8c8a 100644 --- a/frontend/src/pages/Media.tsx +++ b/frontend/src/pages/Media.tsx @@ -13,6 +13,7 @@ import { MobileCardRow, type MobileCardField, } from "@/components/ui/mobile-card"; +import { TablePagination } from "@/components/ui/table-pagination"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -100,97 +101,8 @@ const mediaCardFields: MobileCardField[] = [ }, ]; -// Standalone pagination for the mobile card layout. The DataTable renders its -// own pagination internally; this mirrors that UI (rows count, page-size -// select, page indicator, prev/next) but works off the raw pagination state -// instead of a TanStack table instance. See spec R3.3. -function MediaMobilePagination({ - pageIndex, - pageSize, - pageSizeOptions, - totalRows, - pageCount, - onPaginationChange, -}: { - pageIndex: number; - pageSize: number; - pageSizeOptions: number[]; - totalRows: number; - pageCount: number; - onPaginationChange: OnChangeFn; -}) { - return ( -
-
- {`${totalRows} row${totalRows === 1 ? "" : "s"}`} -
-
-
- Rows per page - -
- - Page {pageIndex + 1} of {pageCount} - -
- - -
-
-
- ); -} +// Mobile pagination uses the shared TablePagination component +// (frontend/src/components/ui/table-pagination.tsx). const MEDIA_TAB_STATE_KEY = "manage.media.tabState"; const SMALL_BREAKPOINT = "(max-width: 900px)"; @@ -662,13 +574,14 @@ export function Media() { /> {queryResult && ( - )}