94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
'use client'
|
|
import * as React from 'react'
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table'
|
|
import { ReactNode, useState } from 'react'
|
|
import { ArrowUpDownIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
|
|
import { ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
type Data = Record<string, unknown>
|
|
|
|
type Column = {
|
|
label: string
|
|
props?: string
|
|
render?: (val: Data) => ReactNode
|
|
sortable?: boolean
|
|
}
|
|
|
|
export function DataTable<T extends Data>(props: {
|
|
data: T[]
|
|
columns: Column[]
|
|
pinFirst?: boolean
|
|
}) {
|
|
const table = useReactTable({
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
data: props.data,
|
|
columns: props.columns.map(col => ({
|
|
meta: col,
|
|
header: col.label,
|
|
accessorKey: col.props,
|
|
cell: info => col.render?.(info.row.original) || String(info.getValue()),
|
|
enableSorting: col.sortable,
|
|
})) as ColumnDef<T>[],
|
|
})
|
|
|
|
return (
|
|
<Table>
|
|
<TableHeader>
|
|
|
|
{/* 表头行 */}
|
|
{table.getHeaderGroups().map(group => (
|
|
<TableRow key={group.id}>
|
|
|
|
{/* 表头 */}
|
|
{group.headers.map((header, index) => (
|
|
<TableHead
|
|
key={header.id}
|
|
className={cn(
|
|
header.column.columnDef.enableSorting && 'hover:bg-gray-200 transition-colors duration-150 ease-in-out cursor-pointer',
|
|
header.column.getIsSorted() && 'text-primary',
|
|
props.pinFirst && index === 0 && 'sticky left-0 bg-gray-50 border-r',
|
|
)}
|
|
onClick={header.column.getToggleSortingHandler()}>
|
|
<div className="flex flex-row items-center justify-between">
|
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
{header.column.columnDef.enableSorting && (
|
|
header.column.getIsSorted() == 'asc' ? (
|
|
<ArrowUpIcon className="size-4" />
|
|
) : header.column.getIsSorted() == 'desc' ? (
|
|
<ArrowDownIcon className="size-4" />
|
|
) : (
|
|
<ArrowUpDownIcon className="size-4" />
|
|
)
|
|
)}
|
|
</div>
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
{/* 表格行 */}
|
|
{table.getRowModel().rows.map(row => (
|
|
<TableRow key={row.id}>
|
|
|
|
{/* 表格 */}
|
|
{row.getVisibleCells().map((cell, index) => (
|
|
<TableCell
|
|
key={cell.id}
|
|
className={cn(
|
|
props.pinFirst && index === 0 && 'sticky left-0 bg-white border-r',
|
|
)}>
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
)
|
|
}
|