Source

Frameworks/AsyncTableTools/hooks/useTableView/views/helpers.js

import React from 'react';
import { treeRow } from '@patternfly/react-table';

export const columnProp = (column) =>
  column.key || column.original?.toLowerCase() || column.title?.toLowerCase();

// TODO Make renderFunc obsolete
// We carried over this pattern to keep compatibility with old columns
// Only having to pass a "component" prop for columns and pass the "item" attributes as props would be nicer
export const itemRow = ({ rowProps, props = {}, ...item }, columns) => ({
  props,
  ...rowProps,
  cells: columns.map((column) => ({
    title: column.renderFunc
      ? column.renderFunc(undefined, undefined, item)
      : item[columnProp(column)],
  })),
  item,
});

export const applyTransformations = (
  item,
  rowsForItem,
  transformers,
  runningIndex,
) =>
  transformers.reduce(
    (currentRowsForItem, transformer) =>
      transformer
        ? transformer(item, currentRowsForItem, runningIndex)
        : currentRowsForItem,
    rowsForItem,
  );

/**
 * This function is used to assign the correct rowIndex for rows and for their details rows' parent prop
 *
 *  @returns {Function} A function to call whenever a row is added, it returns an integer that can optionally be used.
 *
 */
const indexRunner = () => {
  let index = 0;

  return () => {
    const currentIndex = index;
    index++;
    return currentIndex;
  };
};

export const buildRows = (items, columns, rowTransformers) => {
  const runningIndex = indexRunner();

  return items?.flatMap(({ rowColumns, ...item }) => {
    const rowIndex = runningIndex();
    const mainRow = itemRow({ ...item, rowIndex }, rowColumns || columns);

    return applyTransformations(item, [mainRow], rowTransformers, runningIndex);
  });
};

export const treeColumns = (columns, onCollapse, onSelect) => [
  {
    ...columns[0],
    cellTransforms: [
      ...(columns[0].cellTransforms || []),
      treeRow(
        (...args) => onCollapse?.(...args),
        onSelect && ((...args) => onSelect?.(...args)),
      ),
    ],
  },
  ...columns.slice(1),
];

export const collectLeaves = (tableTree, itemId) => {
  const pickBranch = (basket, branch, _idx, _arr, inBranchArg) => {
    const inBranch = inBranchArg || (itemId ? branch.itemId === itemId : true);
    const twigLeaves = branch?.twigs?.flatMap((twig) =>
      pickBranch([], twig, _idx, _arr, inBranch),
    );

    return [
      ...basket,
      ...(twigLeaves || []),
      ...(inBranch ? branch.leaves || [] : []),
      ...(inBranch ? (branch.leave ? [branch.leave] : []) : []),
    ];
  };

  return tableTree.reduce(pickBranch, []);
};

export const getOnTreeSelect = (options) => {
  const { select, deselect } = options.bulkSelect || {};

  return (
    options.bulkSelect &&
    ((...args) => {
      const row = args[4];
      const idsForSelect = row.item.isTreeBranch
        ? collectLeaves(options.tableTree, row.item.itemId).map(
            ({ itemId }) => itemId,
          )
        : row.item.itemId;

      !row.props?.isChecked ? select(idsForSelect) : deselect(idsForSelect);
    })
  );
};

export const treeTableGroupColumns = [
  {
    key: 'title',
    renderFunc: (_u, _n, item) => <strong>{item.title}</strong>,
  },
];

export const groupItem = (item, isChecked, level, setSize) => ({
  ...item,
  rowColumns: treeTableGroupColumns,
  isTreeBranch: true,
  props: {
    isChecked,
    'aria-level': level,
    'aria-setsize':
      setSize || (item.twigs?.length || 0) + (item.leaves?.length || 0),
  },
});

export const isBranchChecked = (tableTree, item, isItemSelected) => {
  const leaves = collectLeaves(tableTree, item.itemId);
  const anySprouts = leaves?.length > 0;

  if (anySprouts && leaves.every(({ itemId }) => isItemSelected(itemId))) {
    return true;
  }

  if (anySprouts && leaves.some(({ itemId }) => isItemSelected(itemId))) {
    return null;
  }

  return false;
};