tasks-table.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { useEffect, useState } from 'react'
  2. import { getRouteApi } from '@tanstack/react-router'
  3. import {
  4. type SortingState,
  5. type VisibilityState,
  6. flexRender,
  7. getCoreRowModel,
  8. getFacetedRowModel,
  9. getFacetedUniqueValues,
  10. getFilteredRowModel,
  11. getPaginationRowModel,
  12. getSortedRowModel,
  13. useReactTable,
  14. } from '@tanstack/react-table'
  15. import { cn } from '@/lib/utils'
  16. import { useTableUrlState } from '@/hooks/use-table-url-state'
  17. import {
  18. Table,
  19. TableBody,
  20. TableCell,
  21. TableHead,
  22. TableHeader,
  23. TableRow,
  24. } from '@/components/ui/table'
  25. import { DataTablePagination, DataTableToolbar } from '@/components/data-table'
  26. import { priorities, statuses } from '../data/data'
  27. import { type Task } from '../data/schema'
  28. import { DataTableBulkActions } from './data-table-bulk-actions'
  29. import { tasksColumns as columns } from './tasks-columns'
  30. const route = getRouteApi('/_authenticated/tasks/')
  31. type DataTableProps = {
  32. data: Task[]
  33. }
  34. export function TasksTable({ data }: DataTableProps) {
  35. // Local UI-only states
  36. const [rowSelection, setRowSelection] = useState({})
  37. const [sorting, setSorting] = useState<SortingState>([])
  38. const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
  39. // Local state management for table (uncomment to use local-only state, not synced with URL)
  40. // const [globalFilter, onGlobalFilterChange] = useState('')
  41. // const [columnFilters, onColumnFiltersChange] = useState<ColumnFiltersState>([])
  42. // const [pagination, onPaginationChange] = useState<PaginationState>({ pageIndex: 0, pageSize: 10 })
  43. // Synced with URL states (updated to match route search schema defaults)
  44. const {
  45. globalFilter,
  46. onGlobalFilterChange,
  47. columnFilters,
  48. onColumnFiltersChange,
  49. pagination,
  50. onPaginationChange,
  51. ensurePageInRange,
  52. } = useTableUrlState({
  53. search: route.useSearch(),
  54. navigate: route.useNavigate(),
  55. pagination: { defaultPage: 1, defaultPageSize: 10 },
  56. globalFilter: { enabled: true, key: 'filter' },
  57. columnFilters: [
  58. { columnId: 'status', searchKey: 'status', type: 'array' },
  59. { columnId: 'priority', searchKey: 'priority', type: 'array' },
  60. ],
  61. })
  62. // eslint-disable-next-line react-hooks/incompatible-library
  63. const table = useReactTable({
  64. data,
  65. columns,
  66. state: {
  67. sorting,
  68. columnVisibility,
  69. rowSelection,
  70. columnFilters,
  71. globalFilter,
  72. pagination,
  73. },
  74. enableRowSelection: true,
  75. onRowSelectionChange: setRowSelection,
  76. onSortingChange: setSorting,
  77. onColumnVisibilityChange: setColumnVisibility,
  78. globalFilterFn: (row, _columnId, filterValue) => {
  79. const id = String(row.getValue('id')).toLowerCase()
  80. const title = String(row.getValue('title')).toLowerCase()
  81. const searchValue = String(filterValue).toLowerCase()
  82. return id.includes(searchValue) || title.includes(searchValue)
  83. },
  84. getCoreRowModel: getCoreRowModel(),
  85. getFilteredRowModel: getFilteredRowModel(),
  86. getPaginationRowModel: getPaginationRowModel(),
  87. getSortedRowModel: getSortedRowModel(),
  88. getFacetedRowModel: getFacetedRowModel(),
  89. getFacetedUniqueValues: getFacetedUniqueValues(),
  90. onPaginationChange,
  91. onGlobalFilterChange,
  92. onColumnFiltersChange,
  93. })
  94. const pageCount = table.getPageCount()
  95. useEffect(() => {
  96. ensurePageInRange(pageCount)
  97. }, [pageCount, ensurePageInRange])
  98. return (
  99. <div
  100. className={cn(
  101. 'max-sm:has-[div[role="toolbar"]]:mb-16', // Add margin bottom to the table on mobile when the toolbar is visible
  102. 'flex flex-1 flex-col gap-4'
  103. )}
  104. >
  105. <DataTableToolbar
  106. table={table}
  107. searchPlaceholder='Filter by title or ID...'
  108. filters={[
  109. {
  110. columnId: 'status',
  111. title: 'Status',
  112. options: statuses,
  113. },
  114. {
  115. columnId: 'priority',
  116. title: 'Priority',
  117. options: priorities,
  118. },
  119. ]}
  120. />
  121. <div className='overflow-hidden rounded-md border'>
  122. <Table>
  123. <TableHeader>
  124. {table.getHeaderGroups().map((headerGroup) => (
  125. <TableRow key={headerGroup.id}>
  126. {headerGroup.headers.map((header) => {
  127. return (
  128. <TableHead
  129. key={header.id}
  130. colSpan={header.colSpan}
  131. className={cn(
  132. header.column.columnDef.meta?.className,
  133. header.column.columnDef.meta?.thClassName
  134. )}
  135. >
  136. {header.isPlaceholder
  137. ? null
  138. : flexRender(
  139. header.column.columnDef.header,
  140. header.getContext()
  141. )}
  142. </TableHead>
  143. )
  144. })}
  145. </TableRow>
  146. ))}
  147. </TableHeader>
  148. <TableBody>
  149. {table.getRowModel().rows?.length ? (
  150. table.getRowModel().rows.map((row) => (
  151. <TableRow
  152. key={row.id}
  153. data-state={row.getIsSelected() && 'selected'}
  154. >
  155. {row.getVisibleCells().map((cell) => (
  156. <TableCell
  157. key={cell.id}
  158. className={cn(
  159. cell.column.columnDef.meta?.className,
  160. cell.column.columnDef.meta?.tdClassName
  161. )}
  162. >
  163. {flexRender(
  164. cell.column.columnDef.cell,
  165. cell.getContext()
  166. )}
  167. </TableCell>
  168. ))}
  169. </TableRow>
  170. ))
  171. ) : (
  172. <TableRow>
  173. <TableCell
  174. colSpan={columns.length}
  175. className='h-24 text-center'
  176. >
  177. No results.
  178. </TableCell>
  179. </TableRow>
  180. )}
  181. </TableBody>
  182. </Table>
  183. </div>
  184. <DataTablePagination table={table} className='mt-auto' />
  185. <DataTableBulkActions table={table} />
  186. </div>
  187. )
  188. }