"use client" import * as React from "react" import { ColumnDef, ColumnFiltersState, ColumnSizingState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable, VisibilityState, } from "@tanstack/react-table" import { IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconLayoutColumns, IconPlus, IconDownload, IconSearch, IconLoader2, } from "@tabler/icons-react" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import type { Endpoint } from "@/types/endpoint.types" interface EndpointsDataTableProps { columns: ColumnDef[] data: TData[] searchPlaceholder?: string searchColumn?: string searchValue?: string onSearch?: (value: string) => void isSearching?: boolean onAddNew?: () => void addButtonText?: string onSelectionChange?: (selectedRows: TData[]) => void pagination?: { pageIndex: number; pageSize: number } onPaginationChange?: (pagination: { pageIndex: number; pageSize: number }) => void totalCount?: number totalPages?: number onDownloadAll?: () => void onDownloadSelected?: () => void } export function EndpointsDataTable({ columns, data, searchPlaceholder = "搜索主机名...", searchColumn = "url", searchValue, onSearch, isSearching = false, onAddNew, addButtonText = "Add", onSelectionChange, pagination: externalPagination, onPaginationChange, totalCount, totalPages, onDownloadAll, onDownloadSelected, }: EndpointsDataTableProps) { const [rowSelection, setRowSelection] = React.useState>({}) const [columnVisibility, setColumnVisibility] = React.useState({}) const [columnFilters, setColumnFilters] = React.useState([]) const [columnSizing, setColumnSizing] = React.useState({}) const [sorting, setSorting] = React.useState([]) const [internalPagination, setInternalPagination] = React.useState<{ pageIndex: number, pageSize: number }>({ pageIndex: 0, pageSize: 10, }) // 本地搜索输入状态(只在回车或点击按钮时触发搜索) const [localSearchValue, setLocalSearchValue] = React.useState(searchValue ?? "") React.useEffect(() => { setLocalSearchValue(searchValue ?? "") }, [searchValue]) const handleSearchSubmit = () => { if (onSearch) { onSearch(localSearchValue) } else { table.getColumn(searchColumn)?.setFilterValue(localSearchValue) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSearchSubmit() } } const pagination = externalPagination || internalPagination const handlePaginationChange = React.useCallback((updaterOrValue: { pageIndex: number; pageSize: number } | ((prev: { pageIndex: number; pageSize: number }) => { pageIndex: number; pageSize: number })) => { if (onPaginationChange) { if (typeof updaterOrValue === 'function') { const newPagination = updaterOrValue(pagination) onPaginationChange(newPagination) } else { onPaginationChange(updaterOrValue) } } else { setInternalPagination(updaterOrValue) } }, [onPaginationChange, pagination]) const table = useReactTable({ data, columns, state: { sorting, columnVisibility, rowSelection, columnFilters, columnSizing, pagination, }, getRowId: (row) => row.id.toString(), enableRowSelection: true, enableColumnResizing: true, columnResizeMode: 'onChange', onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onColumnSizingChange: setColumnSizing, onPaginationChange: handlePaginationChange, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: externalPagination ? undefined : getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), manualPagination: !!externalPagination, pageCount: totalPages, }) React.useEffect(() => { if (onSelectionChange) { const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original) onSelectionChange(selectedRows) } }, [rowSelection, onSelectionChange, table]) return (
setLocalSearchValue(e.target.value)} onKeyDown={handleKeyDown} className="h-8 max-w-sm" />
{table .getAllColumns() .filter( (column) => typeof column.accessorFn !== "undefined" && column.getCanHide() ) .map((column) => { return ( column.toggleVisibility(!!value)} > {column.id === "id" && "ID"} {column.id === "url" && "URL"} {column.id === "endpoint" && "Endpoint"} {column.id === "method" && "Method"} {column.id === "statusCode" && "Status"} {column.id === "title" && "Title"} {column.id === "contentLength" && "Size"} {column.id === "contentType" && "Content Type"} {column.id === "responseTime" && "Response time"} {column.id === "tags" && "Tags"} {column.id === "createdAt" && "Created At"} {column.id === "updatedAt" && "Updated At"} {!["id", "url", "endpoint", "method", "statusCode", "title", "contentLength", "contentType", "responseTime", "tags", "createdAt", "updatedAt"].includes(column.id) && column.id} ) })} {(onDownloadAll || onDownloadSelected) && ( Download Options {onDownloadAll && ( Download All Endpoints )} {onDownloadSelected && ( Download Selected Endpoints )} )} {onAddNew && ( )}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} {header.column.getCanResize() && (
header.column.resetSize()} className="absolute -right-2.5 top-0 h-full w-5 cursor-col-resize select-none touch-none bg-transparent flex justify-center" >
)} ) })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} )) ) : ( No results )}
{table.getFilteredSelectedRowModel().rows.length} of{" "} {externalPagination ? totalCount : table.getFilteredRowModel().rows.length} row(s) selected
Page {table.getState().pagination.pageIndex + 1} of{" "} {externalPagination ? totalPages : table.getPageCount()}
) }