0.6.2 • Published 3 years ago

@jeremyling/react-material-ui-enhanced-table v0.6.2

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

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

PropTypeDefaultDescription
titlestringundefinedTable title
rowsarrayrequiredTable row data
headersarrayrequiredTable headers. Customise with props (see below)
densebooltrueWhether to use dense prop for Material UI's Table
orderstringascColumn sort order. One of :asc,desc
orderBystringidAttribute to sort column by
loadingboolfalseWhether to display loading Backdrop component
pagenumber1Current page
totalCountnumberundefinedTotal result count
rowsPerPagenumber10Number of rows per page
selectedarray[]Selected row
descriptorAttributestringundefinedAttributed used to display descriptor for selected row in toolbar
handleRowClickfunc() => {}Method to handle row click event
handleRequestSortfunc() => {}Method to handle column sort click
handlePageChangefuncundefinedMethod to handle page change
handleRowsPerPageChangefuncundefinedMethod to handle rows per page change
handleUniversalFilterChangefunc() => {}Method to handle universal filter onChange event
handleDateChangefunc() => {}Method to handle date changes in date filters
handleActionClickfunc() => {}Method to handle table action click
handleNestedActionfunc() => {}Method to handle action click within a nested table in a row
handleNestedFieldChangefunc() => {}Method to handle nested field onChange event
nestedRowActionobject{}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 } }
datestringundefinedFilter dates. Must be in the form yyyy-MM-dd
datesobjectundefinedFilter dates. Must be in the form { from: "yyyy-MM-dd", to: "yyyy-MM-dd" }
actionButtonsarray["create", "edit", "delete", "filter"]Actions to include in table
showToolbarbooltrueWhether to show the toolbar
collapsibleboolfalseWhether each row should be collapsible
collapseContentarraynullArray 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.
collapseHeadersarray[]Headers for default table within collapse content. Required if collapsible = true and collapseContent prop is not passed
openRowsobject{}Object to indicate which collapsible rows should be open. Object should be of the form { [row[identifier]]: true }
identifierstringidAttribute used as row identifier
handleCollapseIconClickfunc() => {}Method to handle collapse icon click event
disableRowClickboolfalseWhether to ignore click event on row
disableSelectionboolfalseMakes rows unselectable
selectibleRowsarraynullManually define the selectible rows. Array should contain the row identifiers
refreshBadgeCountnumber0Badge count for refresh button. This can be used to indicate whether the table has pending unfetched data

Header Props

PropTypeDefaultDescription
keystringattributeAttribute used to determine row key
attributestringundefinedAttribute used to determine cell content
labelstringundefinedHeader label
multiFieldboolundefinedWhether the cell should display content from multiple attributes. Best used with data and html props.
multiFieldDataarrayundefinedArray of attributes used for multiField content. Required if multiField = true. Array should be of the form [{ attribute: "name" }]
htmlstringundefinedHTML 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
chipboolundefinedIf true, column will contain a Material UI Chip populated with attribute. The color prop can be used to determine the Chip's color
chipColorobjectundefinedUsed with chip. Object of the form { [option]: [bgColor, borderColor] }
collapseboolundefinedIf true, column will contain a collapse icon
checkboxboolundefinedIf true, column will contain a Material UI Checkbox component that is controlled by whether the row is selected
actionsarrayundefinedArray 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) => {} }
headerActionsarrayundefinedArray containing possible actions for the header. Should be of the form { id: 'edit', icon: <Edit />, tooltip: 'Edit', onClick: (event) => {}, hideCondition: false, color: indigo[400]}
componentfuncundefinedCustom component for cell content. Row data is passed as sole parameter.
stopPropagationboolundefinedUsed with component. If true, cell component click will stop further propagation to parent row.
arrayAttributestringundefinedAttribute used to populate nested table within row
childAttributestringundefinedAttribute within arrayAttribute object to display as nested row
childAttribute2stringundefinedAttribute within arrayAttribute object to display on left of childAttribute within nested row
childLabelAttributestringundefinedAttribute within arrayAttribute object to display on left of childAttribute and childAttribute2 within nested row
childAttributeLabelstringundefinedLabel for childAttribute
childAttribute2LabelstringundefinedLabel for childAttribute2
childLabelAttributeLabelstringundefinedLabel for childLabelAttribute
childActionsobjectundefinedObject 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^arrayundefinedAttribute to order nested table content. Should be of the form [attribute, asc\|desc]
numericboolundefinedIf true, content will be right-aligned
priceboolundefinedIf true, content will be prefixed with $
dateboolundefinedIf true, content will be parsed with date-fns in the format d MMM yyyy
datetimeboolundefinedIf true, content will be parsed with date-fns in the format d MMM yyyy and h:mm:ss a
timeboolundefinedIf true, content will be parsed with date-fns in the format h:mm:ss a
truncatenumberundefinedMax length of string content
widthnumberundefinedFixed width for column
minWidthnumberundefinedMinimum width for column
maxWidthnumberundefinedMaximum width for column
disablePaddingboolundefinedIf true, cell padding will be set to 0
sortableboolundefinedIf true, column will be sortable based on attribute. attribute is required for column to be sortable

^Only for nested table

0.6.2

3 years ago

0.6.1

3 years ago

0.6.0

3 years ago

0.5.3

3 years ago

0.5.0

3 years ago

0.5.2

3 years ago

0.5.1

3 years ago

0.4.6

4 years ago

0.4.5

4 years ago

0.4.4

4 years ago

0.4.1

4 years ago

0.4.0

4 years ago

0.4.3

4 years ago

0.4.2

4 years ago

0.3.9

4 years ago

0.3.8

4 years ago

0.3.7

4 years ago

0.3.6

4 years ago

0.3.5

4 years ago

0.3.0

4 years ago

0.3.2

4 years ago

0.3.1

4 years ago

0.3.4

4 years ago

0.3.3

4 years ago

0.2.1

4 years ago

0.2.2

4 years ago

0.2.0

4 years ago

0.1.9

4 years ago

0.1.8

4 years ago

0.1.7

4 years ago

0.1.4

4 years ago

0.1.6

4 years ago

0.1.5

4 years ago

0.1.2

4 years ago

0.1.1

4 years ago

0.1.0

4 years ago