evaluations-table.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { useEffect, useState } from 'react'
  2. import {
  3. type SortingState,
  4. type VisibilityState,
  5. flexRender,
  6. getCoreRowModel,
  7. getFacetedRowModel,
  8. getFacetedUniqueValues,
  9. useReactTable,
  10. } from '@tanstack/react-table'
  11. import { cn } from '@/lib/utils'
  12. import { type NavigateFn, useTableUrlState } from '@/hooks/use-table-url-state'
  13. import {
  14. Table,
  15. TableBody,
  16. TableCell,
  17. TableHead,
  18. TableHeader,
  19. TableRow,
  20. } from '@/components/ui/table'
  21. import { DataTablePagination, DataTableToolbar } from '@/components/data-table'
  22. import { DataTableBulkActions } from './data-table-bulk-actions'
  23. import { evaluationColumns as columns } from './evaluations-columns'
  24. import type { Pagination } from '@/types/common.types'
  25. import type { Evaluation } from '@/types/evaluations.types'
  26. type DataTableProps = {
  27. data: Evaluation[]
  28. paginationData?: Pagination
  29. search: Record<string, unknown>
  30. navigate: NavigateFn
  31. }
  32. export function EvaluationsTable({
  33. data,
  34. paginationData,
  35. search,
  36. navigate,
  37. }: DataTableProps) {
  38. // Local UI-only states
  39. const [rowSelection, setRowSelection] = useState({})
  40. const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
  41. status: false,
  42. })
  43. const [sorting, setSorting] = useState<SortingState>([])
  44. // Local state management for table (uncomment to use local-only state, not synced with URL)
  45. // const [columnFilters, onColumnFiltersChange] = useState<ColumnFiltersState>([])
  46. // const [pagination, onPaginationChange] = useState<PaginationState>({ pageIndex: 0, pageSize: 10 })
  47. // Synced with URL states (keys/defaults mirror users route search schema)
  48. const {
  49. columnFilters,
  50. onColumnFiltersChange,
  51. pagination,
  52. onPaginationChange,
  53. ensurePageInRange,
  54. } = useTableUrlState({
  55. search,
  56. navigate,
  57. pagination: { defaultPage: 1, defaultPageSize: 10 },
  58. globalFilter: { enabled: false },
  59. columnFilters: [
  60. // username per-column text filter
  61. { columnId: 'username', searchKey: 'username', type: 'string' },
  62. { columnId: 'status', searchKey: 'status', type: 'array' },
  63. { columnId: 'role', searchKey: 'role', type: 'array' },
  64. {
  65. columnId: 'status',
  66. searchKey: 'status',
  67. type: 'string',
  68. },
  69. ],
  70. })
  71. // eslint-disable-next-line react-hooks/incompatible-library
  72. const table = useReactTable({
  73. data,
  74. columns,
  75. state: {
  76. sorting,
  77. pagination,
  78. rowSelection,
  79. columnFilters,
  80. columnVisibility,
  81. },
  82. pageCount: paginationData?.last_page ?? -1,
  83. manualPagination: true,
  84. manualFiltering: true,
  85. manualSorting: true,
  86. enableRowSelection: true,
  87. onPaginationChange,
  88. onColumnFiltersChange,
  89. onRowSelectionChange: setRowSelection,
  90. onSortingChange: setSorting,
  91. onColumnVisibilityChange: setColumnVisibility,
  92. getCoreRowModel: getCoreRowModel(),
  93. getFacetedRowModel: getFacetedRowModel(),
  94. getFacetedUniqueValues: getFacetedUniqueValues(),
  95. })
  96. useEffect(() => {
  97. ensurePageInRange(table.getPageCount())
  98. }, [table, ensurePageInRange])
  99. return (
  100. <div
  101. className={cn(
  102. 'max-sm:has-[div[role="toolbar"]]:mb-16', // Add margin bottom to the table on mobile when the toolbar is visible
  103. 'flex flex-1 flex-col gap-4'
  104. )}
  105. >
  106. <DataTableToolbar
  107. table={table}
  108. searchPlaceholder='فیلتر کاربران...'
  109. searchKey='username'
  110. filters={[
  111. {
  112. columnId: 'status',
  113. title: 'وضعیت ارزیابی',
  114. multiple: false,
  115. options: [
  116. { label: 'تکمیل شده', value: 'approved' },
  117. { label: 'در انتظار تایید', value: 'approved_ai' },
  118. { label: 'در حال بررسی', value: 'pending' },
  119. { label: 'رد شده', value: 'rejected' },
  120. ],
  121. },
  122. ]}
  123. />
  124. <div className='overflow-hidden rounded-md border'>
  125. <Table>
  126. <TableHeader>
  127. {table.getHeaderGroups().map((headerGroup) => (
  128. <TableRow key={headerGroup.id} className='group/row'>
  129. {headerGroup.headers.map((header) => {
  130. return (
  131. <TableHead
  132. key={header.id}
  133. colSpan={header.colSpan}
  134. className={cn(
  135. 'bg-background group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted',
  136. header.column.columnDef.meta?.className,
  137. header.column.columnDef.meta?.thClassName
  138. )}
  139. >
  140. {header.isPlaceholder
  141. ? null
  142. : flexRender(
  143. header.column.columnDef.header,
  144. header.getContext()
  145. )}
  146. </TableHead>
  147. )
  148. })}
  149. </TableRow>
  150. ))}
  151. </TableHeader>
  152. <TableBody>
  153. {table.getRowModel().rows?.length ? (
  154. table.getRowModel().rows.map((row) => (
  155. <TableRow
  156. key={row.id}
  157. data-state={row.getIsSelected() && 'selected'}
  158. className='group/row'
  159. >
  160. {row.getVisibleCells().map((cell) => (
  161. <TableCell
  162. key={cell.id}
  163. className={cn(
  164. 'bg-background group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted',
  165. cell.column.columnDef.meta?.className,
  166. cell.column.columnDef.meta?.tdClassName
  167. )}
  168. >
  169. {flexRender(
  170. cell.column.columnDef.cell,
  171. cell.getContext()
  172. )}
  173. </TableCell>
  174. ))}
  175. </TableRow>
  176. ))
  177. ) : (
  178. <TableRow>
  179. <TableCell
  180. colSpan={columns?.length}
  181. className='h-24 text-center'
  182. >
  183. نتیجه ‌ای یافت نشد.
  184. </TableCell>
  185. </TableRow>
  186. )}
  187. </TableBody>
  188. </Table>
  189. </div>
  190. <DataTablePagination table={table} className='mt-auto' />
  191. <DataTableBulkActions table={table} />
  192. </div>
  193. )
  194. }