add client side pagination
This commit is contained in:
parent
23fd193c34
commit
68bf963d05
8 changed files with 379 additions and 81 deletions
|
|
@ -0,0 +1,83 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { DoubleArrowLeftIcon, ChevronLeftIcon, ChevronRightIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons'
|
||||
import { type Table } from '@tanstack/react-table'
|
||||
|
||||
interface DataTablePaginationProps<TData> {
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
const DataTablePagination = <TData,>({ table }: DataTablePaginationProps<TData>) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm font-medium">Rows per page</p>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={value => {
|
||||
table.setPageSize(Number(value))
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
{[10, 20, 30, 40, 50].map(pageSize => (
|
||||
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||
{pageSize}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<DoubleArrowLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<DoubleArrowRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataTablePagination
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { type Table } from '@tanstack/react-table'
|
||||
import SearchForm from './search-form'
|
||||
import { statuses, priorities } from '../models/data'
|
||||
import DataTableFacetedFilter from './data-table-faceted-filter'
|
||||
import DataTableViewOptions from './data-table-view-options'
|
||||
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||
|
||||
interface DataTableToolbarProps<TData> {
|
||||
table: Table<TData>
|
||||
|
|
@ -10,7 +15,31 @@ const DataTableToolbar = <TData,>({ table }: DataTableToolbarProps<TData>) => {
|
|||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<SearchForm isFiltered={isFiltered} table={table} />
|
||||
<div className="flex flex-col w-full rounded-lg bg-white shadow-md p-3 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
value={(table.getColumn('title')?.getFilterValue() as string) ?? ''}
|
||||
onChange={event => table.getColumn('title')?.setFilterValue(event.target.value)}
|
||||
className="h-8 w-[150px] lg:w-[250px]"
|
||||
/>
|
||||
{table.getColumn('status') && (
|
||||
<DataTableFacetedFilter column={table.getColumn('status')} title="Status" options={statuses} />
|
||||
)}
|
||||
{table.getColumn('priority') && (
|
||||
<DataTableFacetedFilter column={table.getColumn('priority')} title="Priority" options={priorities} />
|
||||
)}
|
||||
{isFiltered && (
|
||||
<Button variant="ghost" onClick={() => table.resetColumnFilters()} className="h-8 px-2 lg:px-3">
|
||||
Reset
|
||||
<Cross2Icon className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DataTableViewOptions table={table} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,15 @@ import {
|
|||
import { useState } from 'react'
|
||||
import DataTableToolbar from './data-table-toolbar'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import DataTablePagination from './data-table-pagination'
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const DataTable = <TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) => {
|
||||
const DataTable = <TData, TValue>({ columns, data, isLoading }: DataTableProps<TData, TValue>) => {
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||
|
|
@ -73,6 +75,12 @@ const DataTable = <TData, TValue>({ columns, data }: DataTableProps<TData, TValu
|
|||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
|
|
@ -83,7 +91,7 @@ const DataTable = <TData, TValue>({ columns, data }: DataTableProps<TData, TValu
|
|||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{/* <DataTablePagination table={table} /> */}
|
||||
<DataTablePagination table={table} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Cross2Icon, MixerVerticalIcon, PlusIcon, Share2Icon } from '@radix-ui/react-icons'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import DataTableFacetedFilter from './data-table-faceted-filter'
|
||||
import { type Table } from '@tanstack/react-table'
|
||||
import { priorities, statuses } from '../models/data'
|
||||
import DataTableViewOptions from './data-table-view-options'
|
||||
|
||||
interface SearchFromProps<TData> {
|
||||
isFiltered: boolean
|
||||
table: Table<TData>
|
||||
}
|
||||
|
||||
const SearchForm = <TData,>({ isFiltered, table }: SearchFromProps<TData>) => {
|
||||
return (
|
||||
<div className="flex flex-col w-full rounded-lg bg-white shadow-xl p-3 space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-700 text-sm font-semibold px-4 py-2">Search</span>
|
||||
<div className="flex justify-center items-center space-x-3">
|
||||
<Button variant={'outline'} className="px-4 py-2 text-sm">
|
||||
View JSON
|
||||
</Button>
|
||||
<Button variant={'outline'} className="px-4 py-2 text-sm">
|
||||
<Share2Icon className="inline-block w-4 h-4 mr-2" />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex space-x-3">
|
||||
<div className="flex w-80">
|
||||
<Input placeholder="Search..." className="flex-grow rounded-tr-none rounded-br-none text-sm" />
|
||||
<Button className="px-4 py-2 text-sm rounded-tl-none rounded-bl-none">Search</Button>
|
||||
</div>
|
||||
<Button variant={'outline'} className="px-4 py-2 text-sm">
|
||||
<MixerVerticalIcon className="inline-block w-4 h-4 mr-2" />
|
||||
Filter
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Button className="px-4 py-2 text-sm">
|
||||
<PlusIcon className="inline-block w-4 h-4 mr-2 text-white" />
|
||||
Add new recipe
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
value={(table.getColumn('title')?.getFilterValue() as string) ?? ''}
|
||||
onChange={event => table.getColumn('title')?.setFilterValue(event.target.value)}
|
||||
className="h-8 w-[150px] lg:w-[250px]"
|
||||
/>
|
||||
{table.getColumn('status') && (
|
||||
<DataTableFacetedFilter column={table.getColumn('status')} title="Status" options={statuses} />
|
||||
)}
|
||||
{table.getColumn('priority') && (
|
||||
<DataTableFacetedFilter column={table.getColumn('priority')} title="Priority" options={priorities} />
|
||||
)}
|
||||
{isFiltered && (
|
||||
<Button variant="ghost" onClick={() => table.resetColumnFilters()} className="h-8 px-2 lg:px-3">
|
||||
Reset
|
||||
<Cross2Icon className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DataTableViewOptions table={table} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchForm
|
||||
|
|
@ -15,10 +15,12 @@ async function getTasks() {
|
|||
|
||||
const RecipesPage = () => {
|
||||
const [tasks, setTasks] = useState<Task[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
getTasks().then(tasks => {
|
||||
setTasks(tasks)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ const RecipesPage = () => {
|
|||
<h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
|
||||
</section>
|
||||
<section>
|
||||
<DataTable data={tasks} columns={columns} />
|
||||
<DataTable data={tasks} columns={columns} isLoading={loading} />
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue