feat: add short number formatting option to number field (#12613)
resolve #11927 Add a new 'Short Number' option that disables decimals and resets the value to 0 when selected. https://github.com/user-attachments/assets/d3524115-e3ec-4a07-9dbf-e19d03cf65dd https://github.com/user-attachments/assets/2f2b46d1-06d9-4a92-8f37-0291d46accab --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useNumberFieldDisplay';
|
import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useNumberFieldDisplay';
|
||||||
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';
|
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { formatAmount } from '~/utils/format/formatAmount';
|
||||||
import { formatNumber } from '~/utils/format/number';
|
import { formatNumber } from '~/utils/format/number';
|
||||||
|
|
||||||
export const NumberFieldDisplay = () => {
|
export const NumberFieldDisplay = () => {
|
||||||
@ -13,7 +14,9 @@ export const NumberFieldDisplay = () => {
|
|||||||
const value =
|
const value =
|
||||||
type === 'percentage'
|
type === 'percentage'
|
||||||
? `${formatNumber(Number(fieldValue) * 100, decimals)}%`
|
? `${formatNumber(Number(fieldValue) * 100, decimals)}%`
|
||||||
: formatNumber(Number(fieldValue), decimals);
|
: type === 'shortNumber'
|
||||||
|
? formatAmount(Number(fieldValue))
|
||||||
|
: formatNumber(Number(fieldValue), decimals);
|
||||||
|
|
||||||
return <NumberDisplay value={value} decimals={decimals} />;
|
return <NumberDisplay value={value} decimals={decimals} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,7 +56,12 @@ export type FieldDateMetadata = BaseFieldMetadata & {
|
|||||||
settings?: FieldDateMetadataSettings;
|
settings?: FieldDateMetadataSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldNumberVariant = 'number' | 'percentage';
|
export const FIELD_NUMBER_VARIANT = [
|
||||||
|
'number',
|
||||||
|
'percentage',
|
||||||
|
'shortNumber',
|
||||||
|
] as const;
|
||||||
|
export type FieldNumberVariant = (typeof FIELD_NUMBER_VARIANT)[number];
|
||||||
|
|
||||||
export type FieldNumberMetadata = BaseFieldMetadata & {
|
export type FieldNumberMetadata = BaseFieldMetadata & {
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
import { FIELD_NUMBER_VARIANT } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const numberFieldDefaultValueSchema = z.object({
|
export const numberFieldDefaultValueSchema = z.object({
|
||||||
decimals: z.number().nullable(),
|
decimals: z.number().nullable(),
|
||||||
type: z.enum(['percentage', 'number']).nullable(),
|
type: z.enum(FIELD_NUMBER_VARIANT).nullable(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import { numberFieldDefaultValueSchema } from '@/object-record/record-field/vali
|
|||||||
import { Separator } from '@/settings/components/Separator';
|
import { Separator } from '@/settings/components/Separator';
|
||||||
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
|
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
|
||||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||||
|
import { NUMBER_DATA_MODEL_SELECT_OPTIONS } from '@/settings/data-model/fields/forms/number/constants/NumberDataModelSelectOptions';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { IconDecimal, IconEye } from 'twenty-ui/display';
|
import { IconDecimal, IconEye } from 'twenty-ui/display';
|
||||||
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';
|
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number';
|
||||||
import { NUMBER_DATA_MODEL_SELECT_OPTIONS } from '@/settings/data-model/fields/forms/number/constants/NumberDataModelSelectOptions';
|
|
||||||
|
|
||||||
export const settingsDataModelFieldNumberFormSchema = z.object({
|
export const settingsDataModelFieldNumberFormSchema = z.object({
|
||||||
settings: numberFieldDefaultValueSchema,
|
settings: numberFieldDefaultValueSchema,
|
||||||
@ -60,7 +60,13 @@ export const SettingsDataModelFieldNumberForm = ({
|
|||||||
dropdownId="number-type"
|
dropdownId="number-type"
|
||||||
dropdownWidth={120}
|
dropdownWidth={120}
|
||||||
value={type}
|
value={type}
|
||||||
onChange={(value) => onChange({ type: value, decimals: count })}
|
onChange={(value) =>
|
||||||
|
onChange({
|
||||||
|
type: value,
|
||||||
|
decimals:
|
||||||
|
value === 'shortNumber' ? DEFAULT_DECIMAL_VALUE : count,
|
||||||
|
})
|
||||||
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
needIconCheck={false}
|
needIconCheck={false}
|
||||||
options={NUMBER_DATA_MODEL_SELECT_OPTIONS.map((option) => ({
|
options={NUMBER_DATA_MODEL_SELECT_OPTIONS.map((option) => ({
|
||||||
@ -70,16 +76,18 @@ export const SettingsDataModelFieldNumberForm = ({
|
|||||||
/>
|
/>
|
||||||
</SettingsOptionCardContentSelect>
|
</SettingsOptionCardContentSelect>
|
||||||
<Separator />
|
<Separator />
|
||||||
<SettingsOptionCardContentCounter
|
{type !== 'shortNumber' && (
|
||||||
Icon={IconDecimal}
|
<SettingsOptionCardContentCounter
|
||||||
title={t`Number of decimals`}
|
Icon={IconDecimal}
|
||||||
description={`E.g. ${(type === 'percentage' ? 99 : 1000).toFixed(count)}${type === 'percentage' ? '%' : ''} for ${count} decimal${count > 1 ? 's' : ''}`}
|
title={t`Number of decimals`}
|
||||||
value={count}
|
description={`E.g. ${(type === 'percentage' ? 99 : 1000).toFixed(count)}${type === 'percentage' ? '%' : ''} for ${count} decimal${count > 1 ? 's' : ''}`}
|
||||||
onChange={(value) => onChange({ type: type, decimals: value })}
|
value={count}
|
||||||
disabled={disabled}
|
onChange={(value) => onChange({ type: type, decimals: value })}
|
||||||
minValue={0}
|
disabled={disabled}
|
||||||
maxValue={100} // needs to be changed
|
minValue={0}
|
||||||
/>
|
maxValue={100} // needs to be changed
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,15 +1,36 @@
|
|||||||
|
import { FieldNumberVariant } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { IconNumber9, IconPercentage } from 'twenty-ui/display';
|
import { ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||||
|
import {
|
||||||
|
IconComponent,
|
||||||
|
IconComponentProps,
|
||||||
|
IconLetterK,
|
||||||
|
IconNumber9,
|
||||||
|
IconPercentage,
|
||||||
|
} from 'twenty-ui/display';
|
||||||
|
|
||||||
|
type NumberDataModelSelectOptions = {
|
||||||
|
Icon: ForwardRefExoticComponent<
|
||||||
|
IconComponentProps & RefAttributes<IconComponent>
|
||||||
|
>;
|
||||||
|
label: MessageDescriptor;
|
||||||
|
value: FieldNumberVariant;
|
||||||
|
};
|
||||||
export const NUMBER_DATA_MODEL_SELECT_OPTIONS = [
|
export const NUMBER_DATA_MODEL_SELECT_OPTIONS = [
|
||||||
{
|
{
|
||||||
Icon: IconNumber9,
|
Icon: IconNumber9,
|
||||||
label: msg`Number`,
|
label: msg`Number`,
|
||||||
value: 'number',
|
value: 'number',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Icon: IconLetterK,
|
||||||
|
label: msg`Short`,
|
||||||
|
value: 'shortNumber',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Icon: IconPercentage,
|
Icon: IconPercentage,
|
||||||
label: msg`Percentage`,
|
label: msg`Percentage`,
|
||||||
value: 'percentage',
|
value: 'percentage',
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies Array<NumberDataModelSelectOptions>;
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
enum ValueType {
|
enum ValueType {
|
||||||
PERCENTAGE = 'percentage',
|
PERCENTAGE = 'percentage',
|
||||||
NUMBER = 'number',
|
NUMBER = 'number',
|
||||||
|
SHORT_NUMBER = 'shortNumber',
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberSettingsValidation {
|
class NumberSettingsValidation {
|
||||||
@ -35,7 +36,7 @@ class NumberSettingsValidation {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(ValueType)
|
@IsEnum(ValueType)
|
||||||
type?: 'percentage' | 'number';
|
type?: 'percentage' | 'number' | 'shortNumber';
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextSettingsValidation {
|
class TextSettingsValidation {
|
||||||
|
|||||||
@ -4,18 +4,18 @@ export {
|
|||||||
IconAlertCircle,
|
IconAlertCircle,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconApi,
|
IconApi,
|
||||||
IconApps,
|
|
||||||
IconAppWindow,
|
IconAppWindow,
|
||||||
|
IconApps,
|
||||||
IconArchive,
|
IconArchive,
|
||||||
IconArchiveOff,
|
IconArchiveOff,
|
||||||
IconArrowBackUp,
|
IconArrowBackUp,
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowsDiagonal,
|
|
||||||
IconArrowsVertical,
|
|
||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconArrowUpRight,
|
IconArrowUpRight,
|
||||||
|
IconArrowsDiagonal,
|
||||||
|
IconArrowsVertical,
|
||||||
IconAt,
|
IconAt,
|
||||||
IconBaselineDensitySmall,
|
IconBaselineDensitySmall,
|
||||||
IconBell,
|
IconBell,
|
||||||
@ -47,8 +47,8 @@ export {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconChevronsRight,
|
|
||||||
IconChevronUp,
|
IconChevronUp,
|
||||||
|
IconChevronsRight,
|
||||||
IconCircleDot,
|
IconCircleDot,
|
||||||
IconCircleOff,
|
IconCircleOff,
|
||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
@ -186,6 +186,7 @@ export {
|
|||||||
IconLayoutSidebarRight,
|
IconLayoutSidebarRight,
|
||||||
IconLayoutSidebarRightCollapse,
|
IconLayoutSidebarRightCollapse,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
|
IconLetterK,
|
||||||
IconLibraryPlus,
|
IconLibraryPlus,
|
||||||
IconLifebuoy,
|
IconLifebuoy,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
|||||||
@ -65,18 +65,18 @@ export {
|
|||||||
IconAlertCircle,
|
IconAlertCircle,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconApi,
|
IconApi,
|
||||||
IconApps,
|
|
||||||
IconAppWindow,
|
IconAppWindow,
|
||||||
|
IconApps,
|
||||||
IconArchive,
|
IconArchive,
|
||||||
IconArchiveOff,
|
IconArchiveOff,
|
||||||
IconArrowBackUp,
|
IconArrowBackUp,
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowsDiagonal,
|
|
||||||
IconArrowsVertical,
|
|
||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconArrowUpRight,
|
IconArrowUpRight,
|
||||||
|
IconArrowsDiagonal,
|
||||||
|
IconArrowsVertical,
|
||||||
IconAt,
|
IconAt,
|
||||||
IconBaselineDensitySmall,
|
IconBaselineDensitySmall,
|
||||||
IconBell,
|
IconBell,
|
||||||
@ -108,8 +108,8 @@ export {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconChevronsRight,
|
|
||||||
IconChevronUp,
|
IconChevronUp,
|
||||||
|
IconChevronsRight,
|
||||||
IconCircleDot,
|
IconCircleDot,
|
||||||
IconCircleOff,
|
IconCircleOff,
|
||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
@ -247,6 +247,7 @@ export {
|
|||||||
IconLayoutSidebarRight,
|
IconLayoutSidebarRight,
|
||||||
IconLayoutSidebarRightCollapse,
|
IconLayoutSidebarRightCollapse,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
|
IconLetterK,
|
||||||
IconLibraryPlus,
|
IconLibraryPlus,
|
||||||
IconLifebuoy,
|
IconLifebuoy,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
|||||||
Reference in New Issue
Block a user