298 lines
7.8 KiB
TypeScript
298 lines
7.8 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
useReactTable,
|
|
type ColumnDef,
|
|
type ColumnSort,
|
|
type RowSelectionState,
|
|
type SortingState,
|
|
type Table,
|
|
type VisibilityState
|
|
} from '@tanstack/react-table'
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
type ReactNode
|
|
} from 'react'
|
|
import { useSearchParams } from 'react-router'
|
|
|
|
import { Card, CardContent } from '@repo/ui/components/ui/card'
|
|
import {
|
|
Table as Table_,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow
|
|
} from '@repo/ui/components/ui/table'
|
|
import { cn } from '@repo/ui/lib/utils'
|
|
import { DataTablePagination } from './pagination'
|
|
|
|
interface DataTableProps<TData, TValue> {
|
|
children?: ReactNode
|
|
columns: ColumnDef<TData, TValue>[]
|
|
data: TData[]
|
|
setSelectedRows?: (selectedRows: TData[]) => void
|
|
pageIndex: number
|
|
sort: SortingState
|
|
pageSize: number
|
|
rowCount: number
|
|
columnVisibilityInit?: VisibilityState
|
|
}
|
|
|
|
interface TableContextProps<TData> {
|
|
table: Table<TData>
|
|
}
|
|
|
|
const TableContext = createContext<TableContextProps<any> | null>(null)
|
|
|
|
export function useDataTable<TData>() {
|
|
const ctx = useContext(TableContext)
|
|
|
|
if (!ctx) {
|
|
throw new Error('TableContext is null')
|
|
}
|
|
|
|
return ctx as { table: Table<TData> }
|
|
}
|
|
|
|
export function DataTable<TData, TValue>({
|
|
data,
|
|
children,
|
|
columns,
|
|
sort,
|
|
pageIndex,
|
|
pageSize,
|
|
rowCount,
|
|
setSelectedRows,
|
|
columnVisibilityInit = {}
|
|
}: DataTableProps<TData, TValue>) {
|
|
const [dataTable, setDataTable] = useState<TData[]>(data)
|
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
|
|
const columnVisibilityInit_ = useMemo<VisibilityState>(() => {
|
|
const columns = searchParams.get('columns')
|
|
|
|
if (columns === null) {
|
|
return columnVisibilityInit
|
|
}
|
|
|
|
const columnVisibility = columns ? columns.split(',') : []
|
|
return Object.keys(columnVisibilityInit).reduce<VisibilityState>(
|
|
(acc, col) => ({
|
|
...acc,
|
|
[col]: columnVisibility.includes(col)
|
|
}),
|
|
{}
|
|
)
|
|
}, [])
|
|
|
|
const [columnVisibility, setColumnVisibility_] = useState<VisibilityState>(
|
|
columnVisibilityInit_
|
|
)
|
|
|
|
const sorting = useMemo(() => {
|
|
const sortParam = searchParams.get('sort')
|
|
|
|
if (!sortParam) {
|
|
return sort
|
|
}
|
|
|
|
return sortParam.split(',').map((s) => {
|
|
const [id, dir] = s.split(':')
|
|
return { id, desc: dir === 'desc' }
|
|
})
|
|
}, [searchParams, sort])
|
|
|
|
const setPagination = useCallback(
|
|
(updater: any) => {
|
|
const newState =
|
|
typeof updater === 'function'
|
|
? updater({ pageIndex, pageSize })
|
|
: updater
|
|
|
|
setRowSelection({})
|
|
setSearchParams((prev) => {
|
|
prev.set('p', newState?.pageIndex.toString())
|
|
prev.set('perPage', newState?.pageSize.toString())
|
|
|
|
return prev
|
|
})
|
|
},
|
|
[pageIndex, pageSize, setSearchParams]
|
|
)
|
|
|
|
const setSorting = useCallback(
|
|
(updater: any) => {
|
|
const newSorting =
|
|
typeof updater === 'function' ? updater(sorting) : updater
|
|
|
|
setSearchParams((prev) => {
|
|
if (newSorting.length) {
|
|
const sort = newSorting
|
|
.map((s: ColumnSort) => `${s.id}:${s.desc ? 'desc' : 'asc'}`)
|
|
.join(',')
|
|
prev.set('sort', sort)
|
|
} else {
|
|
prev.delete('sort')
|
|
}
|
|
|
|
return prev
|
|
})
|
|
},
|
|
[sorting, setSearchParams]
|
|
)
|
|
|
|
const setColumnVisibility = useCallback(
|
|
(updater: any) => {
|
|
const newColumnVisibility =
|
|
typeof updater === 'function' ? updater(columnVisibility) : updater
|
|
|
|
setColumnVisibility_(newColumnVisibility)
|
|
|
|
setSearchParams((prev) => {
|
|
const columns = Object.entries(newColumnVisibility)
|
|
.filter(([_, visible]) => visible)
|
|
.map(([col, _]) => col)
|
|
|
|
if (columns.length > 0) {
|
|
prev.set('columns', columns.join(','))
|
|
} else {
|
|
prev.set('columns', '')
|
|
}
|
|
|
|
return prev
|
|
})
|
|
},
|
|
[setSearchParams, setColumnVisibility_]
|
|
)
|
|
|
|
useEffect(() => {
|
|
setDataTable(data)
|
|
}, [data])
|
|
|
|
const getRowId = useCallback((row: any) => row.id, [])
|
|
const removeRow = useCallback((rowId: string) => {
|
|
setDataTable((rows) => rows.filter((row: any) => row.id !== rowId))
|
|
}, [])
|
|
|
|
const table = useReactTable({
|
|
data: dataTable,
|
|
columns,
|
|
rowCount,
|
|
state: {
|
|
sorting,
|
|
rowSelection,
|
|
columnVisibility,
|
|
pagination: { pageIndex, pageSize }
|
|
},
|
|
manualSorting: true,
|
|
manualPagination: true,
|
|
enableRowSelection: true,
|
|
getRowId,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
onRowSelectionChange: setRowSelection,
|
|
onSortingChange: setSorting,
|
|
onColumnVisibilityChange: setColumnVisibility,
|
|
onPaginationChange: setPagination,
|
|
meta: { removeRow }
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!setSelectedRows) return
|
|
|
|
const selected = table.getSelectedRowModel().flatRows.map((r) => r.original)
|
|
setSelectedRows(selected)
|
|
}, [rowSelection, setSelectedRows, table])
|
|
|
|
return (
|
|
<TableContext value={{ table }}>
|
|
<div className="space-y-2.5 max-md:mb-2">
|
|
<Card className="relative w-full overflow-auto">
|
|
<CardContent>
|
|
{children}
|
|
|
|
<Table_ className="table-auto">
|
|
<TableHeader>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<TableRow
|
|
key={headerGroup.id}
|
|
className="hover:bg-transparent"
|
|
>
|
|
{headerGroup.headers.map((header) => {
|
|
return (
|
|
<TableHead
|
|
key={header.id}
|
|
className={cn(
|
|
'p-2.5',
|
|
// @ts-ignore
|
|
header.column.columnDef.meta?.className
|
|
)}
|
|
>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)}
|
|
</TableHead>
|
|
)
|
|
})}
|
|
</TableRow>
|
|
))}
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
{table.getRowModel().rows?.length ? (
|
|
table.getRowModel().rows.map((row) => (
|
|
<TableRow
|
|
key={row.id}
|
|
data-state={row.getIsSelected() && 'selected'}
|
|
className="has-[[data-state=open]]:bg-muted/50"
|
|
>
|
|
{row.getVisibleCells().map((cell) => (
|
|
<TableCell
|
|
key={cell.id}
|
|
className={cn(
|
|
'p-2.5',
|
|
// @ts-ignore
|
|
cell.column.columnDef.meta?.className
|
|
)}
|
|
>
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext()
|
|
)}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))
|
|
) : (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={columns.length}
|
|
className="h-24 text-center"
|
|
>
|
|
Nenhum resultado.
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table_>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<DataTablePagination table={table} />
|
|
</div>
|
|
</TableContext>
|
|
)
|
|
}
|