import React, { useEffect, useMemo, useRef } from "react"; import { pick, reduce } from "lodash"; import { useTable, usePagination, useBlockLayout, useResizeColumns, useRowSelect, Row as ReactTableRowType, } from "react-table"; import { TableWrapper, TableHeaderWrapper, TableHeaderInnerWrapper, } from "./TableStyledWrappers"; import TableHeader from "./header"; import { Classes } from "@blueprintjs/core"; import { ReactTableColumnProps, ReactTableFilter, TABLE_SIZES, CompactMode, CompactModeTypes, AddNewRowActions, } from "./Constants"; import { Colors } from "constants/Colors"; import { ScrollIndicator } from "design-system-old"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { Scrollbars } from "react-custom-scrollbars"; import { renderEmptyRows } from "./cellComponents/EmptyCell"; import { renderHeaderCheckBoxCell } from "./cellComponents/SelectionCheckboxCell"; import { HeaderCell } from "./cellComponents/HeaderCell"; import { EditableCell, TableVariant } from "../constants"; import { TableBody } from "./TableBody"; 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: ReactTableRowType>[], ) => 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; borderWidth?: number; borderColor?: string; onBulkEditDiscard: () => void; onBulkEditSave: () => void; variant?: TableVariant; primaryColumnId?: string; isAddRowInProgress: boolean; allowAddNewRow: boolean; onAddNewRow: () => void; onAddNewRowAction: ( type: AddNewRowActions, onActionComplete: () => void, ) => void; disabledAddNewRowSave: boolean; } const defaultColumn = { minWidth: 30, width: 150, }; function ScrollbarVerticalThumb(props: any) { return
; } function ScrollbarHorizontalThumb(props: any) { return
; } function ScrollbarHorizontalTrack(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], ); /* For serverSidePaginationEnabled we are taking props.data.length as the page size. As props.pageSize is being set by the visible number of rows in the table (without scrolling), it will not give the correct count of records in the current page when query limit is set higher/lower than the visible number of rows in the table */ const pageCount = props.serverSidePaginationEnabled && props.totalRecordsCount && props.data.length ? Math.ceil(props.totalRecordsCount / props.data.length) : 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 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 || props.allowAddNewRow; const style = useMemo( () => ({ width: props.width, height: 38, }), [props.width], ); const shouldUseVirtual = props.serverSidePaginationEnabled && !props.columns.some( (column) => !!column.columnProperties.allowCellWrapping, ); useEffect(() => { if (props.isAddRowInProgress && tableBodyRef) { tableBodyRef.current?.scrollTo({ top: 0 }); } }, [props.isAddRowInProgress]); 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, props.multiRowSelection, props.accentColor, props.borderRadius, {}, prepareRow, )}
); } export default Table;