Feature/table ui download data (#52)
* Created header for common functionalities in Table Widget * Client side searching added in Table Widget. Action created for server side searching also. * Columns visibility feature initial commit * Column visibility list added in Table Widget * Changed pagination designs in accordance with new layout. This enable user to jump page as well. * Using colors values from constants * Table widget pagination, numeric input page number clamped between 1 and total pages * Adding tool tip to truncated values in table widget. Added AutoToolTipComponent that adds tooltip when text is truncated. * Table data download changes. Added downlaod icon and button to table widget. * Table data download changes * Download table data as CSV implemented * table data download, unused code removed * Code review changes * code review changes Co-authored-by: Arpit Mohan <me@arpitmohan.com>
This commit is contained in:
parent
eabe496fbd
commit
4f47a8ad3f
5
app/client/src/assets/icons/control/download-table.svg
Executable file
5
app/client/src/assets/icons/control/download-table.svg
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<path d="M16.125 8.36658C15.5583 5.49158 13.0333 3.33325 10 3.33325C7.59167 3.33325 5.5 4.69992 4.45833 6.69992C1.95 6.96658 0 9.09158 0 11.6666C0 14.4249 2.24167 16.6666 5 16.6666H15.8333C18.1333 16.6666 20 14.7999 20 12.4999C20 10.2999 18.2917 8.51658 16.125 8.36658ZM14.1667 10.8333L10 14.9999L5.83333 10.8333H8.33333V7.49992H11.6667V10.8333H14.1667Z" fill="#2E3D49"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 484 B |
|
|
@ -3,7 +3,25 @@ import { ColumnAction } from "components/propertyControls/ColumnActionSelectorCo
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
import { RenderMode, RenderModes } from "constants/WidgetConstants";
|
import { RenderMode, RenderModes } from "constants/WidgetConstants";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { getMenuOptions, renderActions, renderCell } from "./TableUtilities";
|
import {
|
||||||
|
getMenuOptions,
|
||||||
|
getAllTableColumnKeys,
|
||||||
|
} from "components/designSystems/appsmith/TableUtilities";
|
||||||
|
|
||||||
|
export enum ColumnTypes {
|
||||||
|
CURRENCY = "currency",
|
||||||
|
TIME = "time",
|
||||||
|
DATE = "date",
|
||||||
|
VIDEO = "video",
|
||||||
|
IMAGE = "image",
|
||||||
|
TEXT = "text",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableColumnMetaProps {
|
||||||
|
isHidden: boolean;
|
||||||
|
format?: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReactTableColumnProps {
|
export interface ReactTableColumnProps {
|
||||||
Header: string;
|
Header: string;
|
||||||
|
|
@ -12,6 +30,7 @@ export interface ReactTableColumnProps {
|
||||||
minWidth: number;
|
minWidth: number;
|
||||||
draggable: boolean;
|
draggable: boolean;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
metaProperties?: TableColumnMetaProps;
|
||||||
Cell: (props: any) => JSX.Element;
|
Cell: (props: any) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +55,7 @@ export interface ColumnMenuSubOptionProps {
|
||||||
|
|
||||||
interface ReactTableComponentProps {
|
interface ReactTableComponentProps {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
|
widgetName: string;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
|
|
@ -72,140 +92,12 @@ interface ReactTableComponentProps {
|
||||||
handleResizeColumn: Function;
|
handleResizeColumn: Function;
|
||||||
handleReorderColumn: Function;
|
handleReorderColumn: Function;
|
||||||
searchTableData: (searchKey: any) => void;
|
searchTableData: (searchKey: any) => void;
|
||||||
|
columns: ReactTableColumnProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactTableComponent = (props: ReactTableComponentProps) => {
|
const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
let dragged = -1;
|
let dragged = -1;
|
||||||
const getAllTableColumnKeys = () => {
|
|
||||||
const tableData: object[] = props.tableData;
|
|
||||||
const columnKeys: string[] = [];
|
|
||||||
for (let i = 0, tableRowCount = tableData.length; i < tableRowCount; i++) {
|
|
||||||
const row = tableData[i];
|
|
||||||
for (const key in row) {
|
|
||||||
if (!columnKeys.includes(key)) {
|
|
||||||
columnKeys.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return columnKeys;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reorderColumns = (columns: ReactTableColumnProps[]) => {
|
|
||||||
const columnOrder = props.columnOrder || [];
|
|
||||||
const reorderedColumns = [];
|
|
||||||
const reorderedFlagMap: { [key: string]: boolean } = {};
|
|
||||||
for (let index = 0; index < columns.length; index++) {
|
|
||||||
const accessor = columnOrder[index];
|
|
||||||
if (accessor) {
|
|
||||||
const column = columns.filter((col: ReactTableColumnProps) => {
|
|
||||||
return col.accessor === accessor;
|
|
||||||
});
|
|
||||||
if (column.length && !reorderedFlagMap[column[0].accessor]) {
|
|
||||||
reorderedColumns.push(column[0]);
|
|
||||||
reorderedFlagMap[column[0].accessor] = true;
|
|
||||||
} else if (!reorderedFlagMap[columns[index].accessor]) {
|
|
||||||
reorderedColumns.push(columns[index]);
|
|
||||||
reorderedFlagMap[columns[index].accessor] = true;
|
|
||||||
}
|
|
||||||
} else if (!reorderedFlagMap[columns[index].accessor]) {
|
|
||||||
reorderedColumns.push(columns[index]);
|
|
||||||
reorderedFlagMap[columns[index].accessor] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (reorderedColumns.length < columns.length) {
|
|
||||||
for (let index = 0; index < columns.length; index++) {
|
|
||||||
if (!reorderedFlagMap[columns[index].accessor]) {
|
|
||||||
reorderedColumns.push(columns[index]);
|
|
||||||
reorderedFlagMap[columns[index].accessor] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reorderedColumns;
|
|
||||||
};
|
|
||||||
const getTableColumns = () => {
|
|
||||||
const tableData: object[] = props.tableData;
|
|
||||||
let columns: ReactTableColumnProps[] = [];
|
|
||||||
const hiddenColumns: ReactTableColumnProps[] = [];
|
|
||||||
if (tableData.length) {
|
|
||||||
const columnKeys: string[] = getAllTableColumnKeys();
|
|
||||||
for (let index = 0; index < columnKeys.length; index++) {
|
|
||||||
const i = columnKeys[index];
|
|
||||||
const columnName: string =
|
|
||||||
props.columnNameMap && props.columnNameMap[i]
|
|
||||||
? props.columnNameMap[i]
|
|
||||||
: i;
|
|
||||||
const columnType: { type: string; format?: string } =
|
|
||||||
props.columnTypeMap && props.columnTypeMap[i]
|
|
||||||
? props.columnTypeMap[i]
|
|
||||||
: { type: "text" };
|
|
||||||
const columnSize: number =
|
|
||||||
props.columnSizeMap && props.columnSizeMap[i]
|
|
||||||
? props.columnSizeMap[i]
|
|
||||||
: 150;
|
|
||||||
const isHidden =
|
|
||||||
!!props.hiddenColumns && props.hiddenColumns.includes(i);
|
|
||||||
const columnData = {
|
|
||||||
Header: columnName,
|
|
||||||
accessor: i,
|
|
||||||
width: columnSize,
|
|
||||||
minWidth: 60,
|
|
||||||
draggable: true,
|
|
||||||
isHidden: false,
|
|
||||||
Cell: (props: any) => {
|
|
||||||
return renderCell(
|
|
||||||
props.cell.value,
|
|
||||||
props.cell.row.index,
|
|
||||||
columnType.type,
|
|
||||||
isHidden,
|
|
||||||
props.widgetId,
|
|
||||||
columnType.format,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (isHidden) {
|
|
||||||
columnData.isHidden = true;
|
|
||||||
hiddenColumns.push(columnData);
|
|
||||||
} else {
|
|
||||||
columns.push(columnData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns = reorderColumns(columns);
|
|
||||||
if (props.columnActions?.length) {
|
|
||||||
columns.push({
|
|
||||||
Header:
|
|
||||||
props.columnNameMap && props.columnNameMap["actions"]
|
|
||||||
? props.columnNameMap["actions"]
|
|
||||||
: "Actions",
|
|
||||||
accessor: "actions",
|
|
||||||
width: 150,
|
|
||||||
minWidth: 60,
|
|
||||||
draggable: true,
|
|
||||||
Cell: () => {
|
|
||||||
return renderActions({
|
|
||||||
columnActions: props.columnActions,
|
|
||||||
onCommandClick: props.onCommandClick,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hiddenColumns.length && props.renderMode === RenderModes.CANVAS) {
|
|
||||||
columns = columns.concat(hiddenColumns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableColumns = React.useMemo(getTableColumns, [
|
|
||||||
JSON.stringify({
|
|
||||||
data: props.tableData,
|
|
||||||
columnNameMap: props.columnNameMap,
|
|
||||||
columnActions: props.columnActions,
|
|
||||||
hiddenColumns: props.hiddenColumns,
|
|
||||||
columnSizeMap: props.columnSizeMap,
|
|
||||||
columnTypeMap: props.columnTypeMap,
|
|
||||||
columnOrder: props.columnOrder,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const headers = Array.prototype.slice.call(
|
const headers = Array.prototype.slice.call(
|
||||||
document.querySelectorAll(`#table${props.widgetId} .draggable-header`),
|
document.querySelectorAll(`#table${props.widgetId} .draggable-header`),
|
||||||
|
|
@ -267,9 +159,9 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let columnOrder = props.columnOrder;
|
let columnOrder = props.columnOrder;
|
||||||
if (columnOrder === undefined) {
|
if (columnOrder === undefined) {
|
||||||
columnOrder = getAllTableColumnKeys();
|
columnOrder = props.columns.map(item => item.accessor);
|
||||||
}
|
}
|
||||||
const draggedColumn = tableColumns[dragged].accessor;
|
const draggedColumn = props.columns[dragged].accessor;
|
||||||
columnOrder.splice(dragged, 1);
|
columnOrder.splice(dragged, 1);
|
||||||
columnOrder.splice(i, 0, draggedColumn);
|
columnOrder.splice(i, 0, draggedColumn);
|
||||||
props.handleReorderColumn(columnOrder);
|
props.handleReorderColumn(columnOrder);
|
||||||
|
|
@ -281,7 +173,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getColumnMenu = (columnIndex: number) => {
|
const getColumnMenu = (columnIndex: number) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnId = column.accessor;
|
const columnId = column.accessor;
|
||||||
const columnType =
|
const columnType =
|
||||||
props.columnTypeMap && props.columnTypeMap[columnId]
|
props.columnTypeMap && props.columnTypeMap[columnId]
|
||||||
|
|
@ -308,7 +200,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideColumn = (columnIndex: number, isColumnHidden: boolean) => {
|
const hideColumn = (columnIndex: number, isColumnHidden: boolean) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
let hiddenColumns = props.hiddenColumns || [];
|
let hiddenColumns = props.hiddenColumns || [];
|
||||||
if (!isColumnHidden) {
|
if (!isColumnHidden) {
|
||||||
hiddenColumns.push(column.accessor);
|
hiddenColumns.push(column.accessor);
|
||||||
|
|
@ -326,7 +218,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateColumnType = (columnIndex: number, columnType: string) => {
|
const updateColumnType = (columnIndex: number, columnType: string) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnTypeMap = props.columnTypeMap || {};
|
const columnTypeMap = props.columnTypeMap || {};
|
||||||
columnTypeMap[column.accessor] = {
|
columnTypeMap[column.accessor] = {
|
||||||
type: columnType,
|
type: columnType,
|
||||||
|
|
@ -336,7 +228,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnNameUpdate = (columnIndex: number, columnName: string) => {
|
const handleColumnNameUpdate = (columnIndex: number, columnName: string) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnNameMap = props.columnNameMap || {};
|
const columnNameMap = props.columnNameMap || {};
|
||||||
columnNameMap[column.accessor] = columnName;
|
columnNameMap[column.accessor] = columnName;
|
||||||
props.updateColumnName(columnNameMap);
|
props.updateColumnName(columnNameMap);
|
||||||
|
|
@ -346,7 +238,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
columnIndex: number,
|
columnIndex: number,
|
||||||
currencySymbol: string,
|
currencySymbol: string,
|
||||||
) => {
|
) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnTypeMap = props.columnTypeMap || {};
|
const columnTypeMap = props.columnTypeMap || {};
|
||||||
columnTypeMap[column.accessor] = {
|
columnTypeMap[column.accessor] = {
|
||||||
type: "currency",
|
type: "currency",
|
||||||
|
|
@ -356,7 +248,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateFormatUpdate = (columnIndex: number, dateFormat: string) => {
|
const handleDateFormatUpdate = (columnIndex: number, dateFormat: string) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnTypeMap = props.columnTypeMap || {};
|
const columnTypeMap = props.columnTypeMap || {};
|
||||||
columnTypeMap[column.accessor] = {
|
columnTypeMap[column.accessor] = {
|
||||||
type: "date",
|
type: "date",
|
||||||
|
|
@ -366,7 +258,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResizeColumn = (columnIndex: number, columnWidth: string) => {
|
const handleResizeColumn = (columnIndex: number, columnWidth: string) => {
|
||||||
const column = tableColumns[columnIndex];
|
const column = props.columns[columnIndex];
|
||||||
const columnSizeMap = props.columnSizeMap || {};
|
const columnSizeMap = props.columnSizeMap || {};
|
||||||
const width = Number(columnWidth.split("px")[0]);
|
const width = Number(columnWidth.split("px")[0]);
|
||||||
columnSizeMap[column.accessor] = width;
|
columnSizeMap[column.accessor] = width;
|
||||||
|
|
@ -391,8 +283,9 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
height={props.height}
|
height={props.height}
|
||||||
pageSize={props.pageSize || 1}
|
pageSize={props.pageSize || 1}
|
||||||
widgetId={props.widgetId}
|
widgetId={props.widgetId}
|
||||||
|
widgetName={props.widgetName}
|
||||||
searchKey={props.searchKey}
|
searchKey={props.searchKey}
|
||||||
columns={tableColumns}
|
columns={props.columns}
|
||||||
hiddenColumns={props.hiddenColumns}
|
hiddenColumns={props.hiddenColumns}
|
||||||
updateHiddenColumns={props.updateHiddenColumns}
|
updateHiddenColumns={props.updateHiddenColumns}
|
||||||
data={props.tableData}
|
data={props.tableData}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ interface TableProps {
|
||||||
height: number;
|
height: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
|
widgetName: string;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
columns: ReactTableColumnProps[];
|
columns: ReactTableColumnProps[];
|
||||||
|
|
@ -64,6 +65,9 @@ export const Table = (props: TableProps) => {
|
||||||
const pageCount = Math.ceil(props.data.length / props.pageSize);
|
const pageCount = Math.ceil(props.data.length / props.pageSize);
|
||||||
const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0;
|
const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0;
|
||||||
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
|
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
|
||||||
|
const columns = React.useMemo(() => props.columns, [
|
||||||
|
JSON.stringify(props.columns),
|
||||||
|
]);
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
|
|
@ -73,7 +77,7 @@ export const Table = (props: TableProps) => {
|
||||||
pageOptions,
|
pageOptions,
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns: props.columns,
|
columns: columns,
|
||||||
data,
|
data,
|
||||||
defaultColumn,
|
defaultColumn,
|
||||||
initialState: {
|
initialState: {
|
||||||
|
|
@ -103,6 +107,8 @@ export const Table = (props: TableProps) => {
|
||||||
id={`table${props.widgetId}`}
|
id={`table${props.widgetId}`}
|
||||||
>
|
>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
|
tableData={props.data}
|
||||||
|
tableColumns={props.columns}
|
||||||
searchTableData={props.searchTableData}
|
searchTableData={props.searchTableData}
|
||||||
searchKey={props.searchKey}
|
searchKey={props.searchKey}
|
||||||
updatePageNo={props.updatePageNo}
|
updatePageNo={props.updatePageNo}
|
||||||
|
|
@ -112,6 +118,7 @@ export const Table = (props: TableProps) => {
|
||||||
pageCount={pageCount}
|
pageCount={pageCount}
|
||||||
currentPageIndex={currentPageIndex}
|
currentPageIndex={currentPageIndex}
|
||||||
pageOptions={pageOptions}
|
pageOptions={pageOptions}
|
||||||
|
widgetName={props.widgetName}
|
||||||
serverSidePaginationEnabled={props.serverSidePaginationEnabled}
|
serverSidePaginationEnabled={props.serverSidePaginationEnabled}
|
||||||
columns={props.columns.filter((column: ReactTableColumnProps) => {
|
columns={props.columns.filter((column: ReactTableColumnProps) => {
|
||||||
return column.accessor !== "actions";
|
return column.accessor !== "actions";
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Icon,
|
Icon,
|
||||||
|
Tooltip,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { IconWrapper } from "constants/IconConstants";
|
import { IconWrapper } from "constants/IconConstants";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
@ -12,19 +13,7 @@ import { Colors } from "constants/Colors";
|
||||||
import { ReactComponent as VisibleIcon } from "assets/icons/control/columns-visibility.svg";
|
import { ReactComponent as VisibleIcon } from "assets/icons/control/columns-visibility.svg";
|
||||||
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button from "components/editorComponents/Button";
|
||||||
|
import { TableIconWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
||||||
const TableIconWrapper = styled.div<{ selected?: boolean; disabled?: boolean }>`
|
|
||||||
background: ${props => (props.selected ? Colors.ATHENS_GRAY : "transparent")};
|
|
||||||
box-shadow: ${props =>
|
|
||||||
props.selected ? `inset 0px 4px 0px ${Colors.GREEN}` : "none"};
|
|
||||||
width: 48px;
|
|
||||||
height: 60px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: ${props => (props.disabled ? 0.6 : 1)};
|
|
||||||
cursor: ${props => !props.disabled && "pointer"};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DropDownWrapper = styled.div`
|
const DropDownWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -117,13 +106,20 @@ const TableColumnsVisibility = (props: TableColumnsVisibilityProps) => {
|
||||||
selectMenu(true);
|
selectMenu(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconWrapper
|
<Tooltip
|
||||||
width={20}
|
autoFocus={false}
|
||||||
height={20}
|
hoverOpenDelay={1000}
|
||||||
color={selected ? Colors.OXFORD_BLUE : Colors.CADET_BLUE}
|
content="Hidden Fields"
|
||||||
|
position="top"
|
||||||
>
|
>
|
||||||
<VisibilityIcon />
|
<IconWrapper
|
||||||
</IconWrapper>
|
width={20}
|
||||||
|
height={20}
|
||||||
|
color={selected ? Colors.OXFORD_BLUE : Colors.CADET_BLUE}
|
||||||
|
>
|
||||||
|
<VisibilityIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</Tooltip>
|
||||||
</TableIconWrapper>
|
</TableIconWrapper>
|
||||||
<DropDownWrapper>
|
<DropDownWrapper>
|
||||||
{columns.map((option: ReactTableColumnProps, index: number) => (
|
{columns.map((option: ReactTableColumnProps, index: number) => (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React from "react";
|
||||||
|
import { IconWrapper } from "constants/IconConstants";
|
||||||
|
import { Tooltip } from "@blueprintjs/core";
|
||||||
|
import { Colors } from "constants/Colors";
|
||||||
|
import { ReactComponent as DownloadIcon } from "assets/icons/control/download-table.svg";
|
||||||
|
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
||||||
|
import { TableIconWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
||||||
|
|
||||||
|
interface TableDataDownloadProps {
|
||||||
|
data: object[];
|
||||||
|
columns: ReactTableColumnProps[];
|
||||||
|
widgetName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableDataDownload = (props: TableDataDownloadProps) => {
|
||||||
|
const [selected, toggleButtonClick] = React.useState(false);
|
||||||
|
const downloadTableData = () => {
|
||||||
|
toggleButtonClick(true);
|
||||||
|
const csvData = [];
|
||||||
|
csvData.push(
|
||||||
|
props.columns
|
||||||
|
.map((column: ReactTableColumnProps) => {
|
||||||
|
if (column.metaProperties && !column.metaProperties.isHidden) {
|
||||||
|
return column.Header;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(i => !!i),
|
||||||
|
);
|
||||||
|
for (let row = 0; row < props.data.length; row++) {
|
||||||
|
const data: { [key: string]: any } = props.data[row];
|
||||||
|
const csvDataRow = [];
|
||||||
|
for (let colIndex = 0; colIndex < props.columns.length; colIndex++) {
|
||||||
|
const column = props.columns[colIndex];
|
||||||
|
const value = data[column.accessor];
|
||||||
|
if (column.metaProperties && !column.metaProperties.isHidden) {
|
||||||
|
csvDataRow.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
csvData.push(csvDataRow);
|
||||||
|
}
|
||||||
|
let csvContent = "";
|
||||||
|
csvData.forEach(function(infoArray, index) {
|
||||||
|
const dataString = infoArray.join(",");
|
||||||
|
csvContent += index < csvData.length ? dataString + "\n" : dataString;
|
||||||
|
});
|
||||||
|
const fileName = `${props.widgetName}.csv`;
|
||||||
|
const anchor = document.createElement("a");
|
||||||
|
const mimeType = "application/octet-stream";
|
||||||
|
if (navigator.msSaveBlob) {
|
||||||
|
navigator.msSaveBlob(
|
||||||
|
new Blob([csvContent], {
|
||||||
|
type: mimeType,
|
||||||
|
}),
|
||||||
|
fileName,
|
||||||
|
);
|
||||||
|
} else if (URL && "download" in anchor) {
|
||||||
|
anchor.href = URL.createObjectURL(
|
||||||
|
new Blob([csvContent], {
|
||||||
|
type: mimeType,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
anchor.setAttribute("download", fileName);
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.click();
|
||||||
|
document.body.removeChild(anchor);
|
||||||
|
}
|
||||||
|
toggleButtonClick(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableIconWrapper
|
||||||
|
onClick={() => {
|
||||||
|
downloadTableData();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
autoFocus={false}
|
||||||
|
hoverOpenDelay={1000}
|
||||||
|
content="Download"
|
||||||
|
position="top"
|
||||||
|
>
|
||||||
|
<IconWrapper
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
color={selected ? Colors.OXFORD_BLUE : Colors.CADET_BLUE}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</Tooltip>
|
||||||
|
</TableIconWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableDataDownload;
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Icon, NumericInput } from "@blueprintjs/core";
|
import { Icon, NumericInput } from "@blueprintjs/core";
|
||||||
import SearchComponent from "components/designSystems/appsmith/SearchComponent";
|
|
||||||
import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
|
|
||||||
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
|
||||||
import {
|
import {
|
||||||
RowWrapper,
|
RowWrapper,
|
||||||
PaginationWrapper,
|
PaginationWrapper,
|
||||||
|
|
@ -11,6 +8,10 @@ import {
|
||||||
PaginationItemWrapper,
|
PaginationItemWrapper,
|
||||||
CommonFunctionsMenuWrapper,
|
CommonFunctionsMenuWrapper,
|
||||||
} from "./TableStyledWrappers";
|
} from "./TableStyledWrappers";
|
||||||
|
import SearchComponent from "components/designSystems/appsmith/SearchComponent";
|
||||||
|
import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
|
||||||
|
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
||||||
|
import TableDataDownload from "components/designSystems/appsmith/TableDataDownload";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
|
|
||||||
const PageNumberInputWrapper = styled(NumericInput)`
|
const PageNumberInputWrapper = styled(NumericInput)`
|
||||||
|
|
@ -58,12 +59,15 @@ interface TableHeaderProps {
|
||||||
nextPageClick: () => void;
|
nextPageClick: () => void;
|
||||||
prevPageClick: () => void;
|
prevPageClick: () => void;
|
||||||
pageNo: number;
|
pageNo: number;
|
||||||
|
tableData: object[];
|
||||||
|
tableColumns: ReactTableColumnProps[];
|
||||||
pageCount: number;
|
pageCount: number;
|
||||||
currentPageIndex: number;
|
currentPageIndex: number;
|
||||||
pageOptions: number[];
|
pageOptions: number[];
|
||||||
columns: ReactTableColumnProps[];
|
columns: ReactTableColumnProps[];
|
||||||
hiddenColumns?: string[];
|
hiddenColumns?: string[];
|
||||||
updateHiddenColumns: (hiddenColumns?: string[]) => void;
|
updateHiddenColumns: (hiddenColumns?: string[]) => void;
|
||||||
|
widgetName: string;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
searchTableData: (searchKey: any) => void;
|
searchTableData: (searchKey: any) => void;
|
||||||
serverSidePaginationEnabled: boolean;
|
serverSidePaginationEnabled: boolean;
|
||||||
|
|
@ -79,6 +83,11 @@ const TableHeader = (props: TableHeaderProps) => {
|
||||||
onSearch={props.searchTableData}
|
onSearch={props.searchTableData}
|
||||||
/>
|
/>
|
||||||
<CommonFunctionsMenuWrapper>
|
<CommonFunctionsMenuWrapper>
|
||||||
|
<TableDataDownload
|
||||||
|
data={props.tableData}
|
||||||
|
columns={props.tableColumns}
|
||||||
|
widgetName={props.widgetName}
|
||||||
|
/>
|
||||||
{props.displayColumnActions && (
|
{props.displayColumnActions && (
|
||||||
<TableColumnsVisibility
|
<TableColumnsVisibility
|
||||||
columns={props.columns}
|
columns={props.columns}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { Colors } from "constants/Colors";
|
||||||
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
||||||
|
|
||||||
export const TableWrapper = styled.div<{ width: number; height: number }>`
|
export const TableWrapper = styled.div<{ width: number; height: number }>`
|
||||||
width: ${props => props.width - 5}px;
|
width: 100%;
|
||||||
height: ${props => props.height - 5}px;
|
height: 100%;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid ${Colors.GEYSER_LIGHT};
|
border: 1px solid ${Colors.GEYSER_LIGHT};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -21,26 +21,14 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
color: ${Colors.BLUE_BAYOUX};
|
color: ${Colors.BLUE_BAYOUX};
|
||||||
position: relative;
|
position: relative;
|
||||||
.thead {
|
overflow-y: auto;
|
||||||
overflow-y: auto;
|
height: ${props => props.height - TABLE_SIZES.TABLE_HEADER_HEIGHT}px;
|
||||||
overflow-x: hidden;
|
.thead,
|
||||||
}
|
|
||||||
.tbody {
|
.tbody {
|
||||||
overflow: scroll;
|
overflow: hidden;
|
||||||
/*
|
|
||||||
Here 5px is subtracted to compensate padding from widget resizers and
|
|
||||||
113px to compensate table column header and table header heights
|
|
||||||
*/
|
|
||||||
height: ${props =>
|
|
||||||
props.height -
|
|
||||||
5 -
|
|
||||||
TABLE_SIZES.TABLE_HEADER_HEIGHT -
|
|
||||||
TABLE_SIZES.COLUMN_HEADER_HEIGHT}px;
|
|
||||||
&.no-scroll {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.tr {
|
.tr {
|
||||||
|
overflow: hidden;
|
||||||
:nth-child(even) {
|
:nth-child(even) {
|
||||||
background: ${Colors.ATHENS_GRAY_DARKER};
|
background: ${Colors.ATHENS_GRAY_DARKER};
|
||||||
}
|
}
|
||||||
|
|
@ -285,3 +273,22 @@ export const RowWrapper = styled.div`
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const TableIconWrapper = styled.div<{
|
||||||
|
selected?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>`
|
||||||
|
background: ${props => (props.selected ? Colors.ATHENS_GRAY : "transparent")};
|
||||||
|
box-shadow: ${props =>
|
||||||
|
props.selected ? `inset 0px 4px 0px ${Colors.GREEN}` : "none"};
|
||||||
|
width: 48px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: ${props => (props.disabled ? 0.6 : 1)};
|
||||||
|
cursor: ${props => !props.disabled && "pointer"};
|
||||||
|
&:hover {
|
||||||
|
background: ${Colors.ATHENS_GRAY};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Icon, InputGroup } from "@blueprintjs/core";
|
import { Icon, InputGroup } from "@blueprintjs/core";
|
||||||
import moment from "moment-timezone";
|
|
||||||
import {
|
import {
|
||||||
MenuColumnWrapper,
|
MenuColumnWrapper,
|
||||||
CellWrapper,
|
CellWrapper,
|
||||||
ActionWrapper,
|
ActionWrapper,
|
||||||
} from "./TableStyledWrappers";
|
} from "./TableStyledWrappers";
|
||||||
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
||||||
import { ColumnMenuOptionProps } from "./ReactTableComponent";
|
import {
|
||||||
import { isString } from "lodash";
|
ColumnMenuOptionProps,
|
||||||
|
ReactTableColumnProps,
|
||||||
|
ColumnTypes,
|
||||||
|
} from "components/designSystems/appsmith/ReactTableComponent";
|
||||||
|
import { isString, isNumber } from "lodash";
|
||||||
import VideoComponent from "components/designSystems/appsmith/VideoComponent";
|
import VideoComponent from "components/designSystems/appsmith/VideoComponent";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button from "components/editorComponents/Button";
|
||||||
import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
|
import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
|
||||||
import TableColumnMenuPopup from "./TableColumnMenu";
|
import TableColumnMenuPopup from "./TableColumnMenu";
|
||||||
|
import { Colors } from "constants/Colors";
|
||||||
|
|
||||||
interface MenuOptionProps {
|
interface MenuOptionProps {
|
||||||
columnAccessor?: string;
|
columnAccessor?: string;
|
||||||
|
|
@ -57,86 +61,106 @@ export const getMenuOptions = (props: MenuOptionProps) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "image"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.IMAGE}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="media"
|
icon="media"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "image" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.IMAGE
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Image</div>
|
<div className="title">Image</div>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
isSelected: props.columnType === "image",
|
isSelected: props.columnType === ColumnTypes.IMAGE,
|
||||||
onClick: (columnIndex: number, isSelected: boolean) => {
|
onClick: (columnIndex: number, isSelected: boolean) => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
props.updateColumnType(columnIndex, "");
|
props.updateColumnType(columnIndex, "");
|
||||||
} else {
|
} else {
|
||||||
props.updateColumnType(columnIndex, "image");
|
props.updateColumnType(columnIndex, ColumnTypes.IMAGE);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "video"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.VIDEO}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="video"
|
icon="video"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "video" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.VIDEO
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Video</div>
|
<div className="title">Video</div>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
isSelected: props.columnType === "video",
|
isSelected: props.columnType === ColumnTypes.VIDEO,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
onClick: (columnIndex: number, isSelected: boolean) => {
|
onClick: (columnIndex: number, isSelected: boolean) => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
props.updateColumnType(columnIndex, "");
|
props.updateColumnType(columnIndex, "");
|
||||||
} else {
|
} else {
|
||||||
props.updateColumnType(columnIndex, "video");
|
props.updateColumnType(columnIndex, ColumnTypes.VIDEO);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "text"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.TEXT}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="label"
|
icon="label"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "text" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.TEXT
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Text</div>
|
<div className="title">Text</div>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
isSelected: props.columnType === "text",
|
isSelected: props.columnType === ColumnTypes.TEXT,
|
||||||
onClick: (columnIndex: number, isSelected: boolean) => {
|
onClick: (columnIndex: number, isSelected: boolean) => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
props.updateColumnType(columnIndex, "");
|
props.updateColumnType(columnIndex, "");
|
||||||
} else {
|
} else {
|
||||||
props.updateColumnType(columnIndex, "text");
|
props.updateColumnType(columnIndex, ColumnTypes.TEXT);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "currency"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.CURRENCY}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="dollar"
|
icon="dollar"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "currency" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.CURRENCY
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Currency</div>
|
<div className="title">Currency</div>
|
||||||
<Icon
|
<Icon
|
||||||
className="sub-menu"
|
className="sub-menu"
|
||||||
icon="chevron-right"
|
icon="chevron-right"
|
||||||
iconSize={16}
|
iconSize={16}
|
||||||
color={props.columnType === "currency" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.CURRENCY
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
isSelected: props.columnType === "currency",
|
isSelected: props.columnType === ColumnTypes.CURRENCY,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
content: "USD - $",
|
content: "USD - $",
|
||||||
|
|
@ -198,23 +222,31 @@ export const getMenuOptions = (props: MenuOptionProps) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "date"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.DATE}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="calendar"
|
icon="calendar"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "date" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.DATE
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Date</div>
|
<div className="title">Date</div>
|
||||||
<Icon
|
<Icon
|
||||||
className="sub-menu"
|
className="sub-menu"
|
||||||
icon="chevron-right"
|
icon="chevron-right"
|
||||||
iconSize={16}
|
iconSize={16}
|
||||||
color={props.columnType === "date" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.DATE
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
isSelected: props.columnType === "date",
|
isSelected: props.columnType === ColumnTypes.DATE,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
content: "MM-DD-YY",
|
content: "MM-DD-YY",
|
||||||
|
|
@ -252,22 +284,26 @@ export const getMenuOptions = (props: MenuOptionProps) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: (
|
content: (
|
||||||
<MenuColumnWrapper selected={props.columnType === "time"}>
|
<MenuColumnWrapper selected={props.columnType === ColumnTypes.TIME}>
|
||||||
<Icon
|
<Icon
|
||||||
icon="time"
|
icon="time"
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
color={props.columnType === "time" ? "#ffffff" : "#2E3D49"}
|
color={
|
||||||
|
props.columnType === ColumnTypes.TIME
|
||||||
|
? Colors.WHITE
|
||||||
|
: Colors.OXFORD_BLUE
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="title">Time</div>
|
<div className="title">Time</div>
|
||||||
</MenuColumnWrapper>
|
</MenuColumnWrapper>
|
||||||
),
|
),
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
isSelected: props.columnType === "time",
|
isSelected: props.columnType === ColumnTypes.TIME,
|
||||||
onClick: (columnIndex: number, isSelected: boolean) => {
|
onClick: (columnIndex: number, isSelected: boolean) => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
props.updateColumnType(columnIndex, "");
|
props.updateColumnType(columnIndex, "");
|
||||||
} else {
|
} else {
|
||||||
props.updateColumnType(columnIndex, "time");
|
props.updateColumnType(columnIndex, ColumnTypes.TIME);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -277,17 +313,11 @@ export const getMenuOptions = (props: MenuOptionProps) => {
|
||||||
|
|
||||||
export const renderCell = (
|
export const renderCell = (
|
||||||
value: any,
|
value: any,
|
||||||
rowIndex: number,
|
|
||||||
columnType: string,
|
columnType: string,
|
||||||
isHidden: boolean,
|
isHidden: boolean,
|
||||||
widgetId: string,
|
|
||||||
format?: string,
|
|
||||||
) => {
|
) => {
|
||||||
if (!value) {
|
|
||||||
return <div></div>;
|
|
||||||
}
|
|
||||||
switch (columnType) {
|
switch (columnType) {
|
||||||
case "image":
|
case ColumnTypes.IMAGE:
|
||||||
if (!isString(value)) {
|
if (!isString(value)) {
|
||||||
return (
|
return (
|
||||||
<CellWrapper isHidden={isHidden}>
|
<CellWrapper isHidden={isHidden}>
|
||||||
|
|
@ -316,7 +346,7 @@ export const renderCell = (
|
||||||
})}
|
})}
|
||||||
</CellWrapper>
|
</CellWrapper>
|
||||||
);
|
);
|
||||||
case "video":
|
case ColumnTypes.VIDEO:
|
||||||
const youtubeRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
|
const youtubeRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
|
||||||
if (isString(value) && youtubeRegex.test(value)) {
|
if (isString(value) && youtubeRegex.test(value)) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -329,65 +359,11 @@ export const renderCell = (
|
||||||
<CellWrapper isHidden={isHidden}>Invalid Video Link</CellWrapper>
|
<CellWrapper isHidden={isHidden}>Invalid Video Link</CellWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "currency":
|
|
||||||
if (!isNaN(value)) {
|
|
||||||
return (
|
|
||||||
<AutoToolTipComponent
|
|
||||||
title={`${format}${value}`}
|
|
||||||
isHidden={isHidden}
|
|
||||||
>{`${format}${value}`}</AutoToolTipComponent>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <CellWrapper isHidden={isHidden}>Invalid Value</CellWrapper>;
|
|
||||||
}
|
|
||||||
case "date":
|
|
||||||
let isValidDate = true;
|
|
||||||
if (isNaN(value)) {
|
|
||||||
const dateTime = Date.parse(value);
|
|
||||||
if (isNaN(dateTime)) {
|
|
||||||
isValidDate = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isValidDate) {
|
|
||||||
return (
|
|
||||||
<AutoToolTipComponent
|
|
||||||
title={moment(value).format(format)}
|
|
||||||
isHidden={isHidden}
|
|
||||||
>
|
|
||||||
{moment(value).format(format)}
|
|
||||||
</AutoToolTipComponent>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <CellWrapper isHidden={isHidden}>Invalid Date</CellWrapper>;
|
|
||||||
}
|
|
||||||
case "time":
|
|
||||||
let isValidTime = true;
|
|
||||||
if (isNaN(value)) {
|
|
||||||
const time = Date.parse(value);
|
|
||||||
if (isNaN(time)) {
|
|
||||||
isValidTime = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isValidTime) {
|
|
||||||
return (
|
|
||||||
<CellWrapper isHidden={isHidden}>
|
|
||||||
{moment(value).format("HH:mm")}
|
|
||||||
</CellWrapper>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <CellWrapper isHidden={isHidden}>Invalid Time</CellWrapper>;
|
|
||||||
}
|
|
||||||
case "text":
|
|
||||||
const text = isString(value) ? value : JSON.stringify(value);
|
|
||||||
return (
|
|
||||||
<AutoToolTipComponent title={text} isHidden={isHidden}>
|
|
||||||
{text}
|
|
||||||
</AutoToolTipComponent>
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
const data = isString(value) ? value : JSON.stringify(value);
|
const data =
|
||||||
|
isString(value) || isNumber(value) ? value : JSON.stringify(value);
|
||||||
return (
|
return (
|
||||||
<AutoToolTipComponent title={data} isHidden={isHidden}>
|
<AutoToolTipComponent title={data.toString()} isHidden={isHidden}>
|
||||||
{data}
|
{data}
|
||||||
</AutoToolTipComponent>
|
</AutoToolTipComponent>
|
||||||
);
|
);
|
||||||
|
|
@ -586,3 +562,51 @@ export const TableHeaderCell = (props: {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAllTableColumnKeys = (tableData: object[]) => {
|
||||||
|
const columnKeys: string[] = [];
|
||||||
|
for (let i = 0, tableRowCount = tableData.length; i < tableRowCount; i++) {
|
||||||
|
const row = tableData[i];
|
||||||
|
for (const key in row) {
|
||||||
|
if (!columnKeys.includes(key)) {
|
||||||
|
columnKeys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reorderColumns = (
|
||||||
|
columns: ReactTableColumnProps[],
|
||||||
|
columnOrder: string[],
|
||||||
|
) => {
|
||||||
|
const reorderedColumns = [];
|
||||||
|
const reorderedFlagMap: { [key: string]: boolean } = {};
|
||||||
|
for (let index = 0; index < columns.length; index++) {
|
||||||
|
const accessor = columnOrder[index];
|
||||||
|
if (accessor) {
|
||||||
|
const column = columns.filter((col: ReactTableColumnProps) => {
|
||||||
|
return col.accessor === accessor;
|
||||||
|
});
|
||||||
|
if (column.length && !reorderedFlagMap[column[0].accessor]) {
|
||||||
|
reorderedColumns.push(column[0]);
|
||||||
|
reorderedFlagMap[column[0].accessor] = true;
|
||||||
|
} else if (!reorderedFlagMap[columns[index].accessor]) {
|
||||||
|
reorderedColumns.push(columns[index]);
|
||||||
|
reorderedFlagMap[columns[index].accessor] = true;
|
||||||
|
}
|
||||||
|
} else if (!reorderedFlagMap[columns[index].accessor]) {
|
||||||
|
reorderedColumns.push(columns[index]);
|
||||||
|
reorderedFlagMap[columns[index].accessor] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reorderedColumns.length < columns.length) {
|
||||||
|
for (let index = 0; index < columns.length; index++) {
|
||||||
|
if (!reorderedFlagMap[columns[index].accessor]) {
|
||||||
|
reorderedColumns.push(columns[index]);
|
||||||
|
reorderedFlagMap[columns[index].accessor] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reorderedColumns;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,19 @@ import React, { Suspense } from "react";
|
||||||
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
||||||
import { WidgetType } from "constants/WidgetConstants";
|
import { WidgetType } from "constants/WidgetConstants";
|
||||||
import { EventType } from "constants/ActionConstants";
|
import { EventType } from "constants/ActionConstants";
|
||||||
import ReactTableComponent from "components/designSystems/appsmith/ReactTableComponent";
|
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 { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
||||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||||
|
import { RenderMode, RenderModes } from "constants/WidgetConstants";
|
||||||
import {
|
import {
|
||||||
WidgetPropertyValidationType,
|
WidgetPropertyValidationType,
|
||||||
BASE_WIDGET_VALIDATION,
|
BASE_WIDGET_VALIDATION,
|
||||||
|
|
@ -12,6 +22,7 @@ import {
|
||||||
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
||||||
import { TriggerPropertiesMap } from "utils/WidgetFactory";
|
import { TriggerPropertiesMap } from "utils/WidgetFactory";
|
||||||
import Skeleton from "components/utils/Skeleton";
|
import Skeleton from "components/utils/Skeleton";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
static getPropertyValidationMap(): WidgetPropertyValidationType {
|
static getPropertyValidationMap(): WidgetPropertyValidationType {
|
||||||
|
|
@ -51,6 +62,141 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: () => {
|
||||||
|
return renderActions({
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
searchTableData = (tableData: object[]) => {
|
searchTableData = (tableData: object[]) => {
|
||||||
const searchKey =
|
const searchKey =
|
||||||
this.props.searchKey !== undefined
|
this.props.searchKey !== undefined
|
||||||
|
|
@ -66,8 +212,9 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
|
|
||||||
getPageView() {
|
getPageView() {
|
||||||
const { tableData, hiddenColumns } = this.props;
|
const { tableData, hiddenColumns } = this.props;
|
||||||
|
const tableColumns = this.getTableColumns(tableData);
|
||||||
const filteredTableData = this.searchTableData(tableData);
|
const filteredTableData = this.searchTableData(tableData);
|
||||||
|
const transformedData = this.transformData(filteredTableData, tableColumns);
|
||||||
const serverSidePaginationEnabled = (this.props
|
const serverSidePaginationEnabled = (this.props
|
||||||
.serverSidePaginationEnabled &&
|
.serverSidePaginationEnabled &&
|
||||||
this.props.serverSidePaginationEnabled) as boolean;
|
this.props.serverSidePaginationEnabled) as boolean;
|
||||||
|
|
@ -94,9 +241,11 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
<ReactTableComponent
|
<ReactTableComponent
|
||||||
height={componentHeight}
|
height={componentHeight}
|
||||||
width={componentWidth}
|
width={componentWidth}
|
||||||
tableData={filteredTableData}
|
tableData={transformedData}
|
||||||
|
columns={tableColumns}
|
||||||
isLoading={this.props.isLoading}
|
isLoading={this.props.isLoading}
|
||||||
widgetId={this.props.widgetId}
|
widgetId={this.props.widgetId}
|
||||||
|
widgetName={this.props.widgetName}
|
||||||
searchKey={this.props.searchKey}
|
searchKey={this.props.searchKey}
|
||||||
renderMode={this.props.renderMode}
|
renderMode={this.props.renderMode}
|
||||||
hiddenColumns={hiddenColumns}
|
hiddenColumns={hiddenColumns}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user