import React, { useState } from 'react' import ReactTable, { Column, ComponentPropsGetter0, ComponentPropsGetterR, DerivedDataObject, RowInfo, TableProps, } from 'react-table' import AutoSizer from 'react-virtualized-auto-sizer' import { FixedSizeList as List } from 'react-window' import { FlattenSimpleInterpolation } from 'styled-components' import { Overwrite } from 'typelevel-ts' import { A, O, pipe, R } from '@monorail/sharedHelpers/fp-ts-imports' import { Colors, getColor } from '@monorail/helpers/exports' import { css } from '@monorail/helpers/styled-components' import { logger } from '@monorail/v2/shared/helpers' import { NoData } from '@monorail/visualComponents/dataStates/DataStates' import { useSort } from '@monorail/visualComponents/dataTable/ReactTable' import { DEFAULT_ITEMS_PER_PAGE_OPTION, ItemsPerPageOption, itemsPerPageOptionToValue, } from '@monorail/visualComponents/dataTable/ReactTableSelect/helpers' import { SelectionSummaryBar } from '@monorail/visualComponents/dataTable/ReactTableSelect/SelectionSummaryBar' import { Choice } from '@monorail/visualComponents/inputs/Choice' enum SELECTED { ALL = 'ALL', SOME = 'SOME', NONE = 'NONE', } export enum CheckboxType { Radio = 'RADIO', Checkbox = 'CHECKBOX', } const getDefaultTrProps = (props: { disabled: boolean }) => ({ isRow: true, css: css` display: flex; flex-flow: row nowrap; height: auto; min-height: 40px; position: relative; width: 100%; cursor: ${props.disabled ? 'default' : 'pointer'}; transition: all 150ms; :hover { background: ${props.disabled ? 'inherit' : getColor(Colors.AccentBlue300, 0.1)}; } ${props.disabled ? css` & :not(i) { color: ${props.disabled ? getColor(Colors.Gray54) : 'inherit'}; font-style: ${props.disabled ? 'italic' : 'inherit'}; } ` : ''} `, }) const DefaultTrComponent = (props: { children: React.ReactElement onClick: () => void style: React.CSSProperties isRow: boolean css: FlattenSimpleInterpolation }) => { if (!props.isRow) { return props.children } else { return (
{props.children}
) } } const TBodyComponent = (tbodyComponentProps: { children: Array> className: string style: React.CSSProperties rowHeight: number }) => { const { rowHeight, children } = tbodyComponentProps return pipe( children, O.fromPredicate(Array.isArray), O.chain(A.head), O.chain(O.fromPredicate(Array.isArray)), O.chain(O.fromPredicate(A.isNonEmpty)), O.fold( () => { return }, (items: Array) => { const Row = (rowProps: { index: number style: React.CSSProperties }) =>
{items[rowProps.index]}
return (
{({ width, height }) => ( /* prefer FixedSizeList for performance Daniel Laubacher Thu 30 Jul 2020 **/ {Row} )}
) }, ), ) } export type SelectableTableProps = { selectProps: { onSelectionChange: (sel: Record) => void selected: Record getId: (x: T) => string isDisabled: (x: T) => boolean totalItems: number // This will prevent the count from getting out of sync when search is used to filter items DL 6/20 rowHeight: number type?: CheckboxType } reactTableProps: ModifiedReactTableProps & Required, 'data' | 'columns'>> } type ModifiedReactTableProps = Partial< Omit, 'data' | 'columns' | 'getTrProps' | 'TrComponent'> > export const ReactTableSelect = ({ selectProps: { type = CheckboxType.Checkbox, totalItems, ...selectProps }, reactTableProps: { ...reactTableProps }, }: SelectableTableProps) => { const [itemsPerPage, setItemsPerPage] = useState( DEFAULT_ITEMS_PER_PAGE_OPTION, ) const [sorted, onSortedChange] = useSort() const isSelected = (item: T) => O.isSome(R.lookup(selectProps.getId(item), selectProps.selected)) const toggleCheckbox = (args: { item: T; sel: Record }) => { const { item, sel } = args const id = selectProps.getId(item) return O.isSome(R.lookup(id, sel)) ? R.deleteAt(id)(sel) : R.insertAt(id, item)(sel) } const toggleRadio = (args: { item: T; sel: Record }) => { const { item, sel } = args const id = selectProps.getId(item) return O.isSome(R.lookup(id, sel)) ? {} : { [id]: item } } const toggleItem = (item: T) => (sel: Record) => { return type === CheckboxType.Checkbox ? toggleCheckbox({ item, sel }) : toggleRadio({ item, sel }) } const removeDisabledItems = (items: Array) => items.filter(x => !selectProps.isDisabled(x)) const removeItems = (items: Array) => items.reduce( (acc, cur) => R.deleteAt(selectProps.getId(cur))(acc), selectProps.selected, ) const addItems = (items: Array) => items.reduce( (acc, cur) => R.insertAt(selectProps.getId(cur), cur)(acc), selectProps.selected, ) const toggleAll = (items: Array) => { return getSelected(items) === SELECTED.ALL ? removeItems(items) : addItems(items) } const getSelected = (items: Array) => items.every(isSelected) ? SELECTED.ALL : items.some(isSelected) ? SELECTED.SOME : SELECTED.NONE const selectColumn: Column = { Header: (info: { data: Array }) => { const visibleRows = info.data.map(data => data._original as T) const selected = getSelected(visibleRows) const noVisibleItems = reactTableProps.data.length === 0 return type === CheckboxType.Checkbox ? ( { selectProps.onSelectionChange( toggleAll(removeDisabledItems(visibleRows)), ) }} /> ) : null }, Cell: (info: { original: T }) => { return ( { /** Handle click on the row not the cell DL 6/20 */ }} /> ) }, width: 60, filterable: false, sortable: false, resizable: true, style: { textAlign: 'center' }, } return ( ({ itemsPerPage, totalItems, totalSelectedItems: Object.keys(selectProps.selected).length, onItemsPerPageChange: setItemsPerPage, })} PaginationComponent={SelectionSummaryBar} {...reactTableProps} columns={[selectColumn, ...reactTableProps.columns]} pageSize={itemsPerPageOptionToValue(itemsPerPage, totalItems)} getTbodyProps={ (() => { return { rowHeight: selectProps.rowHeight } }) as ComponentPropsGetter0 } TbodyComponent={TBodyComponent} getTrProps={ ((_: unknown, rowInfo?: Overwrite) => { const { onClick, disabled } = pipe( O.fromNullable(rowInfo), O.fold( () => { return { onClick: () => logger(({ warn }) => warn( '*WARNING* Possible error in `getTrProps` within `ReactTableSelect`! `rowInfo` is undefined', ), ), disabled: false, } }, row => { const disabled_ = selectProps.isDisabled(row.original) return { onClick: () => { if (!disabled_) { selectProps.onSelectionChange( toggleItem(row.original)(selectProps.selected), ) } }, disabled: disabled_, } }, ), ) return { ...getDefaultTrProps({ disabled }), onClick, disabled, } }) as ComponentPropsGetterR } TrComponent={DefaultTrComponent} /> ) }