add client side pagination

This commit is contained in:
Kenta420 2024-01-24 17:21:33 +07:00
parent 23fd193c34
commit 68bf963d05
8 changed files with 379 additions and 81 deletions

View file

@ -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

View file

@ -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>
)
}

View file

@ -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>
)
}

View file

@ -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

View file

@ -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>
)