Files
supabase/apps/studio/components/interfaces/DataTableDemo/default/data-table.tsx
T
2025-04-15 23:15:16 +08:00

185 lines
6.3 KiB
TypeScript

'use client'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../components/custom/table'
import { DataTableFilterCommand } from '../components/data-table/data-table-filter-command'
import { DataTableFilterControls } from '../components/data-table/data-table-filter-controls'
import { DataTablePagination } from '../components/data-table/data-table-pagination'
import { DataTableProvider } from '../components/data-table/data-table-provider'
import { DataTableToolbar } from '../components/data-table/data-table-toolbar'
import type { DataTableFilterField } from '../components/data-table/types'
import { useLocalStorage } from '../hooks/use-local-storage'
import { cn } from '../lib/utils'
import type {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
Table as TTable,
VisibilityState,
} from '@tanstack/react-table'
import {
flexRender,
getCoreRowModel,
getFacetedMinMaxValues,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table'
import { useQueryStates } from 'nuqs'
import * as React from 'react'
import { searchParamsParser } from 'components/interfaces/DataTableDemo/default/search-params'
export interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
defaultColumnFilters?: ColumnFiltersState
// TODO: add sortingColumnFilters
filterFields?: DataTableFilterField<TData>[]
}
export function DataTable<TData, TValue>({
columns,
data,
defaultColumnFilters = [],
filterFields = [],
}: DataTableProps<TData, TValue>) {
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultColumnFilters)
const [sorting, setSorting] = React.useState<SortingState>([])
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>(
'data-table-visibility',
{}
)
const [_, setSearch] = useQueryStates(searchParamsParser)
const table = useReactTable({
data,
columns,
state: { columnFilters, sorting, columnVisibility, pagination },
onColumnVisibilityChange: setColumnVisibility,
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
onPaginationChange: setPagination,
getSortedRowModel: getSortedRowModel(),
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedMinMaxValues: getFacetedMinMaxValues(),
// REMINDER: it doesn't support array of strings (WARNING: might not work for other types)
getFacetedUniqueValues: (table: TTable<TData>, columnId: string) => () => {
const facets = getFacetedUniqueValues<TData>()(table, columnId)()
const customFacets = new Map()
for (const [key, value] of facets as any) {
if (Array.isArray(key)) {
for (const k of key) {
const prevValue = customFacets.get(k) || 0
customFacets.set(k, prevValue + value)
}
} else {
const prevValue = customFacets.get(key) || 0
customFacets.set(key, prevValue + value)
}
}
return customFacets
},
})
React.useEffect(() => {
const columnFiltersWithNullable = filterFields.map((field) => {
const filterValue = columnFilters.find((filter) => filter.id === field.value)
if (!filterValue) return { id: field.value, value: null }
return { id: field.value, value: filterValue.value }
})
const search = columnFiltersWithNullable.reduce(
(prev, curr) => {
prev[curr.id as string] = curr.value
return prev
},
{} as Record<string, unknown>
)
setSearch(search)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columnFilters])
return (
<DataTableProvider
table={table}
columns={columns}
filterFields={filterFields}
columnFilters={columnFilters}
sorting={sorting}
pagination={pagination}
>
<div className="flex h-full w-full flex-col gap-3 sm:flex-row">
<div
className={cn(
'hidden w-full p-1 sm:block sm:min-w-52 sm:max-w-52 sm:self-start md:min-w-64 md:max-w-64',
'group-data-[expanded=false]/controls:hidden'
)}
>
<DataTableFilterControls />
</div>
<div className="flex max-w-full flex-1 flex-col gap-4 overflow-hidden p-1">
<DataTableFilterCommand searchParamsParser={searchParamsParser} />
<DataTableToolbar />
<div className="rounded-md border">
<Table>
<TableHeader className="bg-muted/50">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="hover:bg-transparent">
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{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'}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination />
</div>
</div>
</DataTableProvider>
)
}