react-chakra-table
Version:
a simple, sortable, filterable, react table grid - styled with chakra-ui.
160 lines (159 loc) • 8.81 kB
JavaScript
import { flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table";
import React, { useState } from "react";
import { Button, Grid, GridItem, HStack, Icon, IconButton, Input, Table as ChakraTable, TableContainer, Tbody, Td, Text, Th, Thead, Tr, Box, } from "@chakra-ui/react";
import { ArrowBackIcon, ArrowDownIcon, ArrowForwardIcon, ArrowUpIcon, DownloadIcon, } from "@chakra-ui/icons";
import { downloadExcel } from "react-export-table-to-excel";
import dayjs from "dayjs";
import LocalizedFormat from "dayjs/plugin/localizedFormat";
import { numberFormatter } from "./utils/numbers";
import { getDate, isDate } from "./utils/date";
/**
* Displays a table-grid, based on ChakraUI, with filtering and sorting options.
* For further details visit: https://github.com/kevinschmidt777/react-chakra-table/
* @returns
*/
const ReactChakraTable = (props) => {
const [sorting, setSorting] = useState(props.defaultSorting ? props.defaultSorting : []);
const [columnFilters, setColumnFilters] = useState(props.defaultFiltering ? props.defaultFiltering : []);
const [page, setPage] = useState(1);
// Prepaire final columns for table.
const columns = [];
for (const column of props.columns) {
columns.push({
...column,
filterFn: "ownFilter",
enableColumnFilter: true,
});
}
/**
* Using a own filter function, to be able to reformat date columns before filtering.
*/
const filterFunction = (row, columnId, value) => {
let string = row.getValue(columnId);
// If date, reformat!
if (isDate(string))
string = getDate(string, true, props.language);
return string.toLowerCase().includes(value.toLowerCase());
};
const table = useReactTable({
data: props.data,
columns: columns,
filterFns: {
ownFilter: filterFunction,
},
state: {
sorting,
columnFilters: columnFilters,
},
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
const pagesCounter = props.itemsPerPage
? Math.ceil(table.getRowModel().rows.length / props.itemsPerPage)
: 1;
const dataItem = (row) => (React.createElement(Tr, { key: row.id, cursor: props.onRowClick ? "pointer" : "inherit", onClick: () => (props.onRowClick ? props.onRowClick(row.original) : null) }, row.getVisibleCells().map((cell) => {
return (React.createElement(Td, { key: cell.id }, flexRender(cell.column.columnDef.cell, cell.getContext())));
})));
const filterInput = (column) => (React.createElement(Input, { defaultValue: column.getFilterValue(), onChange: (value) => column.setFilterValue(value.target.value), placeholder: props.filterByText ? props.filterByText : "Filter by...", size: "sm", mt: "1", ml: "-2" }));
const tableRenderer = (React.createElement(TableContainer, { ...props.tableContainerProps },
React.createElement(ChakraTable, null,
React.createElement(Thead, null, table.getHeaderGroups().map((headerGroup) => (React.createElement(Tr, { key: headerGroup.id }, headerGroup.headers.map((header) => {
return (React.createElement(Th, { key: header.id, verticalAlign: "top" },
header.isPlaceholder ? null : (React.createElement(Text, { cursor: "pointer", onClick: header.column.getToggleSortingHandler(), display: "flex", alignItems: "center", ...{
className: header.column.getCanSort()
? "cursor-pointer select-none"
: "",
} },
flexRender(header.column.columnDef.header, header.getContext()),
{
asc: (React.createElement(Icon, { as: props.sortIconUp
? props.sortIconUp
: ArrowUpIcon, color: props.sortIconColor, w: "4", h: "4", ml: "1" })),
desc: (React.createElement(Icon, { as: props.sortIconDown
? props.sortIconDown
: ArrowDownIcon, color: props.sortIconColor, w: "4", h: "4", ml: "1" })),
}[header.column.getIsSorted()] ?? null)),
header.column.getCanFilter()
? filterInput(header.column)
: null));
}))))),
React.createElement(Tbody, null, props.itemsPerPage
? table
.getRowModel()
.rows.slice(page === 1 ? 0 : (page - 1) * props.itemsPerPage, page * props.itemsPerPage)
.map((row) => dataItem(row))
: table.getRowModel().rows.map((row) => dataItem(row))))));
const paginationPageButtons = () => {
const pagesArray = [];
for (let i = 1; i <= pagesCounter; i++) {
// @ts-ignore
pagesArray.push(i);
}
return pagesArray.map((item) => (React.createElement(Button, { key: item, onClick: () => setPage(item), ...props.paginationPageButtonProps, opacity: page === item ? 1 : 0.5, _hover: {
opacity: 1,
} }, item)));
};
const paginationRenderer = (React.createElement(Box, { maxWidth: "full", overflowX: "auto", mt: "5" },
React.createElement(HStack, { spacing: "2" },
React.createElement(IconButton, { "aria-label": "<", icon: React.createElement(Icon, { as: props.paginationPrevIcon
? props.paginationPrevIcon
: ArrowBackIcon }), isDisabled: page === 1, onClick: () => setPage((prevPage) => prevPage - 1), ...props.paginationNextPrevButtonProps }),
paginationPageButtons(),
React.createElement(IconButton, { "aria-label": ">", icon: React.createElement(Icon, { as: props.paginationNextIcon
? props.paginationNextIcon
: ArrowForwardIcon }), isDisabled: page === pagesCounter, onClick: () => setPage((prevPage) => prevPage + 1), ...props.paginationNextPrevButtonProps }))));
/**
* Prepairs data for excel export and downloads file.
*/
const downloadExcelHandler = () => {
const lang = props.language ? props.language : "en";
dayjs.extend(LocalizedFormat);
dayjs.locale(lang);
let headersObject = {};
const headers = [];
for (const item of table.getHeaderGroups()[0].headers) {
headersObject = {
...headersObject,
[item.column.columnDef.id]: item.column.columnDef.header,
};
headers.push(item.column.columnDef.header);
}
const body = [];
for (const item of table.getRowModel().rows) {
const headerKeys = Object.keys(headersObject);
let prepairedNewBodyItem = {};
for (const head of headerKeys) {
prepairedNewBodyItem = {
...prepairedNewBodyItem,
[headersObject[head]]: props.dateColumns.includes(head)
? getDate(item.original[head], true, props.language)
: item.original[head],
};
}
// @ts-ignore
body.push(prepairedNewBodyItem);
}
return downloadExcel({
fileName: "export-" + getDate(undefined, false, props.language) + ".xls",
sheet: "export-" + getDate(undefined, false, props.language),
tablePayload: {
header: headers,
body: body,
},
});
};
return (React.createElement(React.Fragment, null,
React.createElement(Grid, { templateColumns: { base: "1fr", md: "1fr auto" }, gap: "2" },
React.createElement(GridItem, null, props.amountText && (React.createElement(Text, { fontSize: "md", color: "gray.200" },
props.amountText,
":",
" ",
numberFormatter(table.getRowModel().rows.length)))),
React.createElement(GridItem, null, props.exportText && (React.createElement(Button, { mb: "2", mr: "2", onClick: () => downloadExcelHandler(), leftIcon: React.createElement(Icon, { as: props.exportIcon ? props.exportIcon : DownloadIcon, w: "5", h: "5" }), ...props.exportButtonProps }, props.exportText)))),
tableRenderer,
props.itemsPerPage && pagesCounter > 1 && paginationRenderer));
};
export default ReactChakraTable;