import React, { Suspense } from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/ActionConstants"; import ReactTableComponent, { ReactTableColumnProps, ColumnTypes, } from "components/designSystems/appsmith/ReactTableComponent"; import { getAllTableColumnKeys, renderCell, renderActions, reorderColumns, } from "components/designSystems/appsmith/TableUtilities"; import { TABLE_SIZES } from "components/designSystems/appsmith/Table"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { RenderModes } from "constants/WidgetConstants"; import { WidgetPropertyValidationType, BASE_WIDGET_VALIDATION, } from "utils/ValidationFactory"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; import { TriggerPropertiesMap } from "utils/WidgetFactory"; import Skeleton from "components/utils/Skeleton"; import moment from "moment"; class TableWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { ...BASE_WIDGET_VALIDATION, tableData: VALIDATION_TYPES.TABLE_DATA, nextPageKey: VALIDATION_TYPES.TEXT, prevPageKey: VALIDATION_TYPES.TEXT, label: VALIDATION_TYPES.TEXT, selectedRowIndex: VALIDATION_TYPES.NUMBER, searchText: VALIDATION_TYPES.TEXT, filteredTableData: VALIDATION_TYPES.TABLE_DATA, defaultSearchText: VALIDATION_TYPES.TEXT, }; } static getDerivedPropertiesMap() { return { filteredTableData: "{{!this.onSearchTextChanged ? this.tableData.filter((item) => Object.values(item).join(', ').toUpperCase().includes(this.searchText ? this.searchText.toUpperCase() : '')) : this.tableData}}", selectedRow: "{{this.filteredTableData[this.selectedRowIndex]}}", }; } static getMetaPropertiesMap(): Record { return { pageNo: 1, pageSize: undefined, selectedRowIndex: -1, searchText: undefined, // The following meta property is used for rendering the table. filteredTableData: [], }; } static getDefaultPropertiesMap(): Record { return { searchText: "defaultSearchText", }; } static getTriggerPropertyMap(): TriggerPropertiesMap { return { onRowSelected: true, onPageChange: true, onSearchTextChanged: true, }; } getTableColumns = (tableData: object[]) => { let columns: ReactTableColumnProps[] = []; const hiddenColumns: ReactTableColumnProps[] = []; const { columnNameMap, columnSizeMap, columnTypeMap, widgetId, columnActions, } = this.props; if (tableData.length) { const columnKeys: string[] = getAllTableColumnKeys(tableData); for (let index = 0; index < columnKeys.length; index++) { const i = columnKeys[index]; const columnName: string = columnNameMap && columnNameMap[i] ? columnNameMap[i] : i; const columnType: { type: string; format?: string } = columnTypeMap && columnTypeMap[i] ? columnTypeMap[i] : { type: ColumnTypes.TEXT }; const columnSize: number = columnSizeMap && columnSizeMap[i] ? columnSizeMap[i] : 150; const isHidden = !!this.props.hiddenColumns && this.props.hiddenColumns.includes(i); const columnData = { Header: columnName, accessor: i, width: columnSize, minWidth: 60, draggable: true, isHidden: false, metaProperties: { isHidden: isHidden, type: columnType.type, format: columnType.format, }, Cell: (props: any) => { return renderCell(props.cell.value, columnType.type, isHidden); }, }; if (isHidden) { columnData.isHidden = true; hiddenColumns.push(columnData); } else { columns.push(columnData); } } columns = reorderColumns(columns, this.props.columnOrder || []); if (columnActions?.length) { columns.push({ Header: columnNameMap && columnNameMap["actions"] ? columnNameMap["actions"] : "Actions", accessor: "actions", width: 150, minWidth: 60, draggable: true, Cell: (props: any) => { return renderActions({ isSelected: props.row.isSelected, columnActions: columnActions, onCommandClick: this.onCommandClick, }); }, }); } if ( hiddenColumns.length && this.props.renderMode === RenderModes.CANVAS ) { columns = columns.concat(hiddenColumns); } } return columns; }; transformData = (tableData: object[], columns: ReactTableColumnProps[]) => { const updatedTableData = []; for (let row = 0; row < tableData.length; row++) { const data: { [key: string]: any } = tableData[row]; const tableRow: { [key: string]: any } = {}; for (let colIndex = 0; colIndex < columns.length; colIndex++) { const column = columns[colIndex]; const { accessor } = column; const value = data[accessor]; if (column.metaProperties) { const type = column.metaProperties.type; const format = column.metaProperties.format; switch (type) { case ColumnTypes.CURRENCY: if (!isNaN(value)) { tableRow[accessor] = `${format}${value ? value : ""}`; } else { tableRow[accessor] = "Invalid Value"; } break; case ColumnTypes.DATE: let isValidDate = true; if (isNaN(value)) { const dateTime = Date.parse(value); if (isNaN(dateTime)) { isValidDate = false; } } if (isValidDate) { tableRow[accessor] = moment(value).format(format); } else { tableRow[accessor] = "Invalid Value"; } break; case ColumnTypes.TIME: let isValidTime = true; if (isNaN(value)) { const time = Date.parse(value); if (isNaN(time)) { isValidTime = false; } } if (isValidTime) { tableRow[accessor] = moment(value).format("HH:mm"); } else { tableRow[accessor] = "Invalid Value"; } break; default: tableRow[accessor] = value; break; } } } updatedTableData.push(tableRow); } return updatedTableData; }; getPageView() { const { tableData, hiddenColumns, filteredTableData } = this.props; const tableColumns = this.getTableColumns(tableData); // Use the filtered data to render the table. const transformedData = this.transformData(filteredTableData, tableColumns); const serverSidePaginationEnabled = (this.props .serverSidePaginationEnabled && this.props.serverSidePaginationEnabled) as boolean; let pageNo = this.props.pageNo; if (pageNo === undefined) { pageNo = 1; super.updateWidgetMetaProperty("pageNo", pageNo); } const { componentWidth, componentHeight } = this.getComponentDimensions(); const pageSize = Math.floor( (componentHeight - TABLE_SIZES.TABLE_HEADER_HEIGHT - TABLE_SIZES.COLUMN_HEADER_HEIGHT) / TABLE_SIZES.ROW_HEIGHT, ); if (pageSize !== this.props.pageSize) { super.updateWidgetMetaProperty("pageSize", pageSize); } // /* return ( }> { super.updateWidgetMetaProperty("pageNo", pageNo); }} updateHiddenColumns={(hiddenColumns?: string[]) => { super.updateWidgetProperty("hiddenColumns", hiddenColumns); }} updateColumnType={(columnTypeMap: { [key: string]: { type: string; format: string }; }) => { super.updateWidgetProperty("columnTypeMap", columnTypeMap); }} updateColumnName={(columnNameMap: { [key: string]: string }) => { super.updateWidgetProperty("columnNameMap", columnNameMap); }} handleResizeColumn={(columnSizeMap: { [key: string]: number }) => { super.updateWidgetProperty("columnSizeMap", columnSizeMap); }} handleReorderColumn={(columnOrder: string[]) => { super.updateWidgetProperty("columnOrder", columnOrder); }} columnSizeMap={this.props.columnSizeMap} disableDrag={(disable: boolean) => { this.disableDrag(disable); }} searchTableData={this.handleSearchTable} /> ); } handleSearchTable = (searchKey: any) => { const { onSearchTextChanged } = this.props; this.resetSelectedRowIndex(); super.updateWidgetMetaProperty("searchText", searchKey); if (onSearchTextChanged) { super.executeAction({ dynamicString: onSearchTextChanged, event: { type: EventType.ON_SEARCH, }, }); } }; updateHiddenColumns = (hiddenColumns?: string[]) => { super.updateWidgetProperty("hiddenColumns", hiddenColumns); }; onCommandClick = (action: string, onComplete: () => void) => { super.executeAction({ dynamicString: action, event: { type: EventType.ON_CLICK, callback: onComplete, }, }); }; handleRowClick = (rowData: object, index: number) => { const { onRowSelected } = this.props; super.updateWidgetMetaProperty("selectedRowIndex", index); if (onRowSelected) { super.executeAction({ dynamicString: onRowSelected, event: { type: EventType.ON_ROW_SELECTED, }, }); } }; handleNextPageClick = () => { let pageNo = this.props.pageNo || 1; pageNo = pageNo + 1; super.updateWidgetMetaProperty("pageNo", pageNo); if (this.props.onPageChange) { this.resetSelectedRowIndex(); super.executeAction({ dynamicString: this.props.onPageChange, event: { type: EventType.ON_NEXT_PAGE, }, }); } }; resetSelectedRowIndex = () => { super.updateWidgetMetaProperty("selectedRowIndex", -1); }; handlePrevPageClick = () => { let pageNo = this.props.pageNo || 1; pageNo = pageNo - 1; if (pageNo >= 1) { super.updateWidgetMetaProperty("pageNo", pageNo); if (this.props.onPageChange) { this.resetSelectedRowIndex(); super.executeAction({ dynamicString: this.props.onPageChange, event: { type: EventType.ON_PREV_PAGE, }, }); } } }; getWidgetType(): WidgetType { return "TABLE_WIDGET"; } } export interface TableWidgetProps extends WidgetProps { nextPageKey?: string; prevPageKey?: string; label: string; searchText: string; defaultSearchText: string; tableData: object[]; onPageChange?: string; pageSize: number; onRowSelected?: string; onSearchTextChanged: string; selectedRowIndex?: number; columnActions?: ColumnAction[]; serverSidePaginationEnabled?: boolean; hiddenColumns?: string[]; columnOrder?: string[]; columnNameMap?: { [key: string]: string }; columnTypeMap?: { [key: string]: { type: string; format: string } }; columnSizeMap?: { [key: string]: number }; } export default TableWidget;