UNPKG

vyas-react-table

Version:

A simple yet highly configurable table component

275 lines (251 loc) 8.66 kB
import { useState, useEffect } from "react"; import "./Table.css"; // utils import { checkInclusion } from "../../utils/filter.utils"; import { downloadData } from "../../utils/download.utils"; /** * * @author tanishqvyas * @param tableName - Name of the table. Defaults to "Data" * @param {Array} data An array of objects, where each object is a row of the table. This is required. * @param {Object} tableHeaders An object with the headers & their configuration. This is required. * @param {Boolean} [allowFilters] Whether to allow filtering or not. Default is true. * @param {Boolean} [showSerialNo] Whether to show serial number or not. Default is false. * @param {String} [borderSpacing] The spacing between the cells. Default is "4px". * @param {Number} [itemsPerPage] The number of items to be shown per page. Default is 10. * @param {Boolean} [allowDownload] Whether to allow downloading or not. Default is false. * @param {String} [filename] The name of the file. Defaults to "data.csv" * @param {String} [nullDataPlaceholder] The placeholder to be shown when the value of a cell is null or undefined. Defaults to "-" * @param {Function} [onRowClick] The function to be called when a row is clicked. * @see {@link https://www.npmjs.com/package/vyas-react-table| vyas-react-table documentation} for more information. * @returns Custom table component */ export const Table = ({ tableName = "Data", data, tableHeaders, allowFilters = true, showSerialNo = false, borderSpacing = "4px", itemsPerPage = 10, allowDownload = false, filename = "data", nullDataPlaceholder = "-", onRowClick = (data) => {}, }) => { // Data States const [filteredData, setFilteredData] = useState(data); // Filter States const [filterState, setFilterState] = useState({}); // Pagination States const [rowsPerPage, setRowsPerPage] = useState(itemsPerPage); const [curPage, setCurPage] = useState(1); const [totalPages, setTotalPages] = useState( Math.ceil(filteredData.length / rowsPerPage) ); const [pageState, setPageState] = useState({ sliceStartIdx: 0, sliceEndIdx: rowsPerPage, }); /* Pagination Functions */ const handleRowsPerPageChange = (e) => { if (e.target.value === "") { setRowsPerPage(e.target.value); setCurPage(1); setPageState({ sliceStartIdx: 0, sliceEndIdx: filteredData.length, }); return; } if (Number.isInteger(parseInt(e.target.value)) && e.target.value > 0) { setRowsPerPage(e.target.value); setCurPage(1); setPageState({ sliceStartIdx: 0, sliceEndIdx: e.target.value, }); setTotalPages(Math.ceil(filteredData.length / e.target.value)); return; } return; }; const handleCurPageChange = (e) => { if (e.target.value === "") { setCurPage(e.target.value); return; } if (Number.isInteger(parseInt(e.target.value)) && e.target.value > 0) { setCurPage(e.target.value); setPageState({ sliceStartIdx: (e.target.value - 1) * rowsPerPage, sliceEndIdx: e.target.value * rowsPerPage, }); return; } return; }; useEffect(() => { filterData(); }, [filterState]); /* Filter Functions */ // Clear all filters const clearFilters = () => { setFilterState({}); }; // Function to handle Change in individual filter values const handleColumnFilterChange = (e, header) => { const newFilterState = { ...filterState }; // In case the filter is cleared // Deleting empty filters if (e.target.value === "") { delete newFilterState[header.key]; setFilterState(newFilterState); return; } setFilterState({ ...newFilterState, [header.key]: e.target.value, }); }; // Function to actually filter data const filterData = () => { const filteredData = data.filter((item) => { // Check for all the filters for (const key in filterState) { if ( !checkInclusion(item[key], filterState[key], tableHeaders[key].type) ) { return false; } } return true; }); // Setting the filtered data setFilteredData(filteredData); }; /* Download functions */ const handleDownload = () => { downloadData(filteredData, tableHeaders, filename); }; /* handle rendering */ const renderData = (data) => data?.toString() ?? nullDataPlaceholder; return ( <section className="container"> <div className="table-header"> <div className="table-name">{tableName}</div> <span>Total {filteredData.length} rows</span> </div> <div className="statsContainer"> <div className="stats-div"> <div> Show <input type="number" min="1" value={rowsPerPage} onChange={handleRowsPerPageChange} className="stats-input" /> rows per page </div> <div> Show <input type="number" min="1" max={totalPages} value={curPage} onChange={handleCurPageChange} className="stats-input" /> of {totalPages} pages </div> </div> <div> {allowDownload && ( <button onClick={handleDownload} className="button"> Download </button> )} <button onClick={clearFilters} className="button"> Clear All Filters </button> </div> </div> <table style={{ borderSpacing }} className="table"> <thead className="thead"> <tr className="headRow"> {showSerialNo && ( <th className="th"> <div className="resizable">Serial No.</div> </th> )} {Object.keys(tableHeaders).map((header, headerIdx) => ( <th key={headerIdx} className="th"> <div className="resizable">{tableHeaders[header].title}</div> </th> ))} </tr> {allowFilters && ( <tr className="filterContainer"> {showSerialNo && <th></th>} {Object.keys(tableHeaders).map((header, headerIdx) => ( <th key={headerIdx}> {tableHeaders[header].options === undefined ? ( <input className="vyasInput" type={tableHeaders[header].type} value={filterState[header] ?? ""} placeholder={`Filter by ${tableHeaders[header].title}...`} onChange={(e) => handleColumnFilterChange(e, tableHeaders[header]) } /> ) : ( <select value={filterState[header] ?? ""} onChange={(e) => handleColumnFilterChange(e, tableHeaders[header]) } className="vyasInput" > <option value="">None Selected</option> {tableHeaders[header].options.map((option, optionIdx) => ( <option key={optionIdx} value={option.value}> {option.label} </option> ))} </select> )} </th> ))} </tr> )} </thead> <tbody className="tbody"> {filteredData .slice(pageState.sliceStartIdx, pageState.sliceEndIdx) .map((row, rowIdx) => ( <tr key={rowIdx} className="tr" onClick={() => onRowClick(row)}> {showSerialNo && ( <td className="td"> {(curPage - 1) * rowsPerPage + rowIdx + 1} </td> )} {Object.keys(tableHeaders).map((header, headerIdx) => ( <td key={headerIdx} className="td"> {Array.isArray(row[tableHeaders[header].key]) ? row[tableHeaders[header].key].map((item, itemIdx) => ( <div key={itemIdx}>{renderData(item)}</div> )) : renderData(row[tableHeaders[header].key])} </td> ))} </tr> ))} </tbody> </table> </section> ); };