Data Table

DataTable is a highly versatile component used to organize large quantities of tabular data. It supports pagination to help navigate through the data easily and efficiently, and also allows the user to sort and filter the data by columns. Additionally, it offers the ability to select one or multiple rows using checkboxes, which can be useful for performing batch actions or manipulating the data in other ways.

import React, { useEffect, useState } from "react";
import { DataTable } from "@nimbus-ds/patterns";
import { Tag, Box, IconButton, Chip } from "@nimbus-ds/components";
import {
  ChevronDownIcon,
  CheckCircleIcon,
  ChevronUpIcon,
  ExclamationTriangleIcon,
} from "@nimbus-ds/icons";


const pageSize = 5;

const orders = [
  {
    id: 10,
    clientName: "Dr. Johnnie Bins",
    total: "R$16.788,20",
    qty: "9",
    status: false,
  },
  {
    id: 9,
    clientName: "Earnest Berge",
    total: "R$62.657,83",
    qty: "3",
    status: false,
  },
  {
    id: 8,
    clientName: "Irene Purdy",
    total: "R$17.692,10",
    qty: "4",
    status: false,
  },
  {
    id: 7,
    clientName: "Owen Swift DVM",
    total: "R$60.269,67",
    qty: "5",
    status: false,
  },
  {
    id: 6,
    clientName: "Felipe Ferry",
    total: "R$75.058,94",
    qty: "3",
    status: false,
  },
  {
    id: 5,
    clientName: "Derek Kub",
    total: "R$29.068,91",
    qty: "1",
    status: false,
  },
  {
    id: 4,
    clientName: "Elisa Vandervort",
    total: "R$22.636,41",
    qty: "5",
    status: false,
  },
  {
    id: 3,
    clientName: "Rochelle Spencer",
    total: "R$76.244,05",
    qty: "9",
    status: false,
  },
  {
    id: 2,
    clientName: "Angelina Koelpin",
    total: "R$65.306,79",
    qty: "4",
    status: false,
  },
  {
    id: 1,
    clientName: "Edna Jacobi",
    total: "R$97.025,32",
    qty: "6",
    status: false,
  },
];

const Example: React.FC = () => {
  interface RowProps {
    id: number;
    clientName: string;
    total: string;
    qty: string;
    status: boolean;
  }

  const [rows, setRows] = useState<RowProps[]>(orders);
  const [checkedRows, setCheckedRows] = useState<number[]>([]);
  const [headerCheckboxStatus, setHeaderCheckboxStatus] = useState(false);
  const [headerIndeterminateStatus, setHeaderIndeterminateStatus] =
    useState(false);
  const [currentPage, setCurrentPage] = useState<number>(1);
  const [sortDirection, setSortDirection] = useState<
    "ascending" | "descending"
  >("descending");
  const [sortColumn, setSortColumn] = useState<"id" | "clientName">("id");

  useEffect(() => {
    if (checkedRows.length === rows.length) {
      setHeaderCheckboxStatus(true);
      setHeaderIndeterminateStatus(false);
    } else if (checkedRows.length > 0) {
      setHeaderCheckboxStatus(false);
      setHeaderIndeterminateStatus(true);
    } else {
      setHeaderCheckboxStatus(false);
      setHeaderIndeterminateStatus(false);
    }
  }, [checkedRows.length, rows.length]);

  const handleRowClick = (id: number) => {
    if (checkedRows.includes(id)) {
      setCheckedRows(checkedRows.filter((rowId) => rowId !== id));
    } else {
      setCheckedRows([...checkedRows, id]);
    }
  };

  const handleHeaderCheckboxClick = () => {
    if (headerCheckboxStatus) {
      setCheckedRows([]);
    } else {
      const rowIds = rows.map((row) => row.id);
      setCheckedRows(rowIds);
    }
  };

  const handleBulkUpdateStatusClick = (status: boolean) => {
    const updatedRows = rows.map((row) => {
      const checked = checkedRows.includes(row.id);
      return { ...row, status: checked ? status : row.status };
    });
    setRows(updatedRows);
  };

  const handlePageChange = (page: number): void => {
    setCurrentPage(page);
  };

  const handleSort = (column: "id" | "clientName") => {
    if (column === sortColumn) {
      setSortDirection(
        sortDirection === "ascending" ? "descending" : "ascending"
      );
    } else {
      setSortColumn(column);
      setSortDirection("ascending");
    }
  };

  const sortCompareFunction = (rowA: RowProps, rowB: RowProps) => {
    if (sortColumn === "id") {
      return sortDirection === "ascending"
        ? rowA.id - rowB.id
        : rowB.id - rowA.id;
    }
    if (sortColumn === "clientName") {
      return sortDirection === "ascending"
        ? rowA.clientName.localeCompare(rowB.clientName)
        : rowB.clientName.localeCompare(rowA.clientName);
    }
    return 0;
  };

  const getDisplayedRows = (): RowProps[] => {
    const sortedRows = rows.slice().sort(sortCompareFunction);
    const startIndex = (currentPage - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    return sortedRows.slice(startIndex, endIndex);
  };

  const displayedRows = getDisplayedRows();
  const totalRows = rows.length;
  const firstRow = (currentPage - 1) * pageSize + 1;
  const lastRow = Math.min(currentPage * pageSize, totalRows);

  const tableHeader = (
    <DataTable.Header
      checkbox={{
        name: "check-all-rows",
        checked: headerCheckboxStatus,
        onChange: handleHeaderCheckboxClick,
        indeterminate: headerIndeterminateStatus,
      }}
    >
      <DataTable.Cell width="120px">
        <Box display="flex" gap="2" alignItems="center">
          Order no.
          <IconButton
            source={
              sortDirection === "ascending" ? (
                <ChevronUpIcon size={10} />
              ) : (
                <ChevronDownIcon size={10} />
              )
            }
            size="1rem"
            onClick={() => handleSort("id")}
          />
        </Box>
      </DataTable.Cell>
      <DataTable.Cell width="auto">Client name</DataTable.Cell>
      <DataTable.Cell width="120px">Total</DataTable.Cell>
      <DataTable.Cell width="120px">Qty. of products</DataTable.Cell>
      <DataTable.Cell width="120px">Order status</DataTable.Cell>
    </DataTable.Header>
  );

  const tableFooter = (
    <DataTable.Footer
      itemCount={`Showing ${firstRow}-${lastRow} orders of ${totalRows}`}
      pagination={{
        pageCount: Math.ceil(totalRows / pageSize),
        activePage: currentPage,
        onPageChange: handlePageChange,
      }}
    />
  );

  const hasBulkActions = checkedRows.length > 0 && (
    <DataTable.BulkActions
      checkbox={{
        name: "check-all",
        checked: headerCheckboxStatus,
        onChange: handleHeaderCheckboxClick,
        indeterminate: headerIndeterminateStatus,
      }}
      label={`${checkedRows.length} selected`}
      action={
        <Box display="flex" gap="1">
          <Chip
            onClick={() => handleBulkUpdateStatusClick(true)}
            text="Fulfill orders"
          />
          <Chip
            onClick={() => handleBulkUpdateStatusClick(false)}
            text="Unfulfill orders"
          />
        </Box>
      }
    />
  );

  return (
    <DataTable
      header={tableHeader}
      footer={tableFooter}
      bulkActions={hasBulkActions}
    >
      {displayedRows.map((row) => {
        const { id, status } = row;
        const statusIcon = status ? (
          <CheckCircleIcon />
        ) : (
          <ExclamationTriangleIcon />
        );
        const statusAppearance = status ? "success" : "warning";
        const statusMsg = status ? "Fulfilled" : "Pending";

        return (
          <DataTable.Row
            key={id}
            backgroundColor={
              checkedRows.includes(id)
                ? {
                    rest: "primary-surface",
                    hover: "primary-surfaceHighlight",
                  }
                : {
                    rest: "neutral-background",
                    hover: "neutral-surface",
                  }
            }
            checkbox={{
              name: `check-${id}`,
              checked: checkedRows.includes(id),
              onChange: () => handleRowClick(id),
            }}
          >
            <DataTable.Cell>#{row.id}</DataTable.Cell>
            <DataTable.Cell>{row.clientName}</DataTable.Cell>
            <DataTable.Cell>{row.total}</DataTable.Cell>
            <DataTable.Cell>{row.qty}</DataTable.Cell>
            <DataTable.Cell>
              <Tag appearance={statusAppearance}>
                {statusIcon}
                {statusMsg}
              </Tag>
            </DataTable.Cell>
          </DataTable.Row>
        );
      })}
    </DataTable>
  );
};

export default Example;

Instalá el componente via terminal.

npm install @nimbus-ds/data-table

Additional props are passed to the <DataTable> element. See div docs for a list of props accepted by the <DataTable> element.