From 15b8b9b158bd625db7eb1cb9e48dfe2ceb6512a4 Mon Sep 17 00:00:00 2001 From: Guillim Date: Thu, 14 Nov 2024 14:23:25 +0100 Subject: [PATCH] Feature : Adding percentage option to Input Number (#8481) fixing #7375 --------- Co-authored-by: guillim --- ...ormatFieldMetadataItemAsFieldDefinition.ts | 1 - .../components/RecordBoardCard.tsx | 1 - .../display/components/NumberFieldDisplay.tsx | 16 ++++-- .../meta-types/hooks/useNumberField.ts | 16 +++++- .../record-field/types/FieldDefinition.ts | 3 - .../record-field/types/FieldMetadata.ts | 26 +++++++++ .../utils/computeDraftValueFromFieldValue.ts | 16 +++++- .../numberFieldDefaultValueSchema.ts | 1 + .../hooks/__tests__/useRecordData.test.tsx | 3 - .../SettingsDataModelFieldNumberForm.tsx | 47 +++++++++++++-- .../SettingsObjectFieldDataType.tsx | 9 ++- .../SettingsObjectFieldItemTableRow.tsx | 4 +- .../display/components/NumberDisplay.tsx | 8 +-- .../utils/mapViewFieldsToColumnDefinitions.ts | 2 +- .../field-metadata-validation.service.ts | 57 ++++++++++++++----- .../field-metadata/field-metadata.service.ts | 2 +- .../field-metadata-settings.interface.ts | 1 + .../display/icon/components/TablerIcons.ts | 2 + 18 files changed, 167 insertions(+), 48 deletions(-) diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts index f372cd2eb3ac..8ba9ebe23315 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts @@ -54,6 +54,5 @@ export const formatFieldMetadataItemAsFieldDefinition = ({ metadata: fieldDefintionMetadata, type: field.type, }), - settings: field.settings, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 66dbd0695553..92d897a614ae 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -361,7 +361,6 @@ export const RecordBoardCard = ({ metadata: fieldDefinition.metadata, type: fieldDefinition.type, }), - settings: fieldDefinition.settings, }, useUpdateRecord: useUpdateOneRecordHook, hotkeyScope: InlineCellHotkeyScope.InlineCell, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx index cb30dbed3776..da254a440736 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx @@ -1,12 +1,16 @@ import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useNumberFieldDisplay'; import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay'; +import { formatNumber } from '~/utils/format/number'; export const NumberFieldDisplay = () => { const { fieldValue, fieldDefinition } = useNumberFieldDisplay(); - return ( - - ); + const decimals = fieldDefinition.metadata.settings?.decimals; + const type = fieldDefinition.metadata.settings?.type; + const value = + type === 'percentage' && fieldValue + ? `${formatNumber(Number(fieldValue) * 100, decimals)}%` + : fieldValue + ? formatNumber(Number(fieldValue), decimals) + : null; + return ; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts index 097bcb8beef5..4b27ad3ae9c9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts @@ -11,6 +11,7 @@ import { castAsNumberOrNull, } from '~/utils/cast-as-number-or-null'; +import { isNull } from '@sniptt/guards'; import { FieldContext } from '../../contexts/FieldContext'; import { usePersistField } from '../../hooks/usePersistField'; import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; @@ -33,12 +34,23 @@ export const useNumberField = () => { const persistField = usePersistField(); const persistNumberField = (newValue: string) => { + if (fieldDefinition?.metadata?.settings?.type === 'percentage') { + newValue = newValue.replaceAll('%', ''); + if (!canBeCastAsNumberOrNull(newValue)) { + return; + } + const castedValue = castAsNumberOrNull(newValue); + if (!isNull(castedValue)) { + persistField(castedValue / 100); + return; + } + persistField(null); + return; + } if (!canBeCastAsNumberOrNull(newValue)) { return; } - const castedValue = castAsNumberOrNull(newValue); - persistField(castedValue); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts index 1d8107c17953..16f05f2418d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts @@ -16,7 +16,4 @@ export type FieldDefinition = { infoTooltipContent?: string; defaultValue?: any; editButtonIcon?: IconComponent; - settings?: { - decimals?: number; - }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts index 434d168e7e11..53e037dbdaf6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts @@ -10,17 +10,20 @@ import { CurrencyCode } from './CurrencyCode'; export type FieldUuidMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldBooleanMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldTextMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldDateTimeMetadata = { @@ -46,17 +49,23 @@ export type FieldNumberMetadata = { fieldName: string; placeHolder: string; isPositive?: boolean; + settings?: { + decimals?: number; + type?: 'percentage' | 'number'; + }; }; export type FieldLinkMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldLinksMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldCurrencyMetadata = { @@ -64,56 +73,66 @@ export type FieldCurrencyMetadata = { fieldName: string; placeHolder: string; isPositive?: boolean; + settings?: Record; }; export type FieldFullNameMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldEmailMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldEmailsMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldPhoneMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldRatingMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldAddressMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: Record; }; export type FieldRawJsonMetadata = { objectMetadataNameSingular?: string; fieldName: string; placeHolder: string; + settings?: Record; }; export type FieldRichTextMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldPositionMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldRelationMetadata = { @@ -125,6 +144,7 @@ export type FieldRelationMetadata = { relationType?: RelationDefinitionType; targetFieldMetadataName?: string; useEditButton?: boolean; + settings?: Record; }; export type FieldSelectMetadata = { @@ -132,33 +152,39 @@ export type FieldSelectMetadata = { fieldName: string; options: { label: string; color: ThemeColor; value: string }[]; isNullable: boolean; + settings?: Record; }; export type FieldMultiSelectMetadata = { objectMetadataNameSingular?: string; fieldName: string; options: { label: string; color: ThemeColor; value: string }[]; + settings?: Record; }; export type FieldActorMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldArrayMetadata = { objectMetadataNameSingular?: string; fieldName: string; values: { label: string; value: string }[]; + settings?: Record; }; export type FieldPhonesMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldTsVectorMetadata = { objectMetadataNameSingular?: string; fieldName: string; + settings?: Record; }; export type FieldMetadata = diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index 298ef3c9a4a9..47a404873b14 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -3,6 +3,8 @@ import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldIn import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; +import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; +import { isFieldNumberValue } from '@/object-record/record-field/types/guards/isFieldNumberValue'; import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; @@ -12,7 +14,7 @@ import { isDefined } from '~/utils/isDefined'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type computeDraftValueFromFieldValueParams = { - fieldDefinition: Pick, 'type'>; + fieldDefinition: Pick, 'type' | 'metadata'>; fieldValue: FieldValue; }; @@ -40,6 +42,18 @@ export const computeDraftValueFromFieldValue = ({ } as unknown as FieldInputDraftValue; } + if ( + isFieldNumber(fieldDefinition) && + isFieldNumberValue(fieldValue) && + fieldDefinition.metadata.settings?.type === 'percentage' + ) { + return (isUndefinedOrNull(fieldValue) + ? '' + : ( + fieldValue * 100 + ).toString()) as unknown as FieldInputDraftValue; + } + if (isFieldRelation(fieldDefinition)) { return computeEmptyDraftValue({ fieldDefinition }); } diff --git a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts index 48981e4a5a34..a08ff263b37c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts @@ -2,4 +2,5 @@ import { z } from 'zod'; export const numberFieldDefaultValueSchema = z.object({ decimals: z.number().nullable(), + type: z.enum(['percentage', 'number']).nullable(), }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useRecordData.test.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useRecordData.test.tsx index 4190d08b1a67..8ea445ef16b3 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useRecordData.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useRecordData.test.tsx @@ -304,9 +304,6 @@ describe('useRecordData', () => { }, }, position: expect.any(Number), - settings: { - displayAsRelativeDate: true, - }, showLabel: undefined, size: 100, type: 'DATE_TIME', diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx index ec5686ff3eff..139be3993f3c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx @@ -4,7 +4,9 @@ import { z } from 'zod'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema'; import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput'; -import { CardContent } from 'twenty-ui'; +import { Select } from '@/ui/input/components/Select'; +import styled from '@emotion/styled'; +import { CardContent, IconNumber9, IconPercentage } from 'twenty-ui'; import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number'; export const settingsDataModelFieldNumberFormSchema = z.object({ @@ -15,6 +17,13 @@ export type SettingsDataModelFieldNumberFormValues = z.infer< typeof settingsDataModelFieldNumberFormSchema >; +const StyledFormCardTitle = styled.div` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + type SettingsDataModelFieldNumberFormProps = { disabled?: boolean; fieldMetadataItem: Pick< @@ -36,17 +45,43 @@ export const SettingsDataModelFieldNumberForm = ({ defaultValue={{ decimals: fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE, + type: fieldMetadataItem?.settings?.type || 'number', }} control={control} render={({ field: { onChange, value } }) => { const count = value?.decimals ?? 0; + const type = value?.type ?? 'number'; return ( - onChange({ decimals: value })} - disabled={disabled} - > + <> + Type +