UNPKG

react-chakra-table

Version:

a simple, sortable, filterable, react table grid - styled with chakra-ui.

160 lines (159 loc) 8.81 kB
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;