import React, { useMemo, useState } from 'react' import classnames from 'classnames' import { fetchQueryParam } from '../../../utils/urls' import { ColumnHeadings } from './ColumnHeadings' import { TableRows } from './TableRows' import { TableNavigation } from './TableNavigation' import type { ColumnHeadingsProps } from './ColumnHeadings' import type { Key, ReactNode } from 'react' export interface ListTableColumn { id: Key title?: ReactNode render: (item: T) => ReactNode isHidden?: boolean isPrimary?: boolean isHeading?: boolean sortedValue?: (item: T) => Key defaultSortDirection?: ListTableSortDirection } export type ListTableSortDirection = 'asc' | 'desc' export interface ListTableAction { key: A label: string group?: string } export interface ListTableNavProps { actions?: ListTableAction[] doAction: (action: A, selected: Set) => Promise disabled?: boolean extraTableNav?: (which: 'top' | 'bottom') => ReactNode } export interface ListTableRowsProps { items: T[] getKey: (item: T) => K columns: ListTableColumn[] noItems?: ReactNode rowClassName?: (item: T) => string } export interface ListTablePaginationProps { totalPages?: number useQueryVars?: boolean } export interface ListTableBorderProps { fixed?: boolean striped?: boolean className?: string } export type ListTableProps = ListTableBorderProps & ListTableNavProps & ListTablePaginationProps & ListTableRowsProps const sortItems = ( items: T[], sortColumn: ListTableColumn | undefined, sortDirection: ListTableSortDirection ): T[] => items.toSorted((itemA, itemB) => { const valueA = sortColumn?.sortedValue?.(itemA) const valueB = sortColumn?.sortedValue?.(itemB) if (valueA === undefined || valueB === undefined) { return 0 } if (valueA < valueB) { return 'asc' === sortDirection ? -1 : 1 } if (valueA > valueB) { return 'asc' === sortDirection ? 1 : -1 } return 0 }) const pageItems = ( items: T[], { currentPage, totalPages }: { currentPage: number; totalPages?: number } ): T[] => { if (totalPages) { const itemsPerPage = Math.ceil(items.length / totalPages) const start = (currentPage - 1) * itemsPerPage const end = start + itemsPerPage return items.slice(start, end) } else { return items } } const getVisibleSelected = ( visibleItems: T[], getKey: (item: T) => K, selected: Set ): Set => new Set(visibleItems.map(getKey).filter(key => selected.has(key))) interface TableBorderProps extends ListTableBorderProps, Omit, 'which'> { children: ReactNode } const TableBorder = ({ fixed, striped, children, className, ...tableHeadingsProps }: TableBorderProps) => ( {children}
) export const ListTable = ({ items, getKey, columns, actions, doAction, totalPages, extraTableNav, disabled = false, useQueryVars = true, fixed, striped, className, ...tableRowsProps }: ListTableProps) => { const [selected, setSelected] = useState(() => new Set()) const [sortColumn, setSortColumn] = useState>() const [currentPage, setCurrentPage] = useState(() => useQueryVars && Number(fetchQueryParam('paged')) || 1) const [sortDirection, setSortDirection] = useState('asc') const visibleItems: T[] = useMemo( () => pageItems(sortItems(items, sortColumn, sortDirection), { currentPage, totalPages }), [items, sortColumn, sortDirection, currentPage, totalPages]) return ( ) }