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 endTableNav?: (which: 'top' | 'bottom') => ReactNode } export interface ListTableRowsProps { getKey: (item: T) => K columns: ListTableColumn[] noItems?: ReactNode rowClassName?: (item: T) => string } export interface ListTablePaginationProps { totalPages?: number pageSearchParam?: string } export interface ListTableBorderProps { fixed?: boolean striped?: boolean className?: string } export const sortTableItems = ( 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 interface ListTableProps extends ListTableBorderProps, ListTableNavProps, ListTablePaginationProps, ListTableRowsProps { items: T[] beforeTable?: ReactNode selectAllControl?: boolean } export const ListTable = ({ items, getKey, totalPages, pageSearchParam = 'paged', ...tableProps }: ListTableProps) => { const [sortColumn, setSortColumn] = useState>() const [currentPage, setCurrentPage] = useState( () => pageSearchParam && Number(fetchQueryParam(pageSearchParam)) || 1 ) const [sortDirection, setSortDirection] = useState('asc') const visibleItems: T[] = useMemo( () => pageItems(sortTableItems(items, sortColumn, sortDirection), { currentPage, totalPages }), [items, sortColumn, sortDirection, currentPage, totalPages]) return ( ) } export interface PartialDataListTableProps extends ListTablePaginationProps, ListTableBorderProps, ListTableRowsProps, ListTableNavProps { sortColumn: ListTableColumn | undefined totalItems: number currentPage: number visibleItems: T[] beforeTable?: ReactNode selectAllControl?: boolean setSortColumn: (column: ListTableColumn | undefined) => void sortDirection?: ListTableSortDirection setCurrentPage: (page: number) => void setSortDirection: (direction: ListTableSortDirection) => void } export const PartialDataListTable = ({ getKey, actions, columns, noItems, doAction, disabled = false, totalItems, totalPages, currentPage, beforeTable, endTableNav, rowClassName, visibleItems, sortDirection = 'asc', extraTableNav, selectAllControl, setCurrentPage, pageSearchParam, ...tableBorderProps }: PartialDataListTableProps) => { const [selected, setSelected] = useState(() => new Set()) return ( {beforeTable} ) }