277 lines
7.2 KiB
TypeScript
277 lines
7.2 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,
|
|
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
|
|
columnInvisibilityInit?: string[]
|
|
}
|
|
|
|
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,
|
|
columnInvisibilityInit = []
|
|
}: DataTableProps<TData, TValue>) {
|
|
const [dataTable, setDataTable] = useState<TData[]>(data)
|
|
const columnInvisibility = useMemo<VisibilityState>(
|
|
() =>
|
|
Object.fromEntries(
|
|
columnInvisibilityInit.map((column) => [column, false])
|
|
),
|
|
[columnInvisibilityInit]
|
|
)
|
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
const [columnVisibility, setColumnVisibility] =
|
|
useState<VisibilityState>(columnInvisibility)
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
|
|
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]
|
|
)
|
|
useEffect(() => {
|
|
setDataTable(data)
|
|
}, [data])
|
|
|
|
const tableConfig = useMemo(
|
|
() => ({
|
|
data: dataTable,
|
|
columns,
|
|
rowCount,
|
|
state: {
|
|
sorting,
|
|
rowSelection,
|
|
columnVisibility,
|
|
pagination: {
|
|
pageIndex,
|
|
pageSize
|
|
}
|
|
},
|
|
manualSorting: true,
|
|
manualPagination: true,
|
|
enableRowSelection: true,
|
|
getRowId: (row: any) => row.id,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
onRowSelectionChange: setRowSelection,
|
|
onSortingChange: setSorting,
|
|
onColumnVisibilityChange: setColumnVisibility,
|
|
onPaginationChange: setPagination,
|
|
meta: {
|
|
removeRow: (rowId: string) => {
|
|
setDataTable((rows) => rows.filter((row: any) => row?.id !== rowId))
|
|
}
|
|
}
|
|
}),
|
|
[
|
|
dataTable,
|
|
columns,
|
|
rowCount,
|
|
sorting,
|
|
rowSelection,
|
|
columnVisibility,
|
|
setColumnVisibility,
|
|
pageIndex,
|
|
pageSize,
|
|
setSorting,
|
|
setPagination
|
|
]
|
|
)
|
|
|
|
const table = useReactTable(tableConfig)
|
|
|
|
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>
|
|
)
|
|
}
|