import React, { useMemo, useRef } from "react"; import { pick, reduce } from "lodash"; import { useTable, usePagination, useBlockLayout, useResizeColumns, useRowSelect, Row, } from "react-table"; import { TableWrapper, TableHeaderWrapper, TableHeaderInnerWrapper, } from "./TableStyledWrappers"; import TableHeader from "./TableHeader"; import { Classes } from "@blueprintjs/core"; import { ReactTableColumnProps, ReactTableFilter, TABLE_SIZES, CompactMode, CompactModeTypes, } from "./Constants"; import { Colors } from "constants/Colors"; import ScrollIndicator from "components/ads/ScrollIndicator"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { Scrollbars } from "react-custom-scrollbars"; import { renderEmptyRows } from "./cellComponents/EmptyCell"; import { renderBodyCheckBoxCell, renderHeaderCheckBoxCell, } from "./cellComponents/CheckboxCell"; import { HeaderCell } from "./cellComponents/HeaderCell"; import { EditableCell } from "../constants"; interface TableProps { width: number; height: number; pageSize: number; widgetId: string; widgetName: string; searchKey: string; isLoading: boolean; columnWidthMap?: { [key: string]: number }; columns: ReactTableColumnProps[]; data: Array>; totalRecordsCount?: number; editMode: boolean; editableCell: EditableCell; sortTableColumn: (columnIndex: number, asc: boolean) => void; handleResizeColumn: (columnWidthMap: { [key: string]: number }) => void; selectTableRow: (row: { original: Record; index: number; }) => void; pageNo: number; updatePageNo: (pageNo: number, event?: EventType) => void; multiRowSelection?: boolean; isSortable?: boolean; nextPageClick: () => void; prevPageClick: () => void; serverSidePaginationEnabled: boolean; selectedRowIndex: number; selectedRowIndices: number[]; disableDrag: () => void; enableDrag: () => void; toggleAllRowSelect: ( isSelect: boolean, pageData: Row>[], ) => void; triggerRowSelection: boolean; searchTableData: (searchKey: any) => void; filters?: ReactTableFilter[]; applyFilter: (filters: ReactTableFilter[]) => void; compactMode?: CompactMode; isVisibleDownload?: boolean; isVisibleFilters?: boolean; isVisiblePagination?: boolean; isVisibleSearch?: boolean; delimiter: string; accentColor: string; borderRadius: string; boxShadow?: string; onBulkEditDiscard: () => void; onBulkEditSave: () => void; } const defaultColumn = { minWidth: 30, width: 150, }; function ScrollbarVerticalThumb(props: any) { return
; } function ScrollbarHorizontalThumb(props: any) { return
; } export function Table(props: TableProps) { const isResizingColumn = React.useRef(false); const handleResizeColumn = (columnWidths: Record) => { const columnWidthMap = { ...props.columnWidthMap, ...columnWidths, }; for (const i in columnWidthMap) { if (columnWidthMap[i] < 60) { columnWidthMap[i] = 60; } else if (columnWidthMap[i] === undefined) { const columnCounts = props.columns.filter((column) => !column.isHidden) .length; columnWidthMap[i] = props.width / columnCounts; } } props.handleResizeColumn(columnWidthMap); }; const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]); const columnString = JSON.stringify( pick(props, ["columns", "compactMode", "columnWidthMap"]), ); const columns = React.useMemo(() => props.columns, [columnString]); const tableHeadercolumns = React.useMemo( () => props.columns.filter((column: ReactTableColumnProps) => { return column.alias !== "actions"; }), [columnString], ); const pageCount = props.serverSidePaginationEnabled && props.totalRecordsCount ? Math.ceil(props.totalRecordsCount / props.pageSize) : Math.ceil(props.data.length / props.pageSize); const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0; const { getTableBodyProps, getTableProps, headerGroups, page, pageOptions, prepareRow, state, } = useTable( { columns: columns, data, defaultColumn, initialState: { pageIndex: currentPageIndex, pageSize: props.pageSize, }, manualPagination: true, pageCount, }, useBlockLayout, useResizeColumns, usePagination, useRowSelect, ); //Set isResizingColumn as true when column is resizing using table state if (state.columnResizing.isResizingColumn) { isResizingColumn.current = true; } else { // We are updating column size since the drag is complete when we are changing value of isResizing from true to false if (isResizingColumn.current) { //update isResizingColumn in next event loop so that dragEnd event does not trigger click event. setTimeout(function() { isResizingColumn.current = false; handleResizeColumn(state.columnResizing.columnWidths); }, 0); } } let startIndex = currentPageIndex * props.pageSize; let endIndex = startIndex + props.pageSize; if (props.serverSidePaginationEnabled) { startIndex = 0; endIndex = props.data.length; } const subPage = page.slice(startIndex, endIndex); const selectedRowIndex = props.selectedRowIndex; const selectedRowIndices = props.selectedRowIndices || []; const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT]; const tableWrapperRef = useRef(null); const tableBodyRef = useRef(null); const tableHeaderWrapperRef = React.createRef(); const rowSelectionState = React.useMemo(() => { // return : 0; no row selected | 1; all row selected | 2: some rows selected if (!props.multiRowSelection) return null; const selectedRowCount = reduce( page, (count, row) => { return selectedRowIndices.includes(row.index) ? count + 1 : count; }, 0, ); const result = selectedRowCount === 0 ? 0 : selectedRowCount === page.length ? 1 : 2; return result; }, [selectedRowIndices, page]); const handleAllRowSelectClick = ( e: React.MouseEvent, ) => { // if all / some rows are selected we remove selection on click // else select all rows props.toggleAllRowSelect(!Boolean(rowSelectionState), page); // loop over subPage rows and toggleRowSelected if required e.stopPropagation(); }; const isHeaderVisible = props.isVisibleSearch || props.isVisibleFilters || props.isVisibleDownload || props.isVisiblePagination; const style = useMemo( () => ({ width: props.width, height: 38, }), [props.width], ); return ( {isHeaderVisible && ( )}
{headerGroups.map((headerGroup: any, index: number) => { const headerRowProps = { ...headerGroup.getHeaderGroupProps(), style: { display: "flex" }, }; return (
{props.multiRowSelection && renderHeaderCheckBoxCell( handleAllRowSelectClick, rowSelectionState, props.accentColor, props.borderRadius, )} {headerGroup.headers.map( (column: any, columnIndex: number) => { return ( ); }, )}
); })} {headerGroups.length === 0 && renderEmptyRows( 1, props.columns, props.width, subPage, prepareRow, props.multiRowSelection, props.accentColor, props.borderRadius, )}
subPage.length ? "no-scroll" : "" }`} ref={tableBodyRef} > {subPage.map((row, rowIndex) => { prepareRow(row); const rowProps = { ...row.getRowProps(), style: { display: "flex" }, }; const isRowSelected = props.multiRowSelection ? selectedRowIndices.includes(row.index) : row.index === selectedRowIndex; return (
{ row.toggleRowSelected(); props.selectTableRow(row); e.stopPropagation(); }} > {props.multiRowSelection && renderBodyCheckBoxCell( isRowSelected, props.accentColor, props.borderRadius, )} {row.cells.map((cell, cellIndex) => { return (
{cell.render("Cell")}
); })}
); })} {props.pageSize > subPage.length && renderEmptyRows( props.pageSize - subPage.length, props.columns, props.width, subPage, prepareRow, props.multiRowSelection, props.accentColor, props.borderRadius, )}
); } export default Table;