Skip to content

An extended React Material UI Table that aims to make creating complex tables more straightforward.

License

Notifications You must be signed in to change notification settings

jzcling/react-material-ui-enhanced-table

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React Material UI Enhanced Table

An extended React Material UI Table that aims to make creating complex tables more straightforward.

Installation

npm install --save @jeremyling/react-material-ui-enhanced-table

The following packages are peer dependencies that must be installed for this package to work.

  @mui/material
  @mui/icons-material
  @mui/lab
  date-fns
  lodash

Usage Examples

Example of a collapsible table with a nested table as the collapse content.

import React, { useEffect, useState } from "react";
import EnhancedTable from "@jeremyling/react-material-ui-enhanced-table";
import { amber, green, indigo, lightGreen, red } from "@mui/material/colors";
import { Block, MoveToInbox } from "@mui/icons-material";

export default function Orders(props) {
  const [data, setData] = useState([]);
  const [totalCount, setTotalCount] = useState();
  const [tableLoading, setTableLoading] = useState(false);
  const [order, setOrder] = useState("");
  const [orderBy, setOrderBy] = useState("");
  const [page, setPage] = useState(1);
  const [rowsPerPage, setRowsPerPage] = useState(10);

  const [selectedRow, setSelectedRow] = useState();
  const [selectedItems, setSelectedItems] = useState({});

  const [openRows, setOpenRows] = useState({});

  const [filters, setFilters] = useState();

  const [notificationsCount, setNotificationsCount] = useState();

  const headers = [
    {
      key: "collapse",
      collapse: true,
    },
    { attribute: "order_number", label: "Order No" },
    {
      key: "status",
      attribute: "status",
      label: "Status",
      chip: true,
      chipColor: {
        "In Progress": [amber[50], amber[200]],
        Completed: [green[50], green[200]],
      },
    },
    {
      attribute: "updated_at",
      datetime: true,
      label: "Updated",
    },
    {
      multiField: true,
      key: "customer",
      multiFieldData: [
        {
          attribute: "user.name",
        },
        {
          attribute: "user.email",
        },
        {
          attribute: "user.phone",
        },
      ],
      html: `
        <div><strong>{{0}}</strong></div>
        <div style="color: #777">{{1}}</div>
        <div style="color: #777">{{2}}</div>
      `,
      label: "Customer",
    },
    {
      attribute: "total",
      label: "Total",
      numeric: true,
      price: true,
    },
    {
      key: "actions",
      actions: [
        {
          id: "process",
          icon: <MoveToInbox />,
          tooltip: "Process",
          onClick: (event, row) => handleAction(event, "process", row),
          color: amber[400],
          hideCondition: (row) => {},
        },
        {
          id: "cancel",
          icon: <Block />,
          tooltip: "Cancel",
          onClick: (event, row) => handleAction(event, "cancel", row),
          color: indigo[400],
        },
      ],
      label: "Actions",
    },
  ];

  const collapseContent = () => {
    const collapseHeaders = [
      {
        key: "checkbox",
        checkbox: true,
      },
      {
        key: "image",
        label: "Image",
        component: (data) => (
          <img src={data.image.url} alt={data.name} loading="lazy" />
        ),
        stopPropagation: true,
      },
      {
        multiField: true,
        key: "product",
        multiFieldData: [
          {
            attribute: "product.name",
          },
          {
            attribute: "product.sku",
          },
        ],
        html: `
        <div><strong>{{0}}</strong></div>
        <div><strong style="color: #777">SKU: {{1}}</strong></div>
      `,
        label: "Product",
        minWidth: "200px",
      },
      {
        key: "status",
        attribute: "status",
        label: "Status",
        chip: true,
        chipColor: {
          Pending: [amber[50], amber[200]],
          Paid: [lightGreen[50], lightGreen[200]],
          Processed: [indigo[50], indigo[200]],
          Completed: [green[50], green[200]],
        },
      },
      {
        attribute: "updated_at",
        datetime: true,
        label: "Updated",
      },
      { attribute: "quantity", label: "Qty", numeric: true },
      {
        key: "actions",
        actions: [
          {
            id: "process",
            icon: <MoveToInbox />,
            tooltip: "Process",
            onClick: (event, row) => handleAction(event, "process", row),
            color: amber[400],
            hideCondition: (data) => {},
          },
          {
            id: "cancel-order",
            icon: <Block />,
            tooltip: "Cancel",
            onClick: (event, row) => handleAction(event, "cancel", row),
            color: indigo[400],
          },
        ],
        label: "Actions",
      },
    ];

    if (data) {
      return data.map((order) => (
        <EnhancedTable
          key={order.order_number}
          rows={order.order_items}
          headers={collapseHeaders}
          handleActionClick={(event, key) => handleAction(event, key)}
          handleRowClick={(event, row) =>
            handleCollapsibleTableRowClick(event, row)
          }
          selected={selectedItems[order.id]}
          showToolbar={false}
          selectibleRows={getSelectibleItems(selectedRow)}
        />
      ));
    }
  };

  const handleRowClick = (event, item) => {
    setSelectedRow(item);
  };

  function handleCollapsibleTableRowClick(event, row) {}

  function getSelectibleItems(row) {}

  function updateOpenRows(event, key, value) {
    event.stopPropagation();

    let updated = JSON.parse(JSON.stringify(openRows));
    updated[key] = value;
    setOpenRows(updated);
  }

  const handleRequestSort = (event, property) => {
    if (orderBy !== property) {
      setOrder("asc");
      setOrderBy(property);
      return;
    }

    switch (order) {
      case "asc":
        setOrder("desc");
        break;
      case "desc":
        setOrder("");
        setOrderBy("");
        break;
      default:
        break;
    }
  };

  const handlePageChange = (event, newPage) => {
    setPage(newPage);
  };

  const handleRowsPerPageChange = (event) => {
    setRowsPerPage(parseInt(event.target.value, 10));
    setPage(1);
  };

  const handleDateChange = (type, value) => {
    // Update filters
  };

  function handleAction(event, key, row = null) {}

  useEffect(() => {
    getData();

    function getData() {
      // Fetch data with filters
    }
  }, [order, orderBy, rowsPerPage, page, filters]);

  return (
    <EnhancedTable
      rows={data || []}
      totalCount={totalCount}
      headers={headers}
      order={order}
      orderBy={orderBy}
      loading={tableLoading}
      page={page}
      rowsPerPage={rowsPerPage}
      handleRowClick={handleRowClick}
      handleRequestSort={handleRequestSort}
      handlePageChange={handlePageChange}
      handleRowsPerPageChange={handleRowsPerPageChange}
      handleActionClick={(event, key) => handleAction(event, key)}
      handleDateChange={handleDateChange}
      dates={{ from: filters.dateFrom, to: filters.dateTo }}
      actionButtons={["dateFilters", "refresh"]}
      collapsible={true}
      collapseContent={collapseContent}
      refreshBadgeCount={notificationsCount}
      disableSelection={true}
      openRows={openRows}
      handleCollapseIconClick={updateOpenRows}
    />
  );
}

Example with a nested table within each row

import React, { useEffect, useState } from "react";
import EnhancedTable from "@jeremyling/react-material-ui-enhanced-table";
import { green, red } from "@mui/material/colors";

export default function Products(props) {
  const [data, setData] = useState([]);
  const [totalCount, setTotalCount] = useState();
  const [tableLoading, setTableLoading] = useState(true);
  const [order, setOrder] = useState("asc");
  const [orderBy, setOrderBy] = useState("");
  const [page, setPage] = useState(0);
  const [rowsPerPage, setRowsPerPage] = useState(10);
  const [selected, setSelected] = useState({});
  const [universalFilter, setUniversalFilter] = useState("");

  const [nestedRowAction, setNestedRowAction] = useState({});

  const [filters, setFilters] = useState();

  const headers = [
    {
      key: "images",
      label: "Images",
      component: (data) => (
        <img src={data.image.url} alt={data.name} loading="lazy" />
      ),
      stopPropagation: true,
    },
    {
      multiField: true,
      key: "product",
      multiFieldData: [
        {
          attribute: "name",
        },
        {
          attribute: "sku",
        },
      ],
      html: `
        <div><strong>{{0}}</strong></div>
        <div><strong style="color: #777">SKU: {{2}}</strong></div>
      `,
      label: "Product",
      minWidth: "200px",
    },
    {
      key: "status",
      attribute: "status",
      label: "Status",
      chip: true,
      chipColor: {
        Inactive: [red[50], red[200]],
        Active: [green[50], green[200]],
      },
    },
    { attribute: "category", label: "Category" },
    { attribute: "stock", label: "Stock", sortable: true, numeric: true },
    {
      key: "price",
      label: "Price",
      arrayAttribute: "prices",
      childAttribute: "amount",
      childAttribute2: "currency",
      childLabelAttribute: "tag",
      childActions: { delete: true, edit: true, add: true },
      numeric: true,
      price: true,
    },
  ];

  const handleRowClick = (event, item) => {
    if (item.id === selected.id) {
      setSelected({});
      return;
    }
    setSelected(item);
  };

  const handleAction = (event, key) => {};

  const handleRequestSort = (event, property) => {
    if (orderBy !== property) {
      setOrder("asc");
      setOrderBy(property);
      return;
    }

    switch (order) {
      case "asc":
        setOrder("desc");
        break;
      case "desc":
        setOrder("asc");
        setOrderBy("");
        break;
      default:
        break;
    }
  };

  const handleUniversalFilterChange = (event) => {
    setUniversalFilter(event.target.value);
  };

  const handlePageChange = (event, newPage) => {
    setPage(newPage);
  };

  const handleRowsPerPageChange = (event) => {
    setRowsPerPage(parseInt(event.target.value, 10));
    setPage(0);
  };

  const handleNestedAction = (event, type, item, id) => {};

  const handleNestedFieldChange = (parentId, id, attribute, value) => {};

  useEffect(() => {
    const getData = () => {
      // Fetch data with filters
    };

    getData();
  }, [universalFilter, order, orderBy, rowsPerPage, page, filters]);

  return (
    <EnhancedTable
      rows={data || []}
      totalCount={totalCount}
      descriptorAttribute="name"
      headers={headers}
      order={order}
      orderBy={orderBy}
      loading={tableLoading}
      page={page}
      rowsPerPage={rowsPerPage}
      selected={[selected]}
      handleRowClick={handleRowClick}
      handleRequestSort={handleRequestSort}
      handlePageChange={handlePageChange}
      handleRowsPerPageChange={handleRowsPerPageChange}
      handleNestedAction={handleNestedAction}
      handleNestedFieldChange={handleNestedFieldChange}
      handleActionClick={(event, key) => handleAction(event, key)}
      actionButtons={["create", "edit", "delete", "filter", "refresh"]}
      handleUniversalFilterChange={handleUniversalFilterChange}
      nestedRowAction={nestedRowAction}
    ></EnhancedTable>
  );
}

Props

Prop Type Default Description
title string undefined Table title
rows array required Table row data
headers array required Table headers. Customise with props (see below)
dense bool true Whether to use dense prop for Material UI's Table
order string asc Column sort order. One of :
asc,
desc
orderBy string id Attribute to sort column by
loading bool false Whether to display loading Backdrop component
page number 1 Current page
totalCount number undefined Total result count
rowsPerPage number 10 Number of rows per page
selected array [] Selected row
descriptorAttribute string undefined Attributed used to display descriptor for selected row in toolbar
handleRowClick func () => {} Method to handle row click event
handleRequestSort func () => {} Method to handle column sort click
handlePageChange func undefined Method to handle page change
handleRowsPerPageChange func undefined Method to handle rows per page change
handleUniversalFilterChange func () => {} Method to handle universal filter onChange event
handleDateChange func () => {} Method to handle date changes in date filters
handleActionClick func () => {} Method to handle table action click
handleNestedAction func () => {} Method to handle action click within a nested table in a row
handleNestedFieldChange func () => {} Method to handle nested field onChange event
nestedRowAction object {} Object indicating the actions available for each nested row. Possible actions include add or edit. Object needs to be of the form { [nestedRowIdentifier]: { add: true, edit: true } }
date string undefined Filter dates. Must be in the form yyyy-MM-dd
dates object undefined Filter dates. Must be in the form { from: "yyyy-MM-dd", to: "yyyy-MM-dd" }
actionButtons array ["create", "edit", "delete", "filter"] Actions to include in table
showToolbar bool true Whether to show the toolbar
collapsible bool false Whether each row should be collapsible
collapseContent array null Array of content for each collapsible row. Index of this array should correspond to index of rows. The collapse content for rows[0] should be collapseContent[0]. Default collapse content is a table.
collapseHeaders array [] Headers for default table within collapse content. Required if collapsible = true and collapseContent prop is not passed
openRows object {} Object to indicate which collapsible rows should be open. Object should be of the form { [row[identifier]]: true }
identifier string id Attribute used as row identifier
handleCollapseIconClick func () => {} Method to handle collapse icon click event
disableRowClick bool false Whether to ignore click event on row
disableSelection bool false Makes rows unselectable
selectibleRows array null Manually define the selectible rows. Array should contain the row identifiers
refreshBadgeCount number 0 Badge count for refresh button. This can be used to indicate whether the table has pending unfetched data

Header Props

Prop Type Default Description
key string attribute Attribute used to determine row key
attribute string undefined Attribute used to determine cell content
label string undefined Header label
multiField bool undefined Whether the cell should display content from multiple attributes. Best used with data and html props.
multiFieldData array undefined Array of attributes used for multiField content. Required if multiField = true. Array should be of the form [{ attribute: "name" }]
html string undefined HTML code for displaying content. Attribute will be substituted in for {{0}}. If used with multiField, data attributes will be substituted in for {{index}}, where index is the position index of the data array
chip bool undefined If true, column will contain a Material UI Chip populated with attribute. The color prop can be used to determine the Chip's color
chipColor object undefined Used with chip. Object of the form { [option]: [bgColor, borderColor] }
collapse bool undefined If true, column will contain a collapse icon
checkbox bool undefined If true, column will contain a Material UI Checkbox component that is controlled by whether the row is selected
actions array undefined Array containing possible actions for each row. Should be of the form { id: "cancel", icon: <Block />, tooltip: "Cancel", onClick: (event, row) => {}, color: indigo[400], hideCondition: (data) => {} }
headerActions array undefined Array containing possible actions for the header. Should be of the form { id: 'edit', icon: <Edit />, tooltip: 'Edit', onClick: (event) => {}, hideCondition: false, color: indigo[400]}
component func undefined Custom component for cell content. Row data is passed as sole parameter.
stopPropagation bool undefined Used with component. If true, cell component click will stop further propagation to parent row.
arrayAttribute string undefined Attribute used to populate nested table within row
childAttribute string undefined Attribute within arrayAttribute object to display as nested row
childAttribute2 string undefined Attribute within arrayAttribute object to display on left of childAttribute within nested row
childLabelAttribute string undefined Attribute within arrayAttribute object to display on left of childAttribute and childAttribute2 within nested row
childAttributeLabel string undefined Label for childAttribute
childAttribute2Label string undefined Label for childAttribute2
childLabelAttributeLabel string undefined Label for childLabelAttribute
childActions object undefined Object indicating which actions to show for each nested row. Actions available are add, edit and delete. Should be of the form { delete: true, edit: true, add: true }.
orderBy^ array undefined Attribute to order nested table content. Should be of the form [attribute, asc|desc]
numeric bool undefined If true, content will be right-aligned
price bool undefined If true, content will be prefixed with $
date bool undefined If true, content will be parsed with date-fns in the format d MMM yyyy
datetime bool undefined If true, content will be parsed with date-fns in the format d MMM yyyy and h:mm:ss a
time bool undefined If true, content will be parsed with date-fns in the format h:mm:ss a
truncate number undefined Max length of string content
width number undefined Fixed width for column
minWidth number undefined Minimum width for column
maxWidth number undefined Maximum width for column
disablePadding bool undefined If true, cell padding will be set to 0
sortable bool undefined If true, column will be sortable based on attribute. attribute is required for column to be sortable

^Only for nested table

About

An extended React Material UI Table that aims to make creating complex tables more straightforward.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages