react-table#useRowSelect TypeScript Examples

The following examples show how to use react-table#useRowSelect. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Table.tsx    From solo with MIT License 5 votes vote down vote up
Table = <T extends object>({
  columns,
  data,
  initialSortBy,
  manualPagination = true,
  manualSortBy = true,
  pageCount = 0,
  fetchData,
  onSelectedRowsChange,
  renderSubComponent,
  renderPagination,
  renderFilterControls
}: TableProps<T>) => {
  const stateReducer = (nextState: any, action: any, prevState: any) => {
    if (action.type === "toggleSortBy" || action.type === "setGlobalFilter") {
      return { ...nextState, pageIndex: 0 };
    }
    return nextState;
  };

  const instance = useTable(
    {
      columns,
      data,
      manualGlobalFilter: true,
      disableMultiSort: true,
      manualSortBy,
      manualPagination,
      autoResetSortBy: !manualSortBy,
      pageCount,
      stateReducer,
      initialState: {
        sortBy: initialSortBy ?? [],
        pageSize: 20,
        globalFilter: []
      }
    },
    useGlobalFilter,
    useSortBy,
    useExpanded,
    usePagination,
    useRowSelect
  );

  const {
    state: { sortBy, pageIndex, globalFilter, selectedRowIds }
  } = instance;

  useEffect(() => {
    fetchData &&
      fetchData({
        sort: sortBy,
        page: pageIndex + 1,
        filters: globalFilter
      });
  }, [fetchData, sortBy, globalFilter, pageIndex]);

  useEffect(() => {
    onSelectedRowsChange && onSelectedRowsChange(instance);
  }, [selectedRowIds, instance, onSelectedRowsChange]);

  return (
    <>
      {renderFilterControls && renderFilterControls(instance)}
      <USWDSTable {...instance.getTableProps()}>
        <TableHead headerGroups={instance.headerGroups} />
        <TableBody {...instance} renderSubComponent={renderSubComponent} />
      </USWDSTable>
      {renderPagination && renderPagination(instance)}
    </>
  );
}
Example #2
Source File: FeTable.tsx    From frontegg-react with MIT License 5 votes vote down vote up
FeTable: FC<TableProps> = <T extends object>(props: TableProps<T>) => {
  const tableRef = useRef<HTMLDivElement>(null);
  const firstRender = useRef<boolean>(true);
  const columns = useMemo(() => {
    const columns = props.columns.map(
      ({ sortable, Filter, Header, ...rest }) =>
        ({
          ...rest,
          disableSortBy: !sortable,
          disableFilters: !Filter,
          Filter,
          Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />,
        } as FeTableColumnOptions<T>)
    );
    if (props.expandable) {
      columns.unshift({
        id: 'fe-expander',
        minWidth: 60,
        maxWidth: '60px' as any,
        Header: <div style={{ minWidth: '2rem', maxWidth: '2rem' }} />,
        Cell: (cell: Cell<T>) => {
          const row = cell.row as Row<T> & UseExpandedRowProps<T>;
          return (
            <FeButton
              className={classNames('fe-table__expand-button', { 'is-expanded': row.isExpanded })}
              {...row.getToggleRowExpandedProps()}
              variant={row.isExpanded ? 'primary' : undefined}
            >
              <FeIcon name='right-arrow' />
            </FeButton>
          );
        },
      });
    }
    if (props.selection) {
      columns.unshift({
        id: 'fe-selection',
        minWidth: 60,
        maxWidth: '60px' as any,
        Cell: (cell: Cell<T>) => {
          const row = cell.row as Row<T> & UseRowSelectRowProps<T>;
          return (
            <FeCheckbox
              {...row.getToggleRowSelectedProps()}
              checked={row.isSelected}
              onChange={(e) => onRowSelected(row.original, e.target.checked)}
            />
          );
        },
      });
    }
    return columns as Column<T>[];
  }, [props.columns, props.expandable]);

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,

    // The page controls ;)
    page,
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,

    // select props
    toggleAllRowsSelected,
    isAllRowsSelected,
    selectedFlatRows,
    toggleRowSelected,
  } = useTable(
    {
      columns,
      data: props.data,
      getRowId: (row: any) => row[props.rowKey],
      manualSortBy: !!props.onSortChange,
      manualFilters: !!props.onFilterChange,
      manualPagination: !!props.onPageChange,
      manualRowSelectedKey: props.rowKey,
      pageCount: !!props.onPageChange ? props.pageCount : undefined,
      autoResetPage: !props.onPageChange,
      useControlledState: (state1: any, meta) =>
        ({
          ...state1,
          sortBy: props.sortBy ?? state1.sortBy,
          filters: props.filters ?? state1.filters,
          selectedRowIds: props.selectedRowIds ?? state1.selectedRowIds,
        } as FeTableState<T>),
      expandSubRows: false,
      autoResetExpanded: false,
      initialState: {
        pageIndex: 0,
        pageSize: props.pageSize,
        selectedRowIds: props.selectedRowIds || {},
      },
    } as FeUseTable<T>,
    useFilters,
    useSortBy,
    useExpanded,
    usePagination,
    useRowSelect,
    useFlexLayout
  ) as FeTableInstance<T>;

  checkTableProps(props);

  const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>;

  const onSortChange = useCallback(
    (column: FeTableColumnProps<T>) => {
      if (props.hasOwnProperty('sortBy')) {
        const sortBy = props.isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : [];
        if (!column.isSorted) {
          sortBy.push({ id: column.id, desc: false });
        } else if (!column.isSortedDesc) {
          sortBy.push({ id: column.id, desc: true });
        }
        props.onSortChange?.(sortBy);
      } else {
        if (column.isSorted && column.isSortedDesc) {
          column.clearSortBy();
        } else {
          column.toggleSortBy(column.isSorted, props.isMultiSort ?? false);
        }
      }
    },
    [props.onSortChange]
  );

  const onFilterChange = useCallback(
    (column: FeTableColumnProps<T>, filterValue?: any) => {
      if (props.hasOwnProperty('filters')) {
        const filters = tableState.filters.filter(({ id }) => id !== column.id);
        if (filterValue != null) {
          filters.push({ id: column.id, value: filterValue });
        }
        props.onFilterChange?.(filters);
      } else {
        column.setFilter(filterValue);
      }
    },
    [props.onFilterChange, tableState]
  );

  const onToggleAllRowsSelected = useCallback(
    (value: boolean) => {
      if (props.hasOwnProperty('selectedRowIds')) {
        const selectedIds = props.data.reduce((p, n: any) => ({ ...p, [n[props.rowKey]]: true }), {});
        props.onRowSelected?.(value ? selectedIds : {});
      } else {
        toggleAllRowsSelected(value);
      }
    },
    [props.onRowSelected]
  );

  const onRowSelected = useCallback(
    (row: any, value: boolean) => {
      const id = row[props.rowKey];
      if (props.hasOwnProperty('selectedRowIds')) {
        const newSelectedRows: any = { ...props.selectedRowIds };
        if (value) {
          newSelectedRows[id] = true;
        } else {
          delete newSelectedRows[id];
        }
        props.onRowSelected?.(newSelectedRows);
      } else {
        toggleRowSelected(id, value);
      }
    },
    [props.onRowSelected]
  );

  const handleOnPageChange = useCallback(() => {
    if (pagination === 'pages') {
      tableRef.current?.querySelector(`.${prefixCls}__tbody`)?.scroll?.({ top: 0, left: 0, behavior: 'smooth' });
    }
    props.onPageChange?.(tableState.pageSize, tableState.pageIndex);
  }, [tableState.pageIndex]);

  useEffect(() => {
    !props.hasOwnProperty('sortBy') && props.onSortChange?.(tableState.sortBy);
  }, [props.sortBy, tableState.sortBy]);

  useEffect(() => {
    !props.hasOwnProperty('filters') && props.onFilterChange?.(tableState.filters);
  }, [props.filters, tableState.filters]);

  useEffect(() => {
    firstRender.current ? (firstRender.current = false) : handleOnPageChange();
  }, [tableState.pageIndex]);

  useEffect(() => {
    !props.hasOwnProperty('selectedRowIds') && props.onRowSelected?.(tableState.selectedRowIds as any);
  }, [tableState.selectedRowIds]);

  const tableHeadProps: FeTableTHeadProps<T> = {
    prefixCls,
    headerGroups,
    onSortChange,
    onFilterChange,
    toggleAllRowsSelected,
    isAllRowsSelected,
    selectedFlatRows,
  };

  const tableRows: (Row<T> & UseExpandedRowProps<T>)[] = useMemo(
    () => (props.pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[],
    [page, rows, props.pagination]
  );

  const tablePaginationProps: FeTablePaginationProps<T> = {
    pageIndex: tableState.pageIndex,
    pageSize: tableState.pageSize,
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
  };

  const { className, toolbar, loading, pagination, pageSize } = props;

  return (
    <div className='fe-table__container'>
      <div ref={tableRef} className={classNames(prefixCls, className)} {...getTableProps()}>
        {toolbar && <FeTableToolbar />}

        <div
          className={classNames(
            `${prefixCls}__table-container`,
            loading && pagination === 'pages' && `${prefixCls}__table-container-loading`
          )}
        >
          <FeTableTBody
            pageSize={pageSize}
            pagination={pagination}
            onInfiniteScroll={handleOnPageChange}
            loading={props.loading}
            prefixCls={prefixCls}
            prepareRow={prepareRow}
            getTableBodyProps={getTableBodyProps}
            renderExpandedComponent={props.renderExpandedComponent}
            rows={tableRows}
          />
          <FeTableTHead {...tableHeadProps} />
        </div>

        {loading && pagination === 'pages' && rows.length > 0 && <FeLoader center size={24} />}
        {pagination === 'pages' && <FeTablePagination {...tablePaginationProps} />}
      </div>
    </div>
  );
}
Example #3
Source File: Table.tsx    From frontegg-react with MIT License 4 votes vote down vote up
Table: FC<TableProps> = <T extends object>(props: TableProps<T>) => {
  const classes = useStyles();
  const tableRef = useRef<HTMLTableElement>(null);
  const firstRender = useRef<boolean>(true);
  const columns = useMemo(() => {
    const columns = props.columns.map(
      ({ sortable, Filter, Header, ...rest }) =>
        ({
          ...rest,
          disableSortBy: !sortable,
          disableFilters: !Filter,
          Filter,
          Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />,
        } as FeTableColumnOptions<T>)
    );

    if (props.expandable) {
      columns.unshift({
        id: 'fe-expander',
        minWidth: 60,
        maxWidth: '60px' as any,
        Header: <div style={{ minWidth: '1.5rem', maxWidth: '1.5rem' }} />,
        Cell: (cell: Cell<T>) => {
          const row = cell.row as Row<T> & UseExpandedRowProps<T>;
          return (
            <IconButton className={classes.expandIcon} {...row.getToggleRowExpandedProps()}>
              {row.isExpanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
            </IconButton>
          );
        },
      });
    }
    if (props.selection) {
      columns.unshift({
        id: 'fe-selection',
        minWidth: 60,
        maxWidth: '60px' as any,
        Cell: (cell: Cell<T>) => {
          const row = cell.row as Row<T> & UseRowSelectRowProps<T>;
          return (
            <Checkbox
              className={classes.checkBox}
              {...row.getToggleRowSelectedProps()}
              checked={row.isSelected}
              onChange={(e) => onRowSelected(row.original, e.target.checked)}
            />
          );
        },
      });
    }
    return columns as Column<T>[];
  }, [props.columns, props.expandable]);

  const tableHooks: PluginHook<T>[] = [useFilters, useSortBy];
  if (props.expandable) {
    tableHooks.push(useExpanded);
  }
  if (props.pagination) {
    tableHooks.push(usePagination);
  }
  if (props.selection) {
    tableHooks.push(useRowSelect);
  }
  tableHooks.push(useFlexLayout);

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,

    // The page controls ;)
    page,
    // canPreviousPage,
    // canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    // setPageSize,

    // select props
    toggleAllRowsSelected,
    isAllRowsSelected,
    selectedFlatRows,
    toggleRowSelected,
  } = useTable(
    {
      columns,
      data: props.data,
      getRowId: (row: any) => row[props.rowKey],
      manualSortBy: !!props.onSortChange,
      manualFilters: !!props.onFilterChange,
      manualPagination: !!props.onPageChange,
      manualRowSelectedKey: props.rowKey,
      pageCount: !!props.onPageChange ? props.pageCount : undefined,
      autoResetPage: !props.onPageChange,
      useControlledState: (state1: any) => {
        return {
          ...state1,
          sortBy: props.sortBy ?? state1.sortBy,
          filters: props.filters ?? state1.filters,
          selectedRowIds: props.selectedRowIds ?? state1.selectedRowIds,
        } as TableState<T> & UseFiltersState<T> & UseSortByState<T> & UseRowSelectState<T>;
      },
      expandSubRows: false,
      initialState: {
        pageIndex: 0,
        pageSize: props.pageSize ?? 0,
        selectedRowIds: props.selectedRowIds || {},
      },
    } as UseTableOptions<T> &
      UseFiltersOptions<T> &
      UseSortByOptions<T> &
      UseExpandedOptions<T> &
      UseRowSelectOptions<T> &
      UsePaginationOptions<T>,
    ...tableHooks
  ) as TableInstance<T> & UseTableInstanceProps<T> & UsePaginationInstanceProps<T> & UseRowSelectInstanceProps<T>;

  if (props.expandable && !props.renderExpandedComponent) {
    throw Error('FeTable: you must provide renderExpandedComponent property if the table is expandable');
  }
  if (props.hasOwnProperty('sortBy') && !props.onSortChange) {
    throw Error('FeTable: you must provide onSortChange property if sortBy is controlled');
  }
  if (props.hasOwnProperty('filters') && !props.onFilterChange) {
    throw Error('FeTable: you must provide onFilterChange property if filters is controlled');
  }
  if (props.hasOwnProperty('pagination') && !props.pageSize) {
    throw Error('FeTable: you must provide pageSize property if pagination enabled');
  }
  if (props.hasOwnProperty('onPageChange') && !props.pageCount) {
    throw Error('FeTable: you must provide pageCount property if onPageChange is controlled');
  }

  const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>;

  const onSortChange = useCallback(
    (column: FeTableColumnProps<T>) => {
      if (props.hasOwnProperty('sortBy')) {
        const sortBy = props.isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : [];
        if (!column.isSorted) {
          sortBy.push({ id: column.id, desc: false });
        } else if (!column.isSortedDesc) {
          sortBy.push({ id: column.id, desc: true });
        }
        props.onSortChange?.(sortBy);
      } else {
        if (column.isSorted && column.isSortedDesc) {
          column.clearSortBy();
        } else {
          column.toggleSortBy(column.isSorted, props.isMultiSort ?? false);
        }
      }
    },
    [props.onSortChange]
  );

  const onFilterChange = useCallback(
    (column: FeTableColumnProps<T>, filterValue?: any) => {
      if (props.hasOwnProperty('filters')) {
        const filters = tableState.filters.filter(({ id }) => id !== column.id);
        if (filterValue != null) {
          filters.push({ id: column.id, value: filterValue });
        }
        props.onFilterChange?.(filters);
      } else {
        column.setFilter(filterValue);
      }
    },
    [props.onFilterChange, tableState]
  );

  const onToggleAllRowsSelected = useCallback(
    (value: boolean) => {
      if (props.hasOwnProperty('selectedRowIds')) {
        const selectedIds = props.data.reduce((p, n: any) => ({ ...p, [n[props.rowKey]]: true }), {});
        props.onRowSelected?.(value ? selectedIds : {});
      } else {
        toggleAllRowsSelected(value);
      }
    },
    [props.onRowSelected]
  );

  const onRowSelected = useCallback(
    (row: any, value: boolean) => {
      const id = row[props.rowKey];
      if (props.hasOwnProperty('selectedRowIds')) {
        const newSelectedRows: any = { ...props.selectedRowIds };
        if (value) {
          newSelectedRows[id] = true;
        } else {
          delete newSelectedRows[id];
        }
        props.onRowSelected?.(newSelectedRows);
      } else {
        toggleRowSelected(id, value);
      }
    },
    [props.onRowSelected]
  );

  const handleOnPageChange = useCallback(() => {
    if (pagination === 'pages') {
      tableRef.current?.scroll?.({ top: 0, left: 0, behavior: 'smooth' });
    }
    props.onPageChange?.(tableState.pageSize, tableState.pageIndex);
  }, [tableState.pageIndex]);

  useEffect(() => {
    !props.hasOwnProperty('sortBy') && props.onSortChange?.(tableState.sortBy);
  }, [props.sortBy, tableState.sortBy]);

  useEffect(() => {
    !props.hasOwnProperty('filters') && props.onFilterChange?.(tableState.filters);
  }, [props.filters, tableState.filters]);

  useEffect(() => {
    firstRender.current ? (firstRender.current = false) : handleOnPageChange();
  }, [tableState.pageIndex]);

  useEffect(() => {
    !props.hasOwnProperty('selectedRowIds') && props.onRowSelected?.(tableState.selectedRowIds as any);
  }, [tableState.selectedRowIds]);

  const onPageChangeHandler = (page: number) => {
    if (page > tableState.pageIndex) {
      nextPage();
    } else {
      previousPage();
    }
  };

  const { className, loading, pagination, totalData, pageSize } = props;

  return (
    <Paper ref={tableRef} className={classes.paper}>
      <MaUTable className={classNames(classes.table, className)} {...getTableProps()}>
        <TableHead
          headerGroups={headerGroups}
          onSortChange={onSortChange}
          onFilterChange={onFilterChange}
          toggleAllRowsSelected={onToggleAllRowsSelected}
          isAllRowsSelected={isAllRowsSelected}
          selectedFlatRows={selectedFlatRows}
        />
        <TableBody
          pageSize={pageSize}
          pagination={pagination}
          getTableBodyProps={getTableBodyProps}
          prepareRow={prepareRow}
          loading={loading}
          rows={(pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[]}
          renderExpandedComponent={props.renderExpandedComponent}
          onInfiniteScroll={handleOnPageChange}
        />
      </MaUTable>

      {loading && pagination === 'pages' && rows.length > 0 && <Loader center size={24} />}
      {pagination === 'pages' && (
        <TablePagination
          className={classes.footer}
          rowsPerPageOptions={[]}
          component='div'
          count={totalData || rows.length}
          rowsPerPage={tableState.pageSize}
          page={tableState.pageIndex}
          onChangePage={(e, page) => onPageChangeHandler(page)}
          ActionsComponent={(props) => (
            <TablePaginationActions {...props} gotoPage={gotoPage} pageOptions={pageOptions} />
          )}
        />
      )}
    </Paper>
  );
}
Example #4
Source File: Table.tsx    From opensaas with MIT License 4 votes vote down vote up
Table: React.FC<TableProps> = <T extends object>(props: TableProps<T>) => {
  const {
    expandable,
    selection,
    pagination,
    sortBy,
    pageCount,
    data,
    selectedRowIds,
    pageSize,
    rowKey,
    loading,
    isMultiSort,
    renderExpandedComponent,
    onPageChange,
    filters: propsFilters,
    onSortChange: propsOnSortChange,
    onRowSelected: propsOnRowSelected,
    onFilterChange: propsOnFilterChange,
    columns: propsColumns,
  } = props;
  const classes = useStyles();
  const tableRef = useRef<HTMLTableElement>(null);

  const hasSortBy = props.hasOwnProperty('sortBy');
  const hasFilters = props.hasOwnProperty('filters');
  const hasPagination = props.hasOwnProperty('pagination');
  const hasOnPageChange = props.hasOwnProperty('onPageChange');
  const hasSelectedRowIds = props.hasOwnProperty('selectedRowIds');

  const onRowSelected = useCallback(
    (row: UseRowSelectRowProps<T> & Row<T> & UseTableRowProps<T>, value: boolean) => {
      const id = (row.original as any)[rowKey];
      if (hasSelectedRowIds) {
        const newSelectedRows: any = { ...selectedRowIds };
        if (value) {
          newSelectedRows[id] = true;
        } else {
          delete newSelectedRows[id];
        }
        propsOnRowSelected?.(newSelectedRows);
      } else {
        row.toggleRowSelected(value);
      }
    },
    [hasSelectedRowIds, rowKey, selectedRowIds, propsOnRowSelected],
  );

  const columns = useMemo(() => {
    const columns = propsColumns.map(
      ({ sortable, Filter, Header, ...rest }) =>
        ({
          ...rest,
          disableSortBy: !sortable,
          disableFilters: !Filter,
          Filter,
          Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />,
        } as TableColumnOptions<T>),
    );

    if (expandable) {
      columns.unshift({
        id: 'expander',
        minWidth: 60,
        maxWidth: '60px' as any,
        Cell: (cell: UseTableCellProps<T>) => {
          const row = cell.row as Row<T> & UseExpandedRowProps<T>;
          return (
            <IconButton className={classes.expandIcon} {...row.getToggleRowExpandedProps()}>
              {row.isExpanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
            </IconButton>
          );
        },
      });
    }
    if (selection) {
      columns.unshift({
        id: 'selection',
        minWidth: 60,
        maxWidth: '60px' as any,
        Cell: (cell: UseTableCellProps<T>) => {
          const row = cell.row as UseRowSelectRowProps<T> & Row<T> & UseTableRowProps<T>;
          return (
            <Checkbox
              className={classes.checkBox}
              {...row.getToggleRowSelectedProps()}
              checked={row.isSelected}
              onChange={(e) => onRowSelected(row, e.target.checked)}
            />
          );
        },
      });
    }
    return columns as Column<T>[];
  }, [propsColumns, expandable, classes, selection, onRowSelected]);

  const tableHooks: PluginHook<T>[] = [useFilters, useSortBy];
  if (expandable) {
    tableHooks.push(useExpanded);
  }
  if (pagination) {
    tableHooks.push(usePagination);
  }
  if (selection) {
    tableHooks.push(useRowSelect);
  }
  tableHooks.push(useFlexLayout);

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,
    page,
    pageOptions,
    gotoPage,
    nextPage,
    previousPage,
    toggleAllRowsSelected,
    isAllRowsSelected,
    selectedFlatRows,
  } = useTable(
    {
      columns,
      data,
      getRowId: (row: any) => row[rowKey],
      manualSortBy: !!propsOnSortChange,
      manualFilters: !!propsOnFilterChange,
      manualPagination: !!onPageChange,
      manualRowSelectedKey: rowKey,
      pageCount: pageCount ?? 0,
      useControlledState: (state1: any) =>
        ({
          ...state1,
          sortBy: sortBy ?? state1.sortBy,
          filters: propsFilters ?? state1.filters,
          selectedRowIds: selectedRowIds ?? state1.selectedRowIds,
        } as TableState<T> & UseFiltersState<T> & UseSortByState<T> & UseRowSelectState<T>),
      expandSubRows: false,
      initialState: {
        pageIndex: 0,
        pageSize: pageSize ?? 0,
        selectedRowIds: selectedRowIds || {},
      },
    } as UseTableOptions<T> &
      UseFiltersOptions<T> &
      UseSortByOptions<T> &
      UseExpandedOptions<T> &
      UseRowSelectOptions<T> &
      UsePaginationOptions<T>,
    ...tableHooks,
  ) as TableInstance<T> & UseTableInstanceProps<T> & UsePaginationInstanceProps<T> & UseRowSelectInstanceProps<T>;

  if (expandable && !renderExpandedComponent) {
    throw Error('Table: you must provide renderExpandedComponent property if the table is expandable');
  }
  if (hasSortBy && !propsOnSortChange) {
    throw Error('Table: you must provide onSortChange property if sortBy is controlled');
  }
  if (hasFilters && !propsOnFilterChange) {
    throw Error('Table: you must provide onFilterChange property if filters is controlled');
  }
  if (hasPagination && !pageSize) {
    throw Error('Table: you must provide pageSize property if pagination enabled');
  }
  if (hasOnPageChange && !pageCount) {
    throw Error('Table: you must provide pageCount property if onPageChange is controlled');
  }

  const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>;

  const onSortChange = useCallback(
    (column: TableColumnProps<T>) => {
      if (hasSortBy) {
        const sortBy = isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : [];
        if (!column.isSorted) {
          sortBy.push({ id: column.id, desc: false });
        } else if (!column.isSortedDesc) {
          sortBy.push({ id: column.id, desc: true });
        }
        propsOnSortChange?.(sortBy);
      } else {
        if (column.isSorted && column.isSortedDesc) {
          column.clearSortBy();
        } else {
          column.toggleSortBy(column.isSorted, isMultiSort ?? false);
        }
      }
    },
    [hasSortBy, isMultiSort, tableState.sortBy, propsOnSortChange],
  );

  const onFilterChange = useCallback(
    (column: TableColumnProps<T>, filterValue?: any) => {
      if (hasFilters) {
        const filters = tableState.filters.filter(({ id }) => id !== column.id);
        if (filterValue != null) {
          filters.push({ id: column.id, value: filterValue });
        }
        propsOnFilterChange?.(filters);
      } else {
        column.setFilter(filterValue);
      }
    },
    [propsOnFilterChange, hasFilters, tableState.filters],
  );

  const onToggleAllRowsSelected = useCallback(
    (value: boolean) => {
      if (hasSelectedRowIds) {
        const selectedIds = data.reduce((p, n: any) => ({ ...p, [n[rowKey]]: true }), {});
        propsOnRowSelected?.(value ? selectedIds : {});
      } else {
        toggleAllRowsSelected(value);
      }
    },
    [hasSelectedRowIds, data, rowKey, propsOnRowSelected, toggleAllRowsSelected],
  );

  useEffect(() => {
    if (!hasSortBy) propsOnSortChange?.(tableState.sortBy);
  }, [hasSortBy, propsOnSortChange, tableState.sortBy]);

  useEffect(() => {
    if (!hasFilters) propsOnFilterChange?.(tableState.filters);
  }, [hasFilters, propsOnFilterChange, tableState.filters]);

  useEffect(() => {
    tableRef.current?.querySelector('.table-tbody')?.scroll?.({ top: 0, left: 0, behavior: 'smooth' });
    onPageChange?.(tableState.pageSize, tableState.pageIndex);
  }, [onPageChange, tableState.pageSize, tableState.pageIndex]);

  useEffect(() => {
    if (!hasSelectedRowIds) propsOnRowSelected?.(tableState.selectedRowIds as any);
  }, [hasSelectedRowIds, propsOnRowSelected, tableState.selectedRowIds]);

  const onPageChangeHandler = (page: number) => {
    if (page > tableState.pageIndex) {
      nextPage();
    } else {
      previousPage();
    }
  };

  return (
    <Paper className={classes.paper}>
      <MaterialUITable className={classes.table} ref={tableRef} {...getTableProps()}>
        <TableHead
          headerGroups={headerGroups}
          onSortChange={onSortChange}
          onFilterChange={onFilterChange}
          toggleAllRowsSelected={onToggleAllRowsSelected}
          isAllRowsSelected={isAllRowsSelected}
          selectedFlatRows={selectedFlatRows}
        />
        <TableBody
          getTableBodyProps={getTableBodyProps}
          prepareRow={prepareRow}
          loading={loading}
          rows={(pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[]}
          renderExpandedComponent={renderExpandedComponent}
        />
      </MaterialUITable>
      {pagination === 'pages' && (
        <TablePagination
          className={classes.footer}
          rowsPerPageOptions={[]}
          component='div'
          count={rows.length}
          rowsPerPage={tableState.pageSize}
          page={tableState.pageIndex}
          onChangePage={(e, page) => onPageChangeHandler(page)}
          ActionsComponent={(props) => (
            <TablePaginationActions {...props} gotoPage={gotoPage} pageOptions={pageOptions} />
          )}
        />
      )}
    </Paper>
  );
}
Example #5
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
CommonAdminTable = ({
  id,
  loading,
  onFetchData,
  onRowSelected,
  setNextCursor,
  columns,
  data,
  nextCursor,
  err,
  filtersDesc,
  rowsPerPage,
  initialSortBy = [],
  children,
}: CommonAdminTableProps) => {
  const [cursor, setCursor] = useState("");
  const [prevCursor, setPrevCursor] = useState([]);

  const fetchDataDebounced = useAsyncDebounce(({ sortBy, cursor, filters }) => {
    let order;
    if (sortBy.length) {
      order = sortBy.map((o) => `${o.id}-${o.desc}`).join(",");
    }
    onFetchData({ order, cursor, filters });
  }, 1000);

  const dm = useMemo(() => data, [data]);

  const tableOptions: any = {
    columns,
    data: dm,
    manualFilters: true,
    autoResetSortBy: false,
    manualSortBy: true,
    maxMultiSortColCount: 2,
    initialState: { sortBy: initialSortBy },
  };

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    selectedFlatRows,
    toggleAllRowsSelected,
    setFilter,
    state: { filters, sortBy },
    rows,
  }: any = useTable(
    tableOptions,
    useFilters,
    useSortBy,
    useRowSelect,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        // Let's make a column for selection
        {
          id: "selection",
          // The header can use the table's getToggleAllRowsSelectedProps method
          // to render a checkbox
          Header: ({ rows }) => {
            return (
              <>
                <Button
                  variant="secondarySmall"
                  disabled={prevCursor.length === 0}
                  sx={{ margin: 0, padding: "2px", px: "4px" }}
                  onClick={() => {
                    setNextCursor(cursor);
                    setCursor(prevCursor.pop());
                    setPrevCursor([...prevCursor]);
                  }}>
                  â­ 
                </Button>
                <Button
                  variant="secondarySmall"
                  disabled={rows.length < rowsPerPage || nextCursor === ""}
                  sx={{ margin: 0, ml: 2, padding: "2px", px: "4px" }}
                  onClick={() => {
                    prevCursor.push(cursor);
                    setPrevCursor([...prevCursor]);
                    setCursor(nextCursor);
                    setNextCursor("");
                  }}>
                  â­¢
                </Button>
              </>
            );
          },
          // The cell can use the individual row's getToggleRowSelectedProps method
          // to the render a checkbox
          Cell: ({ row }) => (
            <div>
              <Checkbox
                // @ts-ignore
                value={row.isSelected}
                onClick={(e) => {
                  toggleAllRowsSelected(false);
                  // @ts-ignore
                  row.toggleRowSelected(!row.isSelected);
                }}
              />
            </div>
          ),
        },
        ...columns,
      ]);
    }
  );

  useEffect(() => {
    onRowSelected(selectedFlatRows[0]?.original);
  }, [selectedFlatRows]);

  useEffect(() => {
    setPrevCursor([]);
    setNextCursor("");
    setCursor("");
  }, [sortBy, filters]);

  useEffect(() => {
    fetchDataDebounced({ sortBy, cursor, filters });
  }, [sortBy, cursor, filters]);

  return (
    <Box
      id={id}
      sx={{
        mb: 0,
        mt: 0,
      }}>
      <Flex
        sx={{
          justifyContent: "flex-start",
          alignItems: "baseline",
          my: "1em",
        }}>
        {children}
        {filtersDesc.map((fd) => {
          if (typeof fd.render === "function") {
            return fd.render({
              value: (filters.find((o) => o.id === fd.id) || [])[0]?.value,
              setValue: (v) => setFilter(fd.id, v),
            });
          }
          return (
            <Input
              key={fd.id}
              sx={{ width: "10em", ml: "1em" }}
              label={`${fd.placeholder || fd.id} filter input`}
              value={(filters.find((o) => o.id === fd.id) || [])[0]?.value}
              onChange={(e) => setFilter(fd.id, e.target.value)}
              placeholder={fd.placeholder}></Input>
          );
        })}
      </Flex>
      {err && <Box>{err}</Box>}
      <Box>
        <Box
          as="table"
          sx={{
            display: "table",
            width: "100%",
            borderCollapse: "inherit",
            borderSpacing: "0",
            border: 0,
          }}
          {...getTableProps()}>
          <Box as="thead" sx={{ position: "relative" }}>
            {headerGroups.map((headerGroup, i) => (
              <Box as="tr" key={i} {...headerGroup.getHeaderGroupProps()}>
                {headerGroup.headers.map((column: any, i) => (
                  <Box
                    as="th"
                    sx={{
                      userSelect: "none",
                      fontWeight: "normal",
                      textTransform: "uppercase",
                      bg: "rgba(0,0,0,.03)",
                      borderBottom: "1px solid",
                      borderTop: "1px solid",
                      borderColor: "muted",
                      borderLeft: "0px solid",
                      borderRight: "0px solid",
                      fontSize: 0,
                      color: "gray",
                      py: 2,
                      "&:first-of-type": {
                        borderLeft: "1px solid",
                        borderColor: "muted",
                        borderTopLeftRadius: 6,
                        borderBottomLeftRadius: 6,
                      },
                      "&:last-of-type": {
                        borderRight: "1px solid",
                        borderColor: "muted",
                        borderTopRightRadius: 6,
                        borderBottomRightRadius: 6,
                      },
                    }}
                    align="left"
                    {...column.getHeaderProps(
                      column.getSortByToggleProps({ title: "" })
                    )}
                    key={i}>
                    <Flex sx={{ mr: "-18px" }}>
                      {column.render("Header")}
                      <span>
                        {column.canSort &&
                          (column.isSorted
                            ? column.isSortedDesc
                              ? "â­£"
                              : "â­¡"
                            : "â­¥")}
                      </span>
                      {i === headerGroup.headers.length - 1 && (
                        <Flex
                          sx={{ alignItems: "center", ml: "auto", mr: "1em" }}>
                          <Flex>
                            <ReactTooltip
                              id={`tooltip-multiorder`}
                              className="tooltip"
                              place="top"
                              type="dark"
                              effect="solid">
                              To multi-sort (sort by two column simultaneously)
                              hold shift while clicking on second column name.
                            </ReactTooltip>
                            <Help
                              data-tip
                              data-for={`tooltip-multiorder`}
                              sx={{
                                cursor: "pointer",
                                ml: 1,
                              }}
                            />
                          </Flex>
                        </Flex>
                      )}
                    </Flex>
                  </Box>
                ))}
              </Box>
            ))}
            {loading && (
              <Box
                as="tr"
                sx={{
                  height: "0px",
                  border: 0,
                  bg: "transparent !important",
                  margin: 0,
                  padding: 0,
                }}>
                <Box
                  as="th"
                  sx={{ border: 0, bg: "transparent", margin: 0, padding: 0 }}
                  colSpan={1000}>
                  <Box sx={{ width: "100%", position: "relative" }}>
                    <Box
                      sx={{
                        position: "absolute",
                        top: "-1px",
                        left: "6px",
                        right: "0px",
                      }}>
                      <Box
                        sx={{
                          backgroundColor: "dodgerblue",
                          height: "1px",
                          animation: `${loadingAnim} 3s ease-in-out infinite`,
                        }}
                      />
                    </Box>
                  </Box>
                </Box>
              </Box>
            )}
          </Box>

          <tbody {...getTableBodyProps()}>
            {rows.map((row: any, rowIndex) => {
              prepareRow(row);
              return (
                <Box
                  as="tr"
                  sx={{
                    bg: "transparent !important",
                  }}
                  {...row.getRowProps()}>
                  {row.cells.map((cell) => {
                    return (
                      <Box
                        as="td"
                        sx={{
                          fontSize: 1,
                          borderBottomColor: "muted",
                          borderBottomWidth: "1px",
                          borderBottomStyle: "solid",
                          borderTop: "0px solid",
                          borderLeft: "0px solid",
                          borderRight: "0px solid",
                        }}
                        {...cell.getCellProps()}>
                        {renderCell(cell)}
                      </Box>
                    );
                  })}
                </Box>
              );
            })}
          </tbody>
        </Box>
      </Box>
    </Box>
  );
}
Example #6
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
Table = <T extends Record<string, unknown>>({
  columns,
  data,
  header,
  pageSize = 100,
  rowSelection,
  onRowSelectionChange,
  initialSortBy,
  filters,
  showOverflow,
}: Props<T>) => {
  const someColumnCanSort = useMemo(() => {
    // To see if we show the sort help tooltip or not
    // @ts-ignore
    return columns.some((column) => !column.disableSortBy);
  }, [columns]);

  const getRowId = useCallback((row, relativeIndex, parent) => {
    return row?.id ? row.id : relativeIndex;
  }, []);

  const {
    getTableProps,
    getTableBodyProps,
    prepareRow,
    headerGroups,
    // @ts-ignore
    page,
    // @ts-ignore
    nextPage,
    // @ts-ignore
    previousPage,
    // @ts-ignore
    canPreviousPage,
    // @ts-ignore
    canNextPage,
    // @ts-ignore
    toggleAllRowsSelected,
    // @ts-ignore
    selectedFlatRows,
    // @ts-ignore
    setFilter,
    // @ts-ignore
    state: { filters: currentFilters },
  } = useTable(
    {
      // @ts-ignore
      columns,
      data,
      getRowId,
      initialState: {
        // @ts-ignore
        pageSize,
        pageIndex: 0,
        ...(initialSortBy ? { sortBy: initialSortBy } : undefined),
      },
      manualSortBy: false,
      autoResetFilters: false,
      autoResetSortBy: false,
      autoResetPage: false,
      autoResetSelectedRows: false,
    },
    useFilters,
    useSortBy,
    usePagination,
    useRowSelect,
    (hooks) => {
      if (rowSelection) {
        const isIndividualSelection = rowSelection === "individual";
        hooks.visibleColumns.push((columns) => [
          // Let's make a column for selection
          {
            id: "selection",
            // The header can use the table's getToggleAllRowsSelectedProps method
            // to render a checkbox
            // @ts-ignore
            Header: ({ getToggleAllPageRowsSelectedProps }) => {
              const props = getToggleAllPageRowsSelectedProps();
              return isIndividualSelection ? null : (
                <Checkbox onClick={props.onChange} value={props.checked} />
              );
            },
            // The cell can use the individual row's getToggleRowSelectedProps method
            // to the render a checkbox
            Cell: ({ row }) => {
              return (
                <Checkbox
                  // @ts-ignore
                  value={row.isSelected}
                  onClick={() => {
                    isIndividualSelection && toggleAllRowsSelected(false);
                    // @ts-ignore
                    row.toggleRowSelected(!row.isSelected);
                  }}
                />
              );
            },
          },
          ...columns,
        ]);
      }
    }
  );

  useEffect(() => {
    onRowSelectionChange?.(selectedFlatRows);
  }, [selectedFlatRows, onRowSelectionChange]);

  return (
    <div>
      {header || filters ? (
        <Box
          sx={{
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            mb: 3,
          }}>
          <div>{header}</div>
          {filters ? (
            <Box
              sx={{
                flex: "1",
                display: "flex",
                alignItems: "center",
                justifyContent: "flex-end",
              }}>
              {filters.map((f) => {
                let filter: JSX.Element;
                switch (f.type) {
                  case "text":
                    filter = (
                      <TextFilter
                        {...f.props}
                        setFilter={setFilter}
                        currentFilters={currentFilters}
                      />
                    );
                    break;
                  case "checkbox":
                    filter = (
                      <CheckboxFilter
                        {...f.props}
                        setFilter={setFilter}
                        currentFilters={currentFilters}
                      />
                    );
                    break;
                  default:
                    return null;
                }
                return (
                  <Box
                    key={`${f.type}-${f.props.columnId}`}
                    sx={{ ":not(:last-of-type)": { mr: 3 } }}>
                    {filter}
                  </Box>
                );
              })}
            </Box>
          ) : null}
        </Box>
      ) : null}
      <Box sx={{ overflow: showOverflow ? "visible" : "hidden" }}>
        <Box sx={{ overflowX: showOverflow ? "visible" : "auto" }}>
          <Box
            as="table"
            {...getTableProps()}
            sx={{
              minWidth: "100%",
              borderCollapse: "separate",
              borderSpacing: 0,
            }}>
            <thead>
              {headerGroups.map((headerGroup) => (
                <Box
                  as="tr"
                  {...headerGroup.getHeaderGroupProps()}
                  sx={{ borderRadius: "8px" }}>
                  {headerGroup.headers.map((column, i) => {
                    const withHelpTooltip =
                      someColumnCanSort && i === headerGroup.headers.length - 1;
                    return (
                      <Box
                        as="th"
                        scope="col"
                        {...column.getHeaderProps(
                          // @ts-ignore
                          column.getSortByToggleProps()
                        )}
                        sx={{
                          textTransform: "uppercase",
                          bg: "rgba(0,0,0,.03)",
                          border: 0,
                          borderBottom: "1px solid",
                          borderTop: "1px solid",
                          borderColor: "muted",
                          fontSize: 0,
                          color: "gray",
                          px: 4,
                          py: 2,
                          fontWeight: 400,
                          position: "relative",
                          "&:first-of-type": {
                            borderLeft: "1px solid",
                            borderColor: "muted",
                            borderTopLeftRadius: 6,
                            borderBottomLeftRadius: 6,
                          },
                          "&:last-of-type": {
                            borderRight: "1px solid",
                            borderColor: "muted",
                            borderTopRightRadius: 6,
                            borderBottomRightRadius: 6,
                          },
                        }}>
                        <Box
                          sx={{
                            display: "flex",
                            alignItems: "center",
                            mr: withHelpTooltip ? 3 : 0,
                          }}>
                          <Box as="span" sx={{ whiteSpace: "nowrap" }}>
                            {column.render("Header")}
                          </Box>
                          {/*@ts-ignore */}
                          {column.canSort && (
                            <Box as="span" sx={{ ml: 2 }}>
                              {/* @ts-ignore */}
                              {column.isSorted
                                ? // @ts-ignore
                                  column.isSortedDesc
                                  ? " â­£"
                                  : " â­¡"
                                : " â­¥"}
                            </Box>
                          )}
                        </Box>
                        {withHelpTooltip && (
                          <Box
                            sx={{
                              alignItems: "center",
                              display: "flex",
                              position: "absolute",
                              right: 3,
                              top: "50%",
                              transform: "translateY(-50%)",
                            }}>
                            <ReactTooltip
                              id={`tooltip-multiorder`}
                              className="tooltip"
                              place="top"
                              type="dark"
                              effect="solid">
                              To multi-sort (sort by two column simultaneously)
                              hold shift while clicking on second column name.
                            </ReactTooltip>
                            <Help
                              data-tip
                              data-for={`tooltip-multiorder`}
                              sx={{
                                cursor: "pointer",
                                ml: 1,
                              }}
                            />
                          </Box>
                        )}
                      </Box>
                    );
                  })}
                </Box>
              ))}
            </thead>
            <tbody {...getTableBodyProps()}>
              {page.map((row: Row<object>) => {
                prepareRow(row);
                return (
                  <tr {...row.getRowProps()}>
                    {row.cells.map((cell) => (
                      <Box
                        as="td"
                        {...cell.getCellProps()}
                        sx={{
                          px: 4,
                          py: 3,
                          border: 0,
                          borderBottom: "1px solid",
                          borderBottomColor: "muted",
                          bg: "background",
                          fontSize: 1,
                        }}>
                        {cell.render("Cell")}
                      </Box>
                    ))}
                  </tr>
                );
              })}
            </tbody>
          </Box>
        </Box>
        <Paginator
          canPreviousPage={canPreviousPage}
          canNextPage={canNextPage}
          onPreviousPage={previousPage}
          onNextPage={nextPage}
        />
      </Box>
    </div>
  );
}
Example #7
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
DataTableComponent = <T extends Record<string, unknown>>({
  columns,
  header,
  rowSelection,
  initialSortBy,
  filterItems,
  showOverflow,
  cursor = "default",
  stateSetter,
  state,
  tableData,
  selectAction,
  createAction,
  emptyState,
  viewAll,
  noPagination = false,
  border = false,
  tableLayout = "fixed",
}: DataTableProps<T>) => {
  const { isLoading, data } = tableData;
  const dataMemo = useMemo(() => data?.rows ?? [], [data?.rows]);

  const someColumnCanSort = useMemo(() => {
    // To see if we show the sort help tooltip or not
    // @ts-ignore
    return columns.some((column) => !column.disableSortBy);
  }, [columns]);

  const getRowId = useCallback((row, relativeIndex) => {
    return row?.id ? row.id : relativeIndex;
  }, []);
  const {
    getTableProps,
    getTableBodyProps,
    prepareRow,
    headerGroups,
    rows,
    // @ts-ignore
    toggleAllRowsSelected,
    // @ts-ignore
    selectedFlatRows,
    // @ts-ignore
    state: { sortBy },
  } = useTable(
    {
      // @ts-ignore
      columns,
      getRowId,
      data: dataMemo,
      initialState: {
        // @ts-ignore
        pageSize: state.pageSize,
        pageIndex: 0,
        ...(initialSortBy ? { sortBy: initialSortBy } : undefined),
      },
      disableSortBy: !!viewAll,
      manualSortBy: false,
      autoResetFilters: false,
      autoResetSortBy: false,
      autoResetPage: false,
      autoResetSelectedRows: false,
    },
    useSortBy,
    useRowSelect,
    (hooks) => {
      if (rowSelection) {
        const isIndividualSelection = rowSelection === "individual";
        hooks.visibleColumns.push((columns) => [
          // Let's make a column for selection
          {
            id: "selection",
            // The header can use the table's getToggleAllRowsSelectedProps method
            // to render a checkbox
            Header: ({
              // @ts-ignore
              getToggleAllRowsSelectedProps,
              // @ts-ignore
              isAllRowsSelected,
            }) => {
              if (isIndividualSelection) return null;
              const props = getToggleAllRowsSelectedProps();
              return (
                <Checkbox
                  css={{ display: "flex" }}
                  onClick={props.onChange}
                  value="toggle-all"
                  checked={isAllRowsSelected ? true : false}
                />
              );
            },
            // The cell can use the individual row's getToggleRowSelectedProps method
            // to the render a checkbox
            Cell: ({ row }) => {
              return (
                <Checkbox
                  css={{ display: "flex" }}
                  // @ts-ignore
                  value={row.isSelected}
                  // @ts-ignore
                  checked={row.isSelected}
                  onClick={() => {
                    isIndividualSelection && toggleAllRowsSelected(false);
                    // @ts-ignore
                    row.toggleRowSelected(!row.isSelected);
                  }}
                />
              );
            },
          },
          ...columns,
        ]);
      }
    }
  );

  useEffect(() => {
    stateSetter.setSelectedRows(selectedFlatRows);
  }, [selectedFlatRows, stateSetter.setSelectedRows]);

  useEffect(() => {
    const order = sortBy?.map((o) => `${o.id}-${o.desc}`).join(",") ?? "";
    stateSetter.setOrder(order);
  }, [sortBy, stateSetter.setOrder]);

  useEffect(() => {
    stateSetter.setNextCursor(data?.nextCursor);
  }, [data?.nextCursor, stateSetter.setNextCursor]);

  const handlePreviousPage = useCallback(() => {
    stateSetter.setNextCursor(state.cursor); // current cursor will be next
    const prevCursorsClone = [...state.prevCursors];
    const newCursor = prevCursorsClone.pop();
    stateSetter.setCursor(newCursor);
    stateSetter.setPrevCursors([...prevCursorsClone]);
  }, [
    stateSetter.setNextCursor,
    stateSetter.setCursor,
    stateSetter.setPrevCursors,
    state.prevCursors,
    state.cursor,
  ]);

  const handleNextPage = useCallback(() => {
    stateSetter.setPrevCursors((p) => [...p, state.cursor]);
    stateSetter.setCursor(state.nextCursor);
    stateSetter.setNextCursor("");
  }, [
    stateSetter.setPrevCursors,
    stateSetter.setCursor,
    stateSetter.setNextCursor,
    state.nextCursor,
    state.cursor,
  ]);

  const onSetFilters = (e) => {
    stateSetter.setCursor("");
    stateSetter.setPrevCursors([]);
    stateSetter.setFilters(e);
  };

  return (
    <Box>
      <Flex
        align="end"
        justify="between"
        css={{
          mb: "$3",
          borderBottom: "1px solid",
          borderColor: border ? "$neutral5" : "transparent",
          pb: border ? "$2" : 0,
        }}>
        <Box>{header}</Box>
        <Flex css={{ alignItems: "center" }}>
          {state.selectedRows.length ? (
            <Flex css={{ ai: "center" }}>
              <Flex css={{ ai: "center", mr: "$3" }}>
                <Box css={{ fontSize: "$2", color: "$primary9" }}>
                  {state.selectedRows.length} selected
                </Box>
                <Box
                  css={{ height: 18, width: "1px", bc: "$primary7", mx: "$3" }}
                />
                <Box
                  css={{
                    cursor: "pointer",
                    fontSize: "$2",
                    color: "$violet11",
                  }}
                  onClick={() => toggleAllRowsSelected(false)}>
                  Deselect
                </Box>
              </Flex>
              {selectAction && (
                <Button
                  size="2"
                  // @ts-ignore
                  css={{
                    display: "flex",
                    alignItems: "center",
                  }}
                  {...selectAction}
                />
              )}
            </Flex>
          ) : (
            <>
              {!viewAll && filterItems && (
                <TableFilter
                  items={filterItems}
                  onDone={(e) => onSetFilters(e)}
                />
              )}
              {createAction && (
                <Button
                  variant="primary"
                  size="2"
                  // @ts-ignore
                  css={{ display: "flex", alignItems: "center" }}
                  {...createAction}
                />
              )}
            </>
          )}
        </Flex>
      </Flex>
      {isLoading ? (
        <Flex
          align="center"
          justify="center"
          css={{ height: "calc(100vh - 400px)" }}>
          <Spinner />
        </Flex>
      ) : !data?.count ? (
        !JSON.parse(state.stringifiedFilters).length ? (
          emptyState
        ) : (
          <Flex
            direction="column"
            justify="center"
            css={{
              margin: "0 auto",
              height: "calc(100vh - 400px)",
              maxWidth: 300,
            }}>
            <Heading css={{ fontWeight: 500, mb: "$3" }}>
              No results found
            </Heading>
            <Text variant="gray" css={{ lineHeight: 1.5, mb: "$3" }}>
              There aren't any results for that query.
            </Text>
          </Flex>
        )
      ) : (
        <Box css={{ overflow: showOverflow ? "visible" : "hidden" }}>
          <Box css={{ overflowX: showOverflow ? "visible" : "auto" }}>
            <Table {...getTableProps()}>
              <Thead>
                {headerGroups.map((headerGroup) => (
                  <Tr {...headerGroup.getHeaderGroupProps()}>
                    {headerGroup.headers.map((column, i) => {
                      const withHelpTooltip =
                        someColumnCanSort &&
                        i === headerGroup.headers.length - 1;
                      return (
                        <Td
                          as={i === 0 ? Th : Td}
                          scope="col"
                          css={{
                            pl: i === 0 ? "$1" : 0,
                            width:
                              i === 0 && rowSelection === "all"
                                ? "30px"
                                : "auto",
                          }}
                          {...column.getHeaderProps(
                            // @ts-ignore
                            column.getSortByToggleProps()
                          )}>
                          <Flex
                            css={{
                              ai: "center",
                              mr: withHelpTooltip ? "$3" : 0,
                            }}>
                            <Box css={{ fontSize: "$2", whiteSpace: "nowrap" }}>
                              {column.render("Header")}
                            </Box>
                            {/*@ts-ignore */}
                            {column.canSort && (
                              <Box css={{ ml: "$2" }}>
                                {/* @ts-ignore */}
                                {column.isSorted
                                  ? // @ts-ignore
                                    column.isSortedDesc
                                    ? " â­£"
                                    : " â­¡"
                                  : " â­¥"}
                              </Box>
                            )}
                          </Flex>
                        </Td>
                      );
                    })}
                  </Tr>
                ))}
              </Thead>
              <Tbody {...getTableBodyProps()}>
                {rows.map((row: Row<object>) => {
                  prepareRow(row);
                  return (
                    <Tr
                      css={{
                        "&:hover": {
                          backgroundColor: "$neutral2",
                          cursor,
                        },
                      }}
                      {...row.getRowProps()}>
                      {row.cells.map((cell, i) => (
                        <Td
                          as={i === 0 ? Th : Td}
                          css={{
                            py: 0,
                            width:
                              i === 0 && rowSelection === "all"
                                ? "30px"
                                : "auto",
                            ...cell.value?.css,
                          }}
                          {...cell.getCellProps()}>
                          {cell.value?.href ? (
                            <Link href={cell.value.href} passHref>
                              <A
                                css={{
                                  textDecoration: "none",
                                  py: "$2",
                                  pl: i === 0 ? "$1" : 0,
                                  display: "block",
                                  "&:hover": {
                                    textDecoration: "none",
                                  },
                                }}>
                                {cell.render("Cell")}
                              </A>
                            </Link>
                          ) : (
                            <Box css={{ py: "$2", pl: i === 0 ? "$1" : 0 }}>
                              {cell.render("Cell")}
                            </Box>
                          )}
                        </Td>
                      ))}
                    </Tr>
                  );
                })}
              </Tbody>
            </Table>
          </Box>
          {!noPagination && (
            <Flex justify="between" align="center" css={{ mt: "$4", p: "$1" }}>
              <Text>
                <b>{data?.count}</b> results
              </Text>
              {viewAll ? (
                <Link href={viewAll} passHref>
                  <A variant="primary" css={{ display: "flex", ai: "center" }}>
                    <Box>View all</Box> <ArrowRightIcon />
                  </A>
                </Link>
              ) : (
                <Flex>
                  <Button
                    css={{ marginRight: "6px" }}
                    onClick={handlePreviousPage}
                    disabled={state.prevCursors.length <= 0}>
                    Previous
                  </Button>
                  <Button
                    onClick={handleNextPage}
                    disabled={
                      state.nextCursor === "" ||
                      // @ts-ignore
                      state.pageSize >= parseFloat(data?.count)
                    }>
                    Next
                  </Button>
                </Flex>
              )}
            </Flex>
          )}
        </Box>
      )}
    </Box>
  );
}
Example #8
Source File: add-product-table.tsx    From admin with MIT License 4 votes vote down vote up
AddProductsTable: React.FC<AddProductsTableProps> = ({
  existingRelations,
  onSubmit,
  onClose,
}) => {
  const PAGE_SIZE = 10
  const [query, setQuery] = useState("")
  const [offset, setOffset] = useState(0)
  const [numPages, setNumPages] = useState(0)
  const [currentPage, setCurrentPage] = useState(0)

  const [selectedProducts, setSelectedProducts] = useState<any[]>([])
  const [removedProducts, setRemovedProducts] = useState<any[]>([])

  const debouncedSearchTerm = useDebounce(query, 500)

  const { isLoading, count, products } = useAdminProducts({
    q: debouncedSearchTerm,
    limit: PAGE_SIZE,
    offset,
  })

  const columns = useCollectionProductColumns()

  const {
    rows,
    prepareRow,
    getTableBodyProps,
    getTableProps,
    canPreviousPage,
    canNextPage,
    pageCount,
    nextPage,
    previousPage,
    state: { pageIndex, pageSize, selectedRowIds },
  } = useTable(
    {
      data: products || [],
      columns: columns,
      manualPagination: true,
      initialState: {
        pageIndex: currentPage,
        pageSize: PAGE_SIZE,
        selectedRowIds: existingRelations.reduce((prev, { id }) => {
          prev[id] = true
          return prev
        }, {}),
      },
      pageCount: numPages,
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row) => row.id,
    },
    usePagination,
    useRowSelect,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        {
          id: "selection",
          Cell: ({ row }) => {
            return (
              <Table.Cell className="w-[5%] pl-base">
                <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
              </Table.Cell>
            )
          },
        },
        ...columns,
      ])
    }
  )

  useEffect(() => {
    setSelectedProducts((selectedProducts) =>
      [
        ...selectedProducts.filter(
          (sv) =>
            Object.keys(selectedRowIds).findIndex((id) => id === sv.id) > -1
        ),
        ...(products?.filter(
          (p) =>
            selectedProducts.findIndex((sv) => sv.id === p.id) < 0 &&
            Object.keys(selectedRowIds).findIndex((id) => id === p.id) > -1
        ) || []),
      ].filter((p) => existingRelations.findIndex((ap) => ap.id === p.id) < 0)
    )

    setRemovedProducts([
      ...existingRelations.filter(
        (ap) => Object.keys(selectedRowIds).findIndex((id) => id === ap.id) < 0
      ),
    ])
  }, [selectedRowIds])

  useEffect(() => {
    const controlledPageCount = Math.ceil(count! / PAGE_SIZE)
    setNumPages(controlledPageCount)
  }, [products, count, PAGE_SIZE])

  const handleNext = () => {
    if (canNextPage) {
      setOffset((old) => old + pageSize)
      setCurrentPage((old) => old + 1)
      nextPage()
    }
  }

  const handlePrev = () => {
    if (canPreviousPage) {
      setOffset((old) => old - pageSize)
      setCurrentPage((old) => old - 1)
      previousPage()
    }
  }

  const handleSearch = (q) => {
    setOffset(0)
    setQuery(q)
  }

  const [disabled, setDisabled] = useState(true)

  useEffect(() => {
    if (selectedProducts.length > 0 || removedProducts.length > 0) {
      setDisabled(false)
      return
    }

    setDisabled(true)
  }, [selectedProducts, removedProducts])

  const handleSubmit = () => {
    onSubmit(
      selectedProducts.map((p) => p.id),
      removedProducts.map((p) => p.id)
    )
  }

  return (
    <Modal handleClose={onClose}>
      <Modal.Body>
        <Modal.Header handleClose={onClose}>
          <h3 className="inter-xlarge-semibold">Add Products</h3>
        </Modal.Header>
        <Modal.Content>
          <div className="w-full flex flex-col justify-between h-[650px]">
            <Table
              enableSearch
              handleSearch={handleSearch}
              searchPlaceholder="Search Products"
              {...getTableProps()}
              className="flex-grow"
            >
              {isLoading || !products ? (
                <div className="inter-small-regular text-grey-40 flex flex-grow justify-center items-center">
                  <Spinner size="large" variant="secondary" />
                </div>
              ) : (
                <Table.Body {...getTableBodyProps()}>
                  {rows.map((row) => {
                    prepareRow(row)
                    return (
                      <Table.Row
                        color={"inherit"}
                        {...row.getRowProps()}
                        className="px-base"
                      >
                        {row.cells.map((cell, index) => {
                          return cell.render("Cell", { index })
                        })}
                      </Table.Row>
                    )
                  })}
                </Table.Body>
              )}
            </Table>
            <TablePagination
              count={count!}
              limit={PAGE_SIZE}
              offset={offset}
              pageSize={offset + rows.length}
              title="Products"
              currentPage={pageIndex + 1}
              pageCount={pageCount}
              nextPage={handleNext}
              prevPage={handlePrev}
              hasNext={canNextPage}
              hasPrev={canPreviousPage}
            />
          </div>
        </Modal.Content>
        <Modal.Footer>
          <div className="flex items-center justify-end gap-x-xsmall w-full">
            <Button
              variant="ghost"
              size="small"
              className="w-eventButton"
              onClick={onClose}
            >
              Cancel
            </Button>
            <Button
              variant="primary"
              size="small"
              className="w-eventButton"
              onClick={handleSubmit}
              disabled={disabled}
            >
              Save
            </Button>
          </div>
        </Modal.Footer>
      </Modal.Body>
    </Modal>
  )
}
Example #9
Source File: index.tsx    From admin with MIT License 4 votes vote down vote up
CollectionProductTable: React.FC<CollectionProductTableProps> = ({
  addedProducts,
  setProducts,
}) => {
  const [query, setQuery] = useState("")
  const [limit, setLimit] = useState(10)
  const [offset, setOffset] = useState(0)
  const [numPages, setNumPages] = useState(0)
  const [currentPage, setCurrentPage] = useState(0)
  const [filteringOptions, setFilteringOptions] = useState<
    FilteringOptionProps[]
  >([])

  const [selectedProducts, setSelectedProducts] = useState<any[]>([])

  const debouncedSearchTerm = useDebounce(query, 500)

  const { isLoading, count, products } = useAdminProducts({
    q: debouncedSearchTerm,
    limit: limit,
    offset,
  })

  useEffect(() => {
    setFilteringOptions([
      {
        title: "Sort by",
        options: [
          {
            title: "All",
            onClick: () => {},
          },
          {
            title: "Newest",
            onClick: () => {},
          },
          {
            title: "Oldest",
            onClick: () => {},
          },
        ],
      },
    ])
  }, [products])

  const columns = useCollectionProductColumns() as readonly Column<any[]>[]

  const {
    rows,
    prepareRow,
    getTableBodyProps,
    getTableProps,
    canPreviousPage,
    canNextPage,
    pageCount,
    nextPage,
    previousPage,
    // Get the state from the instance
    state: { pageIndex, pageSize, selectedRowIds },
  } = useTable(
    {
      data: products || [],
      columns: columns,
      manualPagination: true,
      initialState: {
        pageIndex: currentPage,
        pageSize: limit,
        selectedRowIds: addedProducts?.reduce((prev, { id }) => {
          prev[id] = true
          return prev
        }, {}),
      },
      pageCount: numPages,
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row) => row.id,
    },
    usePagination,
    useRowSelect,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        {
          id: "selection",
          Cell: ({ row }) => {
            return (
              <Table.Cell className="w-[5%] pl-base">
                <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
              </Table.Cell>
            )
          },
        },
        ...columns,
      ])
    }
  )

  useEffect(() => {
    setSelectedProducts((selectedProducts) => [
      ...selectedProducts.filter(
        (sv) => Object.keys(selectedRowIds).findIndex((id) => id === sv.id) > -1
      ),
      ...(products?.filter(
        (p) =>
          selectedProducts.findIndex((sv) => sv.id === p.id) < 0 &&
          Object.keys(selectedRowIds).findIndex((id) => id === p.id) > -1
      ) || []),
    ])
  }, [selectedRowIds])

  const handleNext = () => {
    if (canNextPage) {
      setOffset((old) => old + pageSize)
      setCurrentPage((old) => old + 1)
      nextPage()
    }
  }

  const handlePrev = () => {
    if (canPreviousPage) {
      setOffset((old) => old - pageSize)
      setCurrentPage((old) => old - 1)
      previousPage()
    }
  }

  const handleSearch = (q) => {
    setOffset(0)
    setQuery(q)
  }

  useEffect(() => {
    console.log("products", selectedProducts)
  }, [selectedProducts])

  return (
    <div className="w-full h-full flex flex-col justify-between overflow-y-auto">
      <Table
        enableSearch
        handleSearch={handleSearch}
        searchPlaceholder="Search Products"
        filteringOptions={filteringOptions}
        {...getTableProps()}
        className="h-full"
      >
        {isLoading || !products ? null : (
          <Table.Body {...getTableBodyProps()}>
            {rows.map((row) => {
              prepareRow(row)
              return (
                <Table.Row
                  color={"inherit"}
                  {...row.getRowProps()}
                  className="px-base"
                >
                  {row.cells.map((cell, index) => {
                    return cell.render("Cell", { index })
                  })}
                </Table.Row>
              )
            })}
          </Table.Body>
        )}
      </Table>
      <TablePagination
        count={count!}
        limit={limit}
        offset={offset}
        pageSize={offset + rows.length}
        title="Products"
        currentPage={pageIndex + 1}
        pageCount={pageCount}
        nextPage={handleNext}
        prevPage={handlePrev}
        hasNext={canNextPage}
        hasPrev={canPreviousPage}
      />
    </div>
  )
}
Example #10
Source File: edit-customers-table.tsx    From admin with MIT License 4 votes vote down vote up
/*
 * Container for the "edit customers" table.
 */
function EditCustomersTable(props: EditCustomersTableProps) {
  const {
    setSelectedCustomerIds,
    selectedCustomerIds,
    handleSubmit,
    onClose,
  } = props

  const {
    paginate,
    setQuery,
    setFilters,
    filters,
    queryObject,
  } = useQueryFilters(defaultQueryProps)

  const [numPages, setNumPages] = useState(0)
  const [activeGroupId, setActiveGroupId] = useState()

  const { customer_groups } = useAdminCustomerGroups({ expand: "customers" })
  const { customers = [], count = 0 } = useAdminCustomers({
    ...queryObject,
    groups: activeGroupId ? [activeGroupId] : null,
  })

  useEffect(() => {
    if (typeof count !== "undefined") {
      const controlledPageCount = Math.ceil(count / queryObject.limit)
      setNumPages(controlledPageCount)
    }
  }, [count])

  const tableConfig = {
    columns: CUSTOMER_GROUPS_CUSTOMERS_TABLE_COLUMNS,
    data: customers,
    initialState: {
      pageSize: queryObject.limit,
      pageIndex: queryObject.offset / queryObject.limit,
      selectedRowIds: selectedCustomerIds.reduce((prev, id) => {
        prev[id] = true
        return prev
      }, {}),
    },
    pageCount: numPages,
    autoResetSelectedRows: false,
    manualPagination: true,
    autoResetPage: false,
    getRowId: (row) => row.id,
  }

  const table = useTable(tableConfig, usePagination, useRowSelect)

  useEffect(() => {
    setSelectedCustomerIds(Object.keys(table.state.selectedRowIds))
  }, [table.state.selectedRowIds])

  useEffect(() => {
    setFilters("offset", 0)
    table.gotoPage(0)
  }, [activeGroupId])

  const filteringOptions = [
    {
      title: "Groups",
      options: [
        {
          title: "All",
          onClick: () => setActiveGroupId(null),
        },
        ...(customer_groups || []).map((g) => ({
          title: g.name,
          count: g.customers.length,
          onClick: () => setActiveGroupId(g.id),
        })),
      ],
    },
  ]

  const handleNext = () => {
    if (!table.canNextPage) {
      return
    }

    paginate(1)
    table.nextPage()
  }

  const handlePrev = () => {
    if (!table.canPreviousPage) {
      return
    }

    paginate(-1)
    table.previousPage()
  }

  const handleSearch = (text: string) => {
    setQuery(text)

    if (text) {
      table.gotoPage(0)
    }
  }

  return (
    <Modal handleClose={onClose}>
      <Modal.Body>
        <Modal.Header handleClose={onClose}>
          <h3 className="inter-xlarge-semibold">Edit Customers</h3>
        </Modal.Header>

        <Modal.Content>
          <div className="w-full flex flex-col justify-between h-[650px]">
            <Table
              filteringOptions={filteringOptions}
              enableSearch
              handleSearch={handleSearch}
              searchValue={queryObject.q}
              {...table.getTableProps()}
            >
              <Table.Head>
                {table.headerGroups?.map((headerGroup) => (
                  <EditCustomersTableHeaderRow headerGroup={headerGroup} />
                ))}
              </Table.Head>

              <Table.Body {...table.getTableBodyProps()}>
                {table.rows.map((row) => {
                  table.prepareRow(row)
                  return <EditCustomersTableRow row={row} />
                })}
              </Table.Body>
            </Table>

            <TablePagination
              count={count!}
              limit={queryObject.limit}
              offset={queryObject.offset}
              pageSize={queryObject.offset + table.rows.length}
              title="Customers"
              currentPage={table.state.pageIndex + 1}
              pageCount={table.pageCount}
              nextPage={handleNext}
              prevPage={handlePrev}
              hasNext={table.canNextPage}
              hasPrev={table.canPreviousPage}
            />
          </div>
        </Modal.Content>

        <Modal.Footer>
          <div className="flex items-center justify-end gap-x-xsmall w-full">
            <Button
              variant="ghost"
              size="small"
              className="w-eventButton"
              onClick={onClose}
            >
              Cancel
            </Button>
            <Button
              variant="primary"
              size="small"
              className="w-eventButton"
              onClick={handleSubmit}
            >
              Save
            </Button>
          </div>
        </Modal.Footer>
      </Modal.Body>
    </Modal>
  )
}
Example #11
Source File: price-list-table.tsx    From admin with MIT License 4 votes vote down vote up
/*
 * Root component of the price lists table.
 */
export function PriceListTable(props: PriceListTableProps) {
  const {
    priceLists,
    queryObject,
    count,
    paginate,
    setQuery,
    columns,
    options,
  } = props

  const tableConfig: TableOptions<PriceList> = {
    columns: columns,
    data: priceLists || [],
    initialState: {
      pageSize: queryObject.limit,
      pageIndex: queryObject.offset / queryObject.limit,
    },
    pageCount: Math.ceil(count / queryObject.limit),
    manualPagination: true,
    autoResetPage: false,
  }

  const table = useTable(tableConfig, useSortBy, usePagination, useRowSelect)

  // ********* HANDLERS *********

  const handleNext = () => {
    if (!table.canNextPage) {
      return
    }

    paginate(1)
    table.nextPage()
  }

  const handlePrev = () => {
    if (!table.canPreviousPage) {
      return
    }

    paginate(-1)
    table.previousPage()
  }

  const handleSearch = (text: string) => {
    setQuery(text)

    if (text) {
      table.gotoPage(0)
    }
  }

  const debouncedSearch = React.useMemo(() => debounce(handleSearch, 300), [])

  // ********* RENDER *********

  return (
    <>
      <Table
        {...table.getTableProps()}
        {...options}
        enableSearch={options.enableSearch}
        handleSearch={options.enableSearch ? debouncedSearch : undefined}
        filteringOptions={options.filter}
      >
        {/* HEAD */}
        <Table.Head>
          {table.headerGroups?.map((headerGroup, ind) => (
            <PriceListTableHeaderRow key={ind} headerGroup={headerGroup} />
          ))}
        </Table.Head>

        {/* BODY */}
        <Table.Body {...table.getTableBodyProps()}>
          {table.rows.map((row) => {
            table.prepareRow(row)
            return <PriceListTableRow row={row} />
          })}
        </Table.Body>
      </Table>

      {/* PAGINATION */}
      <TablePagination
        count={count}
        limit={queryObject.limit}
        offset={queryObject.offset}
        pageSize={queryObject.offset + table.rows.length}
        title="Price Lists"
        currentPage={table.state.pageIndex + 1}
        pageCount={table.pageCount}
        nextPage={handleNext}
        prevPage={handlePrev}
        hasNext={table.canNextPage}
        hasPrev={table.canPreviousPage}
      />
    </>
  )
}
Example #12
Source File: index.tsx    From admin with MIT License 4 votes vote down vote up
SelectableTable = <
  T extends Product | CustomerGroup | ProductCollection | ProductTag
>({
  label,
  resourceName = "",
  selectedIds = [],
  isLoading,
  totalCount = 0,
  data,
  columns,
  onChange,
  options,
  renderRow,
  renderHeaderGroup,
  setQuery,
  queryObject,
  paginate,
}: SelectableTableProps<T>) => {
  const table = useTable<T>(
    {
      columns,
      data: data || [],
      manualPagination: true,
      initialState: {
        pageIndex: queryObject.offset / queryObject.limit,
        pageSize: queryObject.limit,
        selectedRowIds: selectedIds.reduce((prev, id) => {
          prev[id] = true
          return prev
        }, {} as Record<string, boolean>),
      },
      pageCount: Math.ceil(totalCount / queryObject.limit),
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row: any) => row.id,
    },
    useSortBy,
    usePagination,
    useRowSelect,
    useSelectionColumn
  )

  useEffect(() => {
    if (onChange) {
      onChange(Object.keys(table.state.selectedRowIds))
    }
  }, [table.state.selectedRowIds])

  const handleNext = () => {
    if (!table.canNextPage) {
      return
    }

    paginate(1)
    table.nextPage()
  }

  const handlePrev = () => {
    if (!table.canPreviousPage) {
      return
    }

    paginate(-1)
    table.previousPage()
  }

  const handleSearch = (text: string) => {
    setQuery(text)

    if (text) {
      table.gotoPage(0)
    }
  }

  const debouncedSearch = React.useMemo(() => debounce(handleSearch, 300), [])

  return (
    <div>
      {label && <div className="inter-base-semibold my-large">{label}</div>}
      <Table
        {...options}
        {...table.getTableProps()}
        handleSearch={options.enableSearch ? debouncedSearch : undefined}
      >
        {renderHeaderGroup && (
          <Table.Head>
            {table.headerGroups?.map((headerGroup) =>
              renderHeaderGroup({ headerGroup })
            )}
          </Table.Head>
        )}

        <Table.Body {...table.getTableBodyProps()}>
          {isLoading ? (
            <Spinner size="large" />
          ) : (
            table.rows.map((row, i) => {
              table.prepareRow(row)
              return renderRow({ row })
            })
          )}
        </Table.Body>
      </Table>

      <TablePagination
        count={totalCount!}
        limit={queryObject.limit}
        offset={queryObject.offset}
        pageSize={queryObject.offset + table.rows.length}
        title={resourceName}
        currentPage={table.state.pageIndex + 1}
        pageCount={table.pageCount}
        nextPage={handleNext}
        prevPage={handlePrev}
        hasNext={table.canNextPage}
        hasPrev={table.canPreviousPage}
      />
    </div>
  )
}
Example #13
Source File: selectable-table.tsx    From admin with MIT License 4 votes vote down vote up
SelectableTable = <
  T extends
    | Product
    | CustomerGroup
    | ProductCollection
    | ProductTag
    | ProductType
>({
  label,
  resourceName = "",
  selectedIds = [],
  isLoading,
  totalCount = 0,
  data,
  columns,
  onChange,
  options,
  renderRow,
  renderHeaderGroup,
  setQuery,
  queryObject,
  paginate,
}: SelectableTableProps<T>) => {
  const table = useTable<T>(
    {
      columns,
      data: data || [],
      manualPagination: true,
      initialState: {
        pageIndex: queryObject.offset / queryObject.limit,
        pageSize: queryObject.limit,
        selectedRowIds: selectedIds.reduce((prev, id) => {
          prev[id] = true
          return prev
        }, {} as Record<string, boolean>),
      },
      pageCount: Math.ceil(totalCount / queryObject.limit),
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row: any) => row.id,
    },
    useSortBy,
    usePagination,
    useRowSelect,
    useSelectionColumn
  )

  useEffect(() => {
    onChange(Object.keys(table.state.selectedRowIds))
  }, [table.state.selectedRowIds])

  const handleNext = () => {
    if (!table.canNextPage) {
      return
    }

    paginate(1)
    table.nextPage()
  }

  const handlePrev = () => {
    if (!table.canPreviousPage) {
      return
    }

    paginate(-1)
    table.previousPage()
  }

  const handleSearch = (text: string) => {
    setQuery(text)

    if (text) {
      table.gotoPage(0)
    }
  }

  const debouncedSearch = React.useMemo(() => debounce(handleSearch, 300), [])

  return (
    <div>
      <div className="inter-base-semibold my-large">{label}</div>
      <Table
        {...options}
        {...table.getTableProps()}
        handleSearch={options.enableSearch ? debouncedSearch : undefined}
      >
        {renderHeaderGroup && (
          <Table.Head>
            {table.headerGroups?.map((headerGroup) =>
              renderHeaderGroup({ headerGroup })
            )}
          </Table.Head>
        )}

        <Table.Body {...table.getTableBodyProps()}>
          {isLoading ? (
            <Spinner size="large" />
          ) : (
            table.rows.map((row) => {
              table.prepareRow(row)
              return renderRow({ row })
            })
          )}
        </Table.Body>
      </Table>

      <TablePagination
        count={totalCount!}
        limit={queryObject.limit}
        offset={queryObject.offset}
        pageSize={queryObject.offset + table.rows.length}
        title={resourceName}
        currentPage={table.state.pageIndex + 1}
        pageCount={table.pageCount}
        nextPage={handleNext}
        prevPage={handlePrev}
        hasNext={table.canNextPage}
        hasPrev={table.canPreviousPage}
      />
    </div>
  )
}
Example #14
Source File: products.tsx    From admin with MIT License 4 votes vote down vote up
RMASelectProductSubModal: React.FC<RMASelectProductSubModalProps> = ({
  onSubmit,
  selectedItems,
  isLargeModal = true,
}) => {
  const PAGE_SIZE = 12
  const { pop } = useContext(LayeredModalContext)
  const [query, setQuery] = useState("")
  const [offset, setOffset] = useState(0)
  const [numPages, setNumPages] = useState(0)
  const [currentPage, setCurrentPage] = useState(0)

  const [selectedVariants, setSelectedVariants] = useState<any[]>([])

  const debouncedSearchTerm = useDebounce(query, 500)

  const { isLoading, count, variants } = useAdminVariants({
    q: debouncedSearchTerm,
    limit: PAGE_SIZE,
    offset,
  })

  useEffect(() => {
    if (typeof count !== "undefined") {
      setNumPages(Math.ceil(count / PAGE_SIZE))
    }
  }, [count])

  const columns = useMemo(() => {
    return [
      {
        Header: "Name",
        accessor: "title",
        Cell: ({ row: { original } }) => {
          return (
            <div className="flex items-center">
              <div className="h-[40px] w-[30px] my-1.5 flex items-center mr-4">
                {original.product.thumbnail ? (
                  <img
                    src={original.product.thumbnail}
                    className="h-full object-cover rounded-soft"
                  />
                ) : (
                  <div className="flex items-center justify-center w-full h-full rounded-soft bg-grey-10">
                    <ImagePlaceholder size={16} />
                  </div>
                )}
              </div>
              <div className="flex flex-col">
                <span>{original.product.title}</span>
                {original.title}
              </div>
            </div>
          )
        },
      },
      {
        Header: "Status",
        accessor: "status",
        Cell: ({ row: { original } }) => (
          <StatusIndicator
            title={`${original.product.status
              .charAt(0)
              .toUpperCase()}${original.product.status.slice(1)}`}
            variant={getProductStatusVariant(original.product.status)}
          />
        ),
      },
      {
        Header: <div className="text-right">In Stock</div>,
        accessor: "inventory_quantity",
        Cell: ({ row: { original } }) => (
          <div className="text-right">{original.inventory_quantity}</div>
        ),
      },
    ]
  }, [])

  const {
    getTableProps,
    getTableBodyProps,
    rows,
    prepareRow,
    canPreviousPage,
    canNextPage,
    pageCount,
    nextPage,
    previousPage,
    // Get the state from the instance
    state: { pageIndex, pageSize, selectedRowIds },
  } = useTable(
    {
      columns,
      data: variants || [],
      manualPagination: true,
      initialState: {
        pageIndex: currentPage,
        pageSize: PAGE_SIZE,
        selectedRowIds: selectedItems.reduce((prev, { id }) => {
          prev[id] = true
          return prev
        }, {}),
      },
      pageCount: numPages,
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row) => row.id,
    },
    usePagination,
    useRowSelect,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        // Let's make a column for selection
        {
          id: "selection",
          // The header can use the table's getToggleAllRowsSelectedProps method
          // to render a checkbox
          Header: ({ getToggleAllRowsSelectedProps }) => {
            return (
              <div>
                <IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
              </div>
            )
          },
          // The cell can use the individual row's getToggleRowSelectedProps method
          // to the render a checkbox
          Cell: ({ row }) => {
            return (
              <div>
                <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
              </div>
            )
          },
        },
        ...columns,
      ])
    }
  )

  useEffect(() => {
    setSelectedVariants((selectedVariants) => [
      ...selectedVariants.filter(
        (sv) => Object.keys(selectedRowIds).findIndex((id) => id === sv.id) > -1
      ),
      ...(variants?.filter(
        (v) =>
          selectedVariants.findIndex((sv) => sv.id === v.id) < 0 &&
          Object.keys(selectedRowIds).findIndex((id) => id === v.id) > -1
      ) || []),
    ])
  }, [selectedRowIds])

  const handleNext = () => {
    if (canNextPage) {
      setOffset((old) => old + pageSize)
      setCurrentPage((old) => old + 1)
      nextPage()
    }
  }

  const handlePrev = () => {
    if (canPreviousPage) {
      setOffset((old) => Math.max(old - pageSize, 0))
      setCurrentPage((old) => old - 1)
      previousPage()
    }
  }

  const handleSearch = (q) => {
    setOffset(0)
    setCurrentPage(0)
    setQuery(q)
  }

  const handleSubmit = () => {
    onSubmit(selectedVariants)
    pop()
  }

  return (
    <>
      <Modal.Content isLargeModal={isLargeModal}>
        <div className="min-h-[680px]">
          <Table
            immediateSearchFocus
            enableSearch
            searchPlaceholder="Search Products.."
            handleSearch={handleSearch}
            {...getTableProps()}
          >
            <Table.Body {...getTableBodyProps()}>
              {isLoading ? (
                <Spinner size="large" />
              ) : (
                rows.map((row, i) => {
                  prepareRow(row)
                  return (
                    <Table.Row {...row.getRowProps()}>
                      {row.cells.map((cell) => {
                        return (
                          <Table.Cell {...cell.getCellProps()}>
                            {cell.render("Cell")}
                          </Table.Cell>
                        )
                      })}
                    </Table.Row>
                  )
                })
              )}
            </Table.Body>
          </Table>
          <TablePagination
            count={count!}
            limit={PAGE_SIZE}
            offset={offset}
            pageSize={offset + rows.length}
            title="Products"
            currentPage={pageIndex + 1}
            pageCount={pageCount}
            nextPage={handleNext}
            prevPage={handlePrev}
            hasNext={canNextPage}
            hasPrev={canPreviousPage}
          />
        </div>
      </Modal.Content>
      <Modal.Footer isLargeModal={isLargeModal}>
        <div className="flex w-full justify-end gap-x-xsmall">
          <Button
            variant="ghost"
            size="small"
            className="w-[112px]"
            onClick={() => pop()}
          >
            Back
          </Button>
          <Button
            variant="primary"
            className="w-[112px]"
            size="small"
            onClick={handleSubmit}
          >
            Add
          </Button>
        </div>
      </Modal.Footer>
    </>
  )
}
Example #15
Source File: selectable-table.tsx    From admin with MIT License 4 votes vote down vote up
SelectableTable: React.FC<SelectableTableProps> = ({
  showSearch = true,
  label,
  objectName,
  selectedIds = [],
  isLoading,
  pagination,
  totalCount,
  data,
  columns,
  onPaginationChange,
  onChange,
  onSearch,
}) => {
  const handleQueryChange = (newQuery) => {
    onPaginationChange(newQuery)
  }

  const currentPage = useMemo(() => {
    return Math.floor(pagination.offset / pagination.limit)
  }, [pagination])

  const numPages = useMemo(() => {
    if (totalCount && pagination.limit) {
      return Math.ceil(totalCount / pagination.limit)
    }
    return 0
  }, [totalCount, pagination])

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    canPreviousPage,
    canNextPage,
    pageCount,
    nextPage,
    previousPage,
    // Get the state from the instance
    state: { pageIndex, pageSize, selectedRowIds },
  } = useTable(
    {
      columns,
      data: data || [],
      manualPagination: true,
      initialState: {
        pageIndex: currentPage,
        pageSize: pagination.limit,
        selectedRowIds: selectedIds.reduce((prev, id) => {
          prev[id] = true
          return prev
        }, {}),
      },
      pageCount: numPages,
      autoResetSelectedRows: false,
      autoResetPage: false,
      getRowId: (row) => row.id,
    },
    usePagination,
    useRowSelect,
    (hooks) => {
      hooks.visibleColumns.push((columns) => [
        // Let's make a column for selection
        {
          id: "selection",
          // The header can use the table's getToggleAllRowsSelectedProps method
          // to render a checkbox
          Header: ({ getToggleAllRowsSelectedProps }) => {
            return (
              <div>
                <IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
              </div>
            )
          },
          // The cell can use the individual row's getToggleRowSelectedProps method
          // to the render a checkbox
          Cell: ({ row }) => {
            return (
              <div>
                <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
              </div>
            )
          },
        },
        ...columns,
      ])
    }
  )

  useEffect(() => {
    onChange(Object.keys(selectedRowIds))
  }, [selectedRowIds])

  const handleNext = () => {
    if (canNextPage) {
      handleQueryChange({
        ...pagination,
        offset: pagination.offset + pagination.limit,
      })
      nextPage()
    }
  }

  const handlePrev = () => {
    if (canPreviousPage) {
      handleQueryChange({
        ...pagination,
        offset: Math.max(pagination.offset - pagination.limit, 0),
      })
      previousPage()
    }
  }

  return (
    <div>
      <div className="inter-base-semibold my-large">{label}</div>
      <Table
        immediateSearchFocus={showSearch}
        enableSearch={showSearch}
        searchPlaceholder="Search Products.."
        handleSearch={onSearch}
        {...getTableProps()}
      >
        <Table.Body {...getTableBodyProps()}>
          {isLoading ? (
            <Spinner size="large" />
          ) : (
            rows.map((row, i) => {
              prepareRow(row)
              return (
                <Table.Row {...row.getRowProps()}>
                  {row.cells.map((cell) => {
                    return (
                      <Table.Cell {...cell.getCellProps()}>
                        {cell.render("Cell")}
                      </Table.Cell>
                    )
                  })}
                </Table.Row>
              )
            })
          )}
        </Table.Body>
      </Table>
      <TablePagination
        count={totalCount!}
        limit={pagination.limit}
        offset={pagination.offset}
        pageSize={pagination.offset + rows.length}
        title={objectName}
        currentPage={pageIndex + 1}
        pageCount={pageCount}
        nextPage={handleNext}
        prevPage={handlePrev}
        hasNext={canNextPage}
        hasPrev={canPreviousPage}
      />
    </div>
  )
}