Coursemology/coursemology2

View on GitHub
client/app/lib/components/table/TanStackTableBuilder/useTanStackTableBuilder.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { useState } from 'react';
import {
  Cell,
  ColumnDef,
  ColumnFiltersState,
  getCoreRowModel,
  getFacetedUniqueValues,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  Header,
  Row,
  useReactTable,
} from '@tanstack/react-table';
import { isEmpty, isString } from 'lodash';

import { RowEqualityData, TableProps } from '../adapters';
import { TableTemplate } from '../builder';
import { downloadCsv } from '../utils';

import buildTanStackColumns from './columnsBuilder';
import generateCsv from './csvGenerator';
import { customCellRender, customHeaderRender } from './customFlexRender';

type TanStackTableProps<D> = TableProps<
  Header<D, unknown>,
  Row<D>,
  Cell<D, unknown>
>;

const useTanStackTableBuilder = <D extends object>(
  props: TableTemplate<D>,
): TanStackTableProps<D> => {
  const [columns, getRealColumn] = buildTanStackColumns(
    props.columns,
    props.indexing?.rowSelectable,
    props.indexing?.indices,
  );

  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
  const [searchKeyword, setSearchKeyword] = useState('');
  const [rowSelection, setRowSelection] = useState({});
  const [pagination, setPagination] = useState({
    pageSize:
      props.pagination?.initialPageSize ??
      props.pagination?.rowsPerPage?.[0] ??
      10,
    pageIndex: props.pagination?.initialPageIndex ?? 0,
  });

  const resetPagination = (): void =>
    setPagination((current) => ({ ...current, pageIndex: 0 }));

  const table = useReactTable({
    data: props.data,
    columns,
    enableRowSelection: props.indexing?.rowSelectable,
    getRowId: props.getRowId,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: props.pagination && getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getFacetedUniqueValues: getFacetedUniqueValues(),
    onRowSelectionChange: setRowSelection,
    onColumnFiltersChange: setColumnFilters,
    onGlobalFilterChange: setSearchKeyword,
    onPaginationChange: setPagination,
    autoResetPageIndex: false,
    state: {
      rowSelection,
      columnFilters,
      globalFilter: searchKeyword.trim(),
      pagination,
      columnVisibility: Object.fromEntries(
        props.columns.map((column) => [column.title, !column.hidden]),
      ),
    },
    initialState: {
      sorting: props.sort?.initially && [
        {
          id: props.sort.initially.by,
          desc: props.sort.initially.order === 'desc',
        },
      ],
    },
  });

  const generateAndDownloadCsv = async (): Promise<void> => {
    const headers = table.options.columns.reduce<string[]>(
      (acc, column, index) => {
        const header = column.header || column.id;
        if (header && (getRealColumn(index)?.csvDownloadable ?? false)) {
          acc.push(header as string);
        }
        return acc;
      },
      [],
    );

    const csvData = await generateCsv({
      headers,
      rows: () => table.getCoreRowModel().rows,
      getRealColumn,
    });

    downloadCsv(csvData, props.csvDownload?.filename);
  };

  return {
    header: {
      headers: table.getHeaderGroups()[0]?.headers,
      forEach: (header, index) => ({
        id: header.id,
        render: customHeaderRender(header),
        className: getRealColumn(index)?.className,
        sorting: header.column.getCanSort()
          ? {
              sorted: Boolean(header.column.getIsSorted()),
              direction: header.column.getIsSorted() || undefined,
              onClickSort: header.column.getToggleSortingHandler(),
            }
          : undefined,
        filtering: header.column.getCanFilter()
          ? {
              filters: header.column.getFilterValue() as unknown[],
              uniqueFilterValues: Array.from(
                header.column.getFacetedUniqueValues().keys(),
              ).sort(),
              getFilterLabel: getRealColumn(index)?.filterProps?.getLabel,
              onAddFilter: (value): void => {
                resetPagination();
                header.column.setFilterValue((currentFilters?: unknown[]) =>
                  currentFilters?.filter((filter) => filter !== value),
                );
              },
              onClearFilters: (): void => {
                resetPagination();
                header.column.setFilterValue(undefined);
              },
              onRemoveFilter: (value): void => {
                resetPagination();
                header.column.setFilterValue((currentFilters?: unknown[]) =>
                  currentFilters ? [...currentFilters, value] : [value],
                );
              },
              tooltipLabel: props.filter?.tooltipLabel,
              clearFiltersLabel: props.filter?.clearFilterTooltipLabel,
            }
          : undefined,
      }),
    },
    body: {
      rows: table.getRowModel().rows,
      getCells: (row) => row.getVisibleCells(),
      forEachCell: (cell, _, index) => ({
        id: cell.id,
        render: customCellRender(cell),
        className: getRealColumn(index)?.className,
      }),
      forEachRow: (row) => ({
        id: row.id,
        className: props.getRowClassName?.(row.original),
        getEqualityData:
          props.getRowEqualityData &&
          ((): RowEqualityData => ({
            payload: props.getRowEqualityData?.(row.original),
            selected: rowSelection[row.id],
          })),
      }),
    },
    pagination: props.pagination && {
      allowShowAll: props.pagination.showAllRows,
      page: table.getState().pagination.pageIndex,
      pages: props.pagination.rowsPerPage,
      total: table.getFilteredRowModel().rows.length,
      pageSize: table.getState().pagination.pageSize,
      onPageChange: (page): void => table.setPageIndex(page),
      onPageSizeChange: (size): void => table.setPageSize(size),
      showAllLabel: props.pagination.showAllRowsLabel,
    },
    toolbar: {
      renderNative: props.toolbar?.show,
      alternative: {
        when: () => !isEmpty(rowSelection),
        render: () =>
          props.toolbar?.activeToolbar?.(
            table.getSelectedRowModel().rows.map((row) => row.original),
          ),
      },
      searchKeyword,
      onSearchKeywordChange: setSearchKeyword,
      onDownloadCsv: props.csvDownload && generateAndDownloadCsv,
      csvDownloadLabel: props.csvDownload?.downloadButtonLabel,
      searchPlaceholder: props.search?.searchPlaceholder,
      buttons: props.toolbar?.buttons,
    },
  };
};

export default useTanStackTableBuilder;