feat: Table widget currency column (#27819)

## Description
Adds a new currency column type on the table widget.

#### PR fixes following issue(s)
Fixes #5632 
> if no issue exists, please create an issue and ask the maintainers
about this first
>
>
#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change

- New feature (non-breaking change which adds functionality)
>
>
>
## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [x] Cypress
>
>
#### Test Plan
> https://github.com/appsmithorg/TestSmith/issues/2447
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
balajisoundar 2023-11-06 11:05:26 +05:30 committed by GitHub
parent 83c6e65030
commit 694b17062b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 586 additions and 43 deletions

View File

@ -0,0 +1,125 @@
import * as _ from "../../../../../../support/Objects/ObjectsCore";
const tableData = `{{[
{
"amount": 10000.01,
},
]}}`;
function updateCellValue(value) {
cy.editTableCell(0, 0);
cy.enterTableCellValue(0, 0, value);
cy.saveTableCellValue(0, 0);
_.agHelper.Sleep(500);
}
describe("Currency column", () => {
before(() => {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.TEXT, 300, 400);
_.propPane.UpdatePropertyFieldValue(
"Text",
`{{Table1.editableCell.value}}|{{Table1.editableCell.inputValue}}|{{typeof Table1.editableCell.value}}|{{typeof Table1.editableCell.inputValue}}`,
);
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.TABLE);
_.propPane.EnterJSContext("Table data", tableData);
cy.makeColumnEditable("amount");
});
it("1. should test that currency column is available", () => {
cy.editColumn("amount");
cy.changeColumnType("Currency");
cy.get(".t--property-control-currency").should("exist");
});
it("2. should test that currency column properties are displayed and working", () => {
cy.get(".t--property-control-currency").should("exist");
cy.get(".t--property-control-decimalsallowed").should("exist");
cy.get(".t--property-control-notation").should("exist");
cy.get(".t--property-control-thousandseparator").should("exist");
});
it("3. should test that currency column is formatted correctly", () => {
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("$ 10,000");
});
_.propPane.ToggleJSMode("Currency", true);
_.propPane.EnterJSContext("Currency", "INR");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10,000");
});
_.propPane.SelectPropertiesDropDown("Decimals allowed", "1");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10,000.0");
});
_.propPane.SelectPropertiesDropDown("Decimals allowed", "2");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10,000.01");
});
_.propPane.TogglePropertyState("Thousand separator", false);
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10000.01");
});
_.propPane.SelectPropertiesDropDown("Notation", "Compact");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10.00K");
});
_.propPane.SelectPropertiesDropDown("Decimals allowed", "0");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 10K");
});
});
it("4. shoudl test that currency column is editable", () => {
_.propPane.SelectPropertiesDropDown("Notation", "Standard");
updateCellValue("1,234.23");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 123423");
});
_.propPane.SelectPropertiesDropDown("Decimals allowed", "1");
updateCellValue("4321.23");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 4321.2");
});
_.propPane.SelectPropertiesDropDown("Decimals allowed", "2");
updateCellValue("1234.23");
cy.readTableV2data(0, 0).then((val) => {
expect(val).to.equal("₹ 1234.23");
});
cy.editTableCell(0, 0);
cy.enterTableCellValue(0, 0, 6543.23);
cy.get(".t--widget-textwidget .t--text-widget-container").should(
"have.text",
"6543.23|6543.23|number|string",
);
cy.saveTableCellValue(0, 0);
});
});

View File

@ -212,7 +212,7 @@ export const getCountryCodeFromCurrencyCode = (currencyCode?: string) => {
};
interface CurrencyDropdownProps {
onCurrencyTypeChange: (currencyCountryCode?: string) => void;
onCurrencyTypeChange?: (currencyCountryCode?: string) => void;
options: Array<DropdownOption>;
selected?: string;
allowCurrencyChange?: boolean;

View File

@ -27,6 +27,7 @@ export interface TableSizes {
EDIT_ICON_TOP: number;
ROW_VIRTUAL_OFFSET: number;
VERTICAL_EDITOR_PADDING: number;
EDITABLE_CELL_HEIGHT: number;
}
export enum CompactModeTypes {
@ -63,6 +64,7 @@ export const TABLE_SIZES: { [key: string]: TableSizes } = {
VERTICAL_EDITOR_PADDING: 0,
EDIT_ICON_TOP: 10,
ROW_VIRTUAL_OFFSET: 3,
EDITABLE_CELL_HEIGHT: 30,
},
[CompactModeTypes.SHORT]: {
COLUMN_HEADER_HEIGHT: 32,
@ -73,6 +75,7 @@ export const TABLE_SIZES: { [key: string]: TableSizes } = {
VERTICAL_EDITOR_PADDING: 0,
EDIT_ICON_TOP: 5,
ROW_VIRTUAL_OFFSET: 1,
EDITABLE_CELL_HEIGHT: 20,
},
[CompactModeTypes.TALL]: {
COLUMN_HEADER_HEIGHT: 32,
@ -83,6 +86,7 @@ export const TABLE_SIZES: { [key: string]: TableSizes } = {
VERTICAL_EDITOR_PADDING: 16,
EDIT_ICON_TOP: 21,
ROW_VIRTUAL_OFFSET: 3,
EDITABLE_CELL_HEIGHT: 30,
},
};
@ -192,6 +196,13 @@ export interface DateCellProperties {
timePrecision?: TimePrecision;
}
export interface CurrencyCellProperties {
currencyCode: string;
decimals: number;
thousandSeparator: boolean;
notation: Intl.NumberFormatOptions["notation"];
}
export interface BaseCellProperties {
horizontalAlignment?: CellAlignment;
verticalAlignment?: VerticalAlignment;
@ -217,6 +228,7 @@ export interface CellLayoutProperties
SelectCellProperties,
ImageCellProperties,
DateCellProperties,
CurrencyCellProperties,
BaseCellProperties {}
export interface TableColumnMetaProps {
@ -224,6 +236,7 @@ export interface TableColumnMetaProps {
format?: string;
inputFormat?: string;
type: ColumnTypes;
decimals?: number;
}
export enum StickyType {
@ -330,11 +343,19 @@ export interface EditActionColumnProperties {
selectOptions?: DropdownOption[] | DropdownOption[][];
}
export interface CurrencyColumnProperties {
currencyCode?: string;
decimals?: number;
thousandSeparator?: boolean;
notation?: Intl.NumberFormatOptions["notation"];
}
export interface ColumnProperties
extends ColumnBaseProperties,
ColumnStyleProperties,
DateColumnProperties,
ColumnEditabilityProperties,
CurrencyColumnProperties,
EditActionColumnProperties {
allowSameOptionsInNewRow?: boolean;
newRowSelectOptions?: DropdownOption[];

View File

@ -1,12 +1,26 @@
import { Colors } from "constants/Colors";
import { isNil } from "lodash";
import React, { useCallback, useLayoutEffect, useRef, useState } from "react";
import React, {
useCallback,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import styled from "styled-components";
import type { InputHTMLType } from "widgets/BaseInputWidget/component";
import BaseInputComponent from "widgets/BaseInputWidget/component";
import { InputTypes } from "widgets/BaseInputWidget/constants";
import type { EditableCell } from "widgets/TableWidgetV2/constants";
import type { VerticalAlignment } from "../Constants";
import { EDITABLE_CELL_PADDING_OFFSET, TABLE_SIZES } from "../Constants";
import {
getLocaleDecimalSeperator,
getLocaleThousandSeparator,
} from "widgets/WidgetUtils";
import { limitDecimalValue } from "widgets/CurrencyInputWidget/component/utilities";
import * as Sentry from "@sentry/react";
import { getLocale } from "utils/helpers";
const FOCUS_CLASS = "has-focus";
@ -69,9 +83,21 @@ const Wrapper = styled.div<{
*/
box-shadow: none !important;
padding: 0px 5px 0px 6px;
min-height: 34px;
height: ${(props) =>
TABLE_SIZES[props.compactMode].EDITABLE_CELL_HEIGHT}px;
min-height: ${(props) =>
TABLE_SIZES[props.compactMode].EDITABLE_CELL_HEIGHT}px;
font-size: ${(props) => props.textSize};
}
.currency-change-dropdown-trigger {
border: none;
height: ${(props) =>
TABLE_SIZES[props.compactMode].EDITABLE_CELL_HEIGHT}px;
padding: 0 0 0 5px;
margin-right: 0;
}
.bp3-button-group.bp3-vertical {
display: none;
}
@ -99,10 +125,28 @@ const Wrapper = styled.div<{
}
`;
function convertToNumber(inputValue: string) {
inputValue = inputValue.replace(
new RegExp(`[${getLocaleDecimalSeperator()}]`),
".",
);
const parsedValue = Number(inputValue);
if (isNaN(parsedValue) || inputValue.trim() === "" || isNil(inputValue)) {
return null;
} else if (Number.isFinite(parsedValue)) {
return parsedValue;
} else {
return null;
}
}
interface InlineEditorPropsType {
accentColor: string;
compactMode: string;
inputType: InputTypes.TEXT | InputTypes.NUMBER;
inputType: InputTypes;
inputHTMLType: InputHTMLType;
multiline: boolean;
onChange: (value: EditableCell["value"], inputValue: string) => void;
onDiscard: () => void;
@ -116,13 +160,16 @@ interface InlineEditorPropsType {
widgetId: string;
paddedInput: boolean;
autoFocus: boolean;
additionalProps: Record<string, unknown>;
}
export function InlineCellEditor({
accentColor,
additionalProps = {},
allowCellWrapping,
autoFocus,
compactMode,
inputHTMLType,
inputType = InputTypes.TEXT,
isEditableCellValid,
multiline,
@ -173,16 +220,21 @@ export function InlineCellEditor({
let value: EditableCell["value"] = inputValue;
if (inputType === InputTypes.NUMBER) {
const parsedValue = Number(inputValue);
value = convertToNumber(inputValue);
} else if (inputType === InputTypes.CURRENCY) {
const decimalSeperator = getLocaleDecimalSeperator();
if (
isNaN(parsedValue) ||
inputValue.trim() === "" ||
isNil(inputValue)
) {
value = null;
} else if (Number.isFinite(parsedValue)) {
value = parsedValue;
try {
if (inputValue && inputValue.includes(decimalSeperator)) {
inputValue = limitDecimalValue(
additionalProps.decimals as number,
inputValue,
);
}
value = convertToNumber(inputValue);
} catch (e) {
Sentry.captureException(e);
}
}
@ -201,6 +253,20 @@ export function InlineCellEditor({
}
}, [multiline]);
const parsedValue = useMemo(() => {
if (inputType === InputTypes.CURRENCY && typeof value === "number") {
return Intl.NumberFormat(getLocale(), {
style: "decimal",
minimumFractionDigits: additionalProps.decimals as number,
maximumFractionDigits: additionalProps.decimals as number,
})
.format(value)
.replaceAll(getLocaleThousandSeparator(), "");
} else {
return value;
}
}, [value]);
return (
<Wrapper
accentColor={accentColor}
@ -221,7 +287,7 @@ export function InlineCellEditor({
disableNewLineOnPressEnterKey={false}
errorMessage={validationErrorMessage}
errorTooltipBoundary={`#table${widgetId} .tableWrap`}
inputHTMLType={inputType}
inputHTMLType={inputHTMLType}
inputRef={inputRef}
inputType={inputType}
isInvalid={hasFocus && !isEditableCellValid}
@ -232,8 +298,9 @@ export function InlineCellEditor({
onKeyDown={onKeyDown}
onValueChange={onTextChange}
showError
value={value}
value={parsedValue}
widgetId=""
{...additionalProps}
/>
</Wrapper>
);

View File

@ -15,6 +15,12 @@ import { BasicCell } from "./BasicCell";
import { InlineCellEditor } from "./InlineCellEditor";
import styled from "styled-components";
import fastdom from "fastdom";
import CurrencyTypeDropdown, {
CurrencyDropdownOptions,
} from "widgets/CurrencyInputWidget/component/CurrencyCodeDropdown";
import { getLocale } from "utils/helpers";
import * as Sentry from "@sentry/react";
import { getLocaleThousandSeparator } from "widgets/WidgetUtils";
const Container = styled.div<{
isCellEditMode?: boolean;
@ -58,6 +64,13 @@ export type RenderDefaultPropsType = BaseCellComponentProps & {
isNewRow: boolean;
};
export interface RenderCurrencyPropsType {
currencyCode?: string;
decimals?: number;
notation?: Intl.NumberFormatOptions["notation"];
thousandSeparator?: boolean;
}
interface editPropertyType {
alias: string;
onSubmitString: string;
@ -73,6 +86,8 @@ export function getCellText(
if (value && columnType === ColumnTypes.URL && displayText) {
text = displayText;
} else if (columnType === ColumnTypes.CURRENCY) {
text = value ?? "";
} else if (!isNil(value) && (!isNumber(value) || !isNaN(value))) {
text = (value as string).toString();
} else {
@ -89,7 +104,9 @@ function getContentHeight(ref: RefObject<HTMLDivElement>) {
);
}
function PlainTextCell(props: RenderDefaultPropsType & editPropertyType) {
function PlainTextCell(
props: RenderDefaultPropsType & editPropertyType & RenderCurrencyPropsType,
) {
const {
accentColor,
alias,
@ -97,6 +114,8 @@ function PlainTextCell(props: RenderDefaultPropsType & editPropertyType) {
cellBackground,
columnType,
compactMode,
currencyCode,
decimals,
disabledEditIcon,
disabledEditIconMessage,
displayText,
@ -110,12 +129,14 @@ function PlainTextCell(props: RenderDefaultPropsType & editPropertyType) {
isEditableCellValid,
isHidden,
isNewRow,
notation,
onCellTextChange,
onSubmitString,
rowIndex,
tableWidth,
textColor,
textSize,
thousandSeparator,
toggleCellEditMode,
validationErrorMessage,
verticalAlignment,
@ -174,18 +195,82 @@ function PlainTextCell(props: RenderDefaultPropsType & editPropertyType) {
}
}, [value, isCellEditMode]);
const currency = useMemo(
() => CurrencyDropdownOptions.find((d) => d.value === currencyCode),
[currencyCode],
);
const formattedValue = useMemo(() => {
if (columnType === ColumnTypes.CURRENCY) {
try {
const floatVal = parseFloat(value);
if (isNaN(floatVal)) {
return value;
} else {
let formattedValue = Intl.NumberFormat(getLocale(), {
style: "decimal",
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
notation: notation,
}).format(floatVal);
if (!thousandSeparator) {
formattedValue = formattedValue.replaceAll(
getLocaleThousandSeparator(),
"",
);
}
return currency?.id + " " + formattedValue;
}
} catch (e) {
Sentry.captureException(e);
return value;
}
} else {
return value;
}
}, [value, decimals, currencyCode, notation, thousandSeparator, columnType]);
if (isCellEditMode) {
const [inputType, inputHTMLType]: [InputTypes, "TEXT" | "NUMBER"] = (() => {
switch (columnType) {
case ColumnTypes.NUMBER:
return [InputTypes.NUMBER, "NUMBER"];
case ColumnTypes.CURRENCY:
return [InputTypes.CURRENCY, "NUMBER"];
default:
return [InputTypes.TEXT, "TEXT"];
}
})();
const additionalProps: Record<string, unknown> = {};
if (inputType === InputTypes.CURRENCY) {
additionalProps.inputHTMLType = "NUMBER";
additionalProps.leftIcon = (
<CurrencyTypeDropdown
accentColor={accentColor}
allowCurrencyChange={false}
options={CurrencyDropdownOptions}
selected={currencyCode}
widgetId={widgetId}
/>
);
additionalProps.shouldUseLocale = true;
additionalProps.decimals = decimals;
}
editor = (
<InlineCellEditor
accentColor={accentColor}
additionalProps={additionalProps}
allowCellWrapping={allowCellWrapping}
autoFocus={!isNewRow}
compactMode={compactMode}
inputType={
columnType === ColumnTypes.NUMBER
? InputTypes.NUMBER
: InputTypes.TEXT
}
inputHTMLType={inputHTMLType}
inputType={inputType}
isEditableCellValid={isEditableCellValid}
multiline={isMultiline}
onChange={editEvents.onChange}
@ -230,7 +315,7 @@ function PlainTextCell(props: RenderDefaultPropsType & editPropertyType) {
textColor={textColor}
textSize={textSize}
url={columnType === ColumnTypes.URL ? props.value : null}
value={value}
value={formattedValue}
verticalAlignment={verticalAlignment}
/>
{editor}

View File

@ -142,6 +142,7 @@ export enum ColumnTypes {
EDIT_ACTIONS = "editActions",
CHECKBOX = "checkbox",
SWITCH = "switch",
CURRENCY = "currency",
}
export enum ReadOnlyColumnTypes {

View File

@ -247,8 +247,10 @@ export default {
Object.values(existingColumns).forEach((column) => {
/* guard to not allow columns without id */
if (column.id) {
column.isAscOrder = column.id === sortByColumn ? isAscOrder : undefined;
columns.push(column);
columns.push({
...column,
isAscOrder: column.id === sortByColumn ? isAscOrder : undefined,
});
}
});
@ -348,6 +350,7 @@ export default {
} else {
switch (columnType) {
case "number":
case "currency":
return sortByOrder(
Number(a[sortByColumnOriginalId]) >
Number(b[sortByColumnOriginalId]),
@ -771,7 +774,7 @@ export default {
};
let editableColumns = [];
const validatableColumns = ["text", "number"];
const validatableColumns = ["text", "number", "currency"];
if (props.isAddRowInProgress) {
Object.values(props.primaryColumns)
@ -822,6 +825,7 @@ export default {
/* Column type related validations */
switch (editedColumn.columnType) {
case "number":
case "currency":
if (
!_.isNil(validation.min) &&
validation.min !== "" &&

View File

@ -2483,6 +2483,8 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
cellBackground={cellProperties.cellBackground}
columnType={column.columnType}
compactMode={compactMode}
currencyCode={cellProperties.currencyCode}
decimals={cellProperties.decimals}
disabledEditIcon={
shouldDisableEdit || this.props.isAddRowInProgress
}
@ -2498,12 +2500,14 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
isEditableCellValid={this.isColumnCellValid(alias)}
isHidden={isHidden}
isNewRow={isNewRow}
notation={cellProperties.notation}
onCellTextChange={this.onCellTextChange}
onSubmitString={props.cell.column.columnProperties.onSubmit}
rowIndex={rowIndex}
tableWidth={this.props.componentWidth}
textColor={cellProperties.textColor}
textSize={cellProperties.textSize}
thousandSeparator={cellProperties.thousandSeparator}
toggleCellEditMode={this.toggleCellEditMode}
validationErrorMessage={validationErrorMessage}
value={props.cell.value}

View File

@ -53,6 +53,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,
@ -98,6 +99,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,

View File

@ -100,6 +100,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
]);
},

View File

@ -7,12 +7,14 @@ import {
hideByColumnType,
showByColumnType,
uniqueColumnAliasValidation,
updateCurrencyDefaultValues,
updateMenuItemsSource,
updateNumberColumnTypeTextAlignment,
updateThemeStylesheetsInColumns,
} from "../../propertyUtils";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
import { composePropertyUpdateHook } from "widgets/WidgetUtils";
import { CurrencyDropdownOptions } from "widgets/CurrencyInputWidget/component/CurrencyCodeDropdown";
export default {
sectionName: "Data",
@ -32,6 +34,10 @@ export default {
label: "Checkbox",
value: ColumnTypes.CHECKBOX,
},
{
label: "Currency",
value: ColumnTypes.CURRENCY,
},
{
label: "Date",
value: ColumnTypes.DATE,
@ -77,6 +83,7 @@ export default {
updateNumberColumnTypeTextAlignment,
updateThemeStylesheetsInColumns,
updateMenuItemsSource,
updateCurrencyDefaultValues,
]),
dependencies: ["primaryColumns", "columnOrder", "childStylesheet"],
isBindProperty: false,
@ -108,6 +115,7 @@ export default {
ColumnTypes.DATE,
ColumnTypes.IMAGE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.TEXT,
ColumnTypes.VIDEO,
ColumnTypes.URL,
@ -163,6 +171,7 @@ export default {
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,
ColumnTypes.SELECT,
ColumnTypes.CURRENCY,
]);
},
dependencies: ["primaryColumns", "columnOrder"],
@ -429,5 +438,116 @@ export default {
},
isTriggerProperty: false,
},
{
helpText: "Changes the type of currency",
propertyName: "currencyCode",
label: "Currency",
enableSearch: true,
dropdownHeight: "156px",
controlType: "DROP_DOWN",
searchPlaceholderText: "Search by code or name",
options: CurrencyDropdownOptions,
virtual: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
default: "USD",
required: true,
allowedValues: CurrencyDropdownOptions.map((option) => option.value),
},
},
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
const columnType = get(props, `${baseProperty}.columnType`, "");
return columnType !== ColumnTypes.CURRENCY;
},
dependencies: ["primaryColumns", "columnType"],
},
{
helpText: "No. of decimals in currency input",
propertyName: "decimals",
label: "Decimals allowed",
controlType: "DROP_DOWN",
options: [
{
label: "0",
value: 0,
},
{
label: "1",
value: 1,
},
{
label: "2",
value: 2,
},
],
isJSConvertible: false,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.NUMBER,
params: {
min: 0,
max: 2,
default: 0,
required: true,
},
},
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
const columnType = get(props, `${baseProperty}.columnType`, "");
return columnType !== ColumnTypes.CURRENCY;
},
dependencies: ["primaryColumns", "columnType"],
},
{
propertyName: "thousandSeparator",
helpText: "formats the currency with a thousand separator",
label: "Thousand separator",
controlType: "SWITCH",
dependencies: ["primaryColumns", "columnType"],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
const columnType = get(props, `${baseProperty}.columnType`, "");
return columnType !== ColumnTypes.CURRENCY;
},
},
{
propertyName: "notation",
helpText: "Displays the currency in standard or compact notation",
label: "Notation",
controlType: "DROP_DOWN",
options: [
{
label: "Standard",
value: "standard",
},
{
label: "Compact",
value: "compact",
},
],
dependencies: ["primaryColumns", "columnType"],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: { default: "standard", allowedValues: ["standard", "compact"] },
},
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
const columnType = get(props, `${baseProperty}.columnType`, "");
return columnType !== ColumnTypes.CURRENCY;
},
},
],
};

View File

@ -20,6 +20,7 @@ export default {
!(
columnType === ColumnTypes.TEXT ||
columnType === ColumnTypes.NUMBER ||
columnType === ColumnTypes.CURRENCY ||
columnType === ColumnTypes.CHECKBOX ||
columnType === ColumnTypes.SWITCH ||
columnType === ColumnTypes.SELECT ||
@ -56,7 +57,9 @@ export default {
const isEditable = get(props, `${baseProperty}.isEditable`, "");
return (
!(
columnType === ColumnTypes.TEXT || columnType === ColumnTypes.NUMBER
columnType === ColumnTypes.TEXT ||
columnType === ColumnTypes.NUMBER ||
columnType === ColumnTypes.CURRENCY
) || !isEditable
);
},

View File

@ -57,6 +57,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
]);
},
@ -96,6 +97,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
]);
},
@ -140,6 +142,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,
@ -186,6 +189,7 @@ export default {
ColumnTypes.TEXT,
ColumnTypes.DATE,
ColumnTypes.NUMBER,
ColumnTypes.CURRENCY,
ColumnTypes.URL,
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,

View File

@ -16,7 +16,12 @@ export default {
hideByColumnType(
props,
propertyPath,
[ColumnTypes.TEXT, ColumnTypes.NUMBER, ColumnTypes.DATE],
[
ColumnTypes.TEXT,
ColumnTypes.NUMBER,
ColumnTypes.DATE,
ColumnTypes.CURRENCY,
],
true,
)
);

View File

@ -21,7 +21,12 @@ export default [
},
hidden: (props: TableWidgetProps, propertyPath: string) => {
const path = getColumnPath(propertyPath);
return hideByColumnType(props, path, [ColumnTypes.NUMBER], true);
return hideByColumnType(
props,
path,
[ColumnTypes.NUMBER, ColumnTypes.CURRENCY],
true,
);
},
dependencies: ["primaryColumns"],
},
@ -39,7 +44,12 @@ export default [
},
hidden: (props: TableWidgetProps, propertyPath: string) => {
const path = getColumnPath(propertyPath);
return hideByColumnType(props, path, [ColumnTypes.NUMBER], true);
return hideByColumnType(
props,
path,
[ColumnTypes.NUMBER, ColumnTypes.CURRENCY],
true,
);
},
dependencies: ["primaryColumns"],
},

View File

@ -774,6 +774,50 @@ export const updateMenuItemsSource = (
return propertiesToUpdate?.length ? propertiesToUpdate : undefined;
};
export const updateCurrencyDefaultValues = (
props: TableWidgetProps,
propertyPath: string,
propertyValue: unknown,
): Array<{ propertyPath: string; propertyValue: unknown }> | undefined => {
const propertiesToUpdate: Array<{
propertyPath: string;
propertyValue: unknown;
}> = [];
const baseProperty = getBasePropertyPath(propertyPath);
if (propertyValue === ColumnTypes.CURRENCY) {
if (!get(props, `${baseProperty}.currencyCode`)) {
propertiesToUpdate.push({
propertyPath: `${baseProperty}.currencyCode`,
propertyValue: "USD",
});
}
if (get(props, `${baseProperty}.decimals`) === undefined) {
propertiesToUpdate.push({
propertyPath: `${baseProperty}.decimals`,
propertyValue: 0,
});
}
if (get(props, `${baseProperty}.notation`) === undefined) {
propertiesToUpdate.push({
propertyPath: `${baseProperty}.notation`,
propertyValue: "standard",
});
}
if (get(props, `${baseProperty}.thousandSeparator`) === undefined) {
propertiesToUpdate.push({
propertyPath: `${baseProperty}.thousandSeparator`,
propertyValue: true,
});
}
}
return propertiesToUpdate?.length ? propertiesToUpdate : undefined;
};
export function selectColumnOptionsValidation(
value: unknown,
props: TableWidgetProps,

View File

@ -56,6 +56,7 @@ export const getColumnsPureFn: getColumns = (
type: column.columnType,
format: column.outputFormat || "",
inputFormat: column.inputFormat || "",
decimals: column.decimals || 0,
},
columnProperties: column,
Cell: renderCell,

View File

@ -2,6 +2,7 @@ import type { ColumnProperties, TableStyles } from "../component/Constants";
import { StickyType } from "../component/Constants";
import { ColumnTypes } from "../constants";
import {
convertNumToCompactString,
escapeString,
generateNewColumnOrderFromStickyValue,
getAllTableColumnKeys,
@ -2671,3 +2672,22 @@ describe("getSelectOptions", () => {
]);
});
});
describe("convertNumToCompactString", () => {
it("formats a number in thousands (K)", () => {
expect(convertNumToCompactString(5000)).toBe("5.0K");
expect(convertNumToCompactString(9999)).toBe("10.0K"); // Rounding
expect(convertNumToCompactString(123456)).toBe("123.5K"); // Rounding with precision
});
it("formats a number in millions (M)", () => {
expect(convertNumToCompactString(1500000)).toBe("1.5M");
expect(convertNumToCompactString(9999999)).toBe("10.0M"); // Rounding
expect(convertNumToCompactString(123456789)).toBe("123.5M"); // Rounding with precision
});
it("formats a number less than 1000 as is", () => {
expect(convertNumToCompactString(42)).toBe("42");
expect(convertNumToCompactString(999)).toBe("999");
});
});

View File

@ -221,6 +221,10 @@ export function getDefaultColumnProperties(
)}"]))}}`,
sticky: StickyType.NONE,
validation: {},
currencyCode: "USD",
decimals: 0,
thousandSeparator: true,
notation: "standard" as Intl.NumberFormatOptions["notation"],
};
return columnProps;
@ -508,6 +512,14 @@ export const getCellProperties = (
rowIndex,
true,
),
currencyCode: getPropertyValue(
columnProperties.currencyCode,
rowIndex,
true,
),
decimals: columnProperties.decimals,
thousandSeparator: !!columnProperties.thousandSeparator,
notation: columnProperties.notation,
} as CellLayoutProperties;
}
return {} as CellLayoutProperties;
@ -520,6 +532,7 @@ const EdtiableColumnTypes: string[] = [
ColumnTypes.CHECKBOX,
ColumnTypes.SWITCH,
ColumnTypes.DATE,
ColumnTypes.CURRENCY,
];
export function isColumnTypeEditable(columnType: string) {
@ -1102,3 +1115,13 @@ export const getSelectOptions = (
return getArrayPropertyValue(columnProperties.selectOptions, rowIndex);
}
};
export function convertNumToCompactString(num: number) {
if (num >= 1e6) {
return (num / 1e6).toFixed(1) + "M";
} else if (num >= 1e3) {
return (num / 1e3).toFixed(1) + "K";
} else {
return num.toString();
}
}

View File

@ -1,6 +1,6 @@
import { transformTableDataIntoCsv } from "./Utilities";
import type { TableColumnProps } from "../../Constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
describe("TransformTableDataIntoArrayOfArray", () => {
const columns: TableColumnProps[] = [

View File

@ -1,7 +1,7 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { hideByColumnType } from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Alignment",

View File

@ -1,6 +1,6 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes, ICON_NAMES } from "widgets/TableWidgetV2/constants";
import { ICON_NAMES } from "widgets/TableWidgetV2/constants";
import {
hideByColumnType,
hideByMenuItemsSource,
@ -12,6 +12,7 @@ import { IconNames } from "@blueprintjs/icons";
import { MenuItemsSource } from "widgets/MenuButtonWidget/constants";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import configureMenuItemsConfig from "./childPanels/configureMenuItemsConfig";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Basic",

View File

@ -1,10 +1,10 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import {
hideByColumnType,
removeBoxShadowColorProp,
} from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Border and shadow",

View File

@ -1,7 +1,7 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { hideByColumnType } from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Color",

View File

@ -1,6 +1,6 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes, DateInputFormat } from "widgets/TableWidgetV2/constants";
import { DateInputFormat } from "widgets/TableWidgetV2/constants";
import { get } from "lodash";
import {
getBasePropertyPath,
@ -13,6 +13,7 @@ import {
} from "../../propertyUtils";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
import { composePropertyUpdateHook } from "widgets/WidgetUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Data",

View File

@ -1,10 +1,10 @@
import { get } from "lodash";
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { hideByColumnType, getBasePropertyPath } from "../../propertyUtils";
import { ButtonVariantTypes } from "components/constants";
import { ICON_NAMES } from "WidgetProvider/constants";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Discard Button",

View File

@ -1,5 +1,4 @@
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { get } from "lodash";
import {
getBasePropertyPath,
@ -7,6 +6,7 @@ import {
hideByColumnType,
getColumnPath,
} from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Events",

View File

@ -1,6 +1,5 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { get } from "lodash";
import {
getBasePropertyPath,
@ -13,6 +12,7 @@ import { isColumnTypeEditable } from "../../utilities";
import { composePropertyUpdateHook } from "widgets/WidgetUtils";
import { ButtonVariantTypes } from "components/constants";
import { StickyType } from "widgets/TableWidgetV2/component/Constants";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "General",

View File

@ -1,7 +1,8 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes, ICON_NAMES } from "widgets/TableWidgetV2/constants";
import { ICON_NAMES } from "widgets/TableWidgetV2/constants";
import { hideByColumnType, updateIconAlignment } from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Icon",

View File

@ -1,10 +1,10 @@
import { get } from "lodash";
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { hideByColumnType, getBasePropertyPath } from "../../propertyUtils";
import { ButtonVariantTypes } from "components/constants";
import { ICON_NAMES } from "WidgetProvider/constants";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Save Button",

View File

@ -1,12 +1,12 @@
import { ValidationTypes } from "constants/WidgetValidation";
import { get } from "lodash";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import {
getBasePropertyPath,
hideByColumnType,
selectColumnOptionsValidation,
} from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Select properties",

View File

@ -1,7 +1,7 @@
import { ValidationTypes } from "constants/WidgetValidation";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { hideByColumnType, showByColumnType } from "../../propertyUtils";
import { ColumnTypes } from "widgets/wds/WDSTableWidget/constants";
export default {
sectionName: "Text formatting",

View File

@ -1,10 +1,10 @@
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import { get } from "lodash";
import { hideByColumnType } from "../../propertyUtils";
import commonValidations from "./Validations/Common";
import numberTypeValidations from "./Validations/Number";
import dateTypeValidations from "./Validations/Date";
import { ColumnTypes } from "../../../constants";
export default {
sectionName: "Validation",