From 721173057020cfac8d46cc2ffe99af416600a122 Mon Sep 17 00:00:00 2001 From: "gitstart-app[bot]" <57568882+gitstart-app[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:29:29 +0200 Subject: [PATCH] New field type: DATE (#4876) ### Description New field type: DATE ### Refs https://github.com/twentyhq/twenty/issues/4377 ### Demo https://jam.dev/c/d0b59883-593c-4ca3-966b-c12d5d2e1c32 Fixes #4377 --------- Co-authored-by: gitstart-twenty Co-authored-by: v1b3m Co-authored-by: Toledodev Co-authored-by: Lucas Bordeau --- .../src/generated/graphql.tsx | 1 + .../src/generated-metadata/graphql.ts | 1 + .../twenty-front/src/generated/graphql.tsx | 1 + ...atFieldMetadataItemsAsFilterDefinitions.ts | 2 + ...rmatFieldMetadataItemsAsSortDefinitions.ts | 1 + .../utils/mapFieldMetadataToGraphQLQuery.ts | 1 + .../types/FilterType.ts | 1 + .../utils/getOperandsForFilterType.ts | 1 + .../record-field/components/FieldDisplay.tsx | 4 + .../record-field/components/FieldInput.tsx | 10 +- .../record-field/hooks/usePersistField.ts | 6 ++ .../display/components/DateFieldDisplay.tsx | 4 +- .../components/DateTimeFieldDisplay.tsx | 9 ++ ...s.tsx => DateTimeFieldDisplay.stories.tsx} | 6 +- .../meta-types/hooks/useDateField.ts | 40 ++++++++ .../input/components/BooleanFieldInput.tsx | 2 +- .../input/components/CurrencyFieldInput.tsx | 2 +- .../input/components/DateFieldInput.tsx | 8 +- .../input/components/DateTimeFieldInput.tsx | 68 +++++++++++++ .../input/components/EmailFieldInput.tsx | 2 +- .../input/components/FullNameFieldInput.tsx | 2 +- .../input/components/LinkFieldInput.tsx | 2 +- .../input/components/PhoneFieldInput.tsx | 2 +- .../input/components/RatingFieldInput.tsx | 2 +- .../input/components/RelationFieldInput.tsx | 2 +- .../input/components/TextFieldInput.tsx | 2 +- ...ies.tsx => DateTimeFieldInput.stories.tsx} | 9 +- .../record-field/types/FieldMetadata.ts | 8 ++ .../types/guards/assertFieldMetadata.ts | 57 +++++------ .../record-field/types/guards/isFieldDate.ts | 6 ++ .../types/guards/isFieldDateValue.ts | 10 ++ .../record-field/utils/isFieldValueEmpty.ts | 2 + .../utils/generateEmptyFieldValue.ts | 3 + .../constants/SettingsFieldTypeConfigs.ts | 5 + ...SettingsDataModelFieldSettingsFormCard.tsx | 1 + .../ui/field/input/components/DateInput.tsx | 13 +-- .../date/components/InternalDatePicker.tsx | 95 ++++++++++++++++++- .../services/type-mapper.service.ts | 3 + ...p-field-metadata-to-graphql-query.utils.ts | 1 + .../open-api/utils/components.utils.ts | 1 + .../dtos/default-value.input.ts | 6 ++ .../field-metadata/field-metadata.entity.ts | 1 + .../field-metadata-default-value.interface.ts | 3 + .../validate-default-value-for-type.util.ts | 2 + .../factories/basic-column-action.factory.ts | 1 + ...field-metadata-type-to-column-type.util.ts | 2 + .../workspace-migration.factory.ts | 1 + .../src/utils/computeInputFields.ts | 3 + .../twenty-zapier/src/utils/data.types.ts | 1 + 49 files changed, 354 insertions(+), 62 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx rename packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/{DateFieldDisplay.stories.tsx => DateTimeFieldDisplay.stories.tsx} (91%) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateField.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateTimeFieldInput.tsx rename packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/{DateFieldInput.stories.tsx => DateTimeFieldInput.stories.tsx} (94%) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDate.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDateValue.ts diff --git a/packages/twenty-chrome-extension/src/generated/graphql.tsx b/packages/twenty-chrome-extension/src/generated/graphql.tsx index 95d45a4c2..f48e6ba5d 100644 --- a/packages/twenty-chrome-extension/src/generated/graphql.tsx +++ b/packages/twenty-chrome-extension/src/generated/graphql.tsx @@ -1391,6 +1391,7 @@ export type FieldDeleteResponse = { export enum FieldMetadataType { Boolean = 'BOOLEAN', Currency = 'CURRENCY', + Date = 'DATE', DateTime = 'DATE_TIME', Email = 'EMAIL', FullName = 'FULL_NAME', diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index e6ad38f71..3328bbdd7 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -284,6 +284,7 @@ export enum FieldMetadataType { Address = 'ADDRESS', Boolean = 'BOOLEAN', Currency = 'CURRENCY', + Date = 'DATE', DateTime = 'DATE_TIME', Email = 'EMAIL', FullName = 'FULL_NAME', diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 620eaf377..7b68aa5ef 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -194,6 +194,7 @@ export enum FieldMetadataType { Address = 'ADDRESS', Boolean = 'BOOLEAN', Currency = 'CURRENCY', + Date = 'DATE', DateTime = 'DATE_TIME', Email = 'EMAIL', FullName = 'FULL_NAME', diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 9b80562da..3915413cb 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -61,6 +61,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { switch (fieldType) { case FieldMetadataType.DateTime: return 'DATE_TIME'; + case FieldMetadataType.Date: + return 'DATE'; case FieldMetadataType.Link: return 'LINK'; case FieldMetadataType.FullName: diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts index 4e48dcfa9..5138491cd 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts @@ -12,6 +12,7 @@ export const formatFieldMetadataItemsAsSortDefinitions = ({ if ( ![ FieldMetadataType.DateTime, + FieldMetadataType.Date, FieldMetadataType.Number, FieldMetadataType.Text, FieldMetadataType.Boolean, diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts index 14e1bfcf8..172288fc9 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts @@ -31,6 +31,7 @@ export const mapFieldMetadataToGraphQLQuery = ({ 'TEXT', 'PHONE', 'DATE_TIME', + 'DATE', 'EMAIL', 'NUMBER', 'BOOLEAN', diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts index 650223884..7aa1bed31 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts @@ -3,6 +3,7 @@ export type FilterType = | 'PHONE' | 'EMAIL' | 'DATE_TIME' + | 'DATE' | 'NUMBER' | 'CURRENCY' | 'FULL_NAME' diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index 874acf3a5..741293e0d 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -15,6 +15,7 @@ export const getOperandsForFilterType = ( case 'CURRENCY': case 'NUMBER': case 'DATE_TIME': + case 'DATE': return [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan]; case 'RELATION': case 'SELECT': diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx index 2bc4da0b0..68999b4fc 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx @@ -5,6 +5,7 @@ import { AddressFieldDisplay } from '../meta-types/display/components/AddressFie import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay'; import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay'; import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay'; +import { DateTimeFieldDisplay } from '../meta-types/display/components/DateTimeFieldDisplay'; import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay'; import { FullNameFieldDisplay } from '../meta-types/display/components/FullNameFieldDisplay'; import { JsonFieldDisplay } from '../meta-types/display/components/JsonFieldDisplay'; @@ -18,6 +19,7 @@ import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisp import { UuidFieldDisplay } from '../meta-types/display/components/UuidFieldDisplay'; import { isFieldAddress } from '../types/guards/isFieldAddress'; import { isFieldCurrency } from '../types/guards/isFieldCurrency'; +import { isFieldDate } from '../types/guards/isFieldDate'; import { isFieldDateTime } from '../types/guards/isFieldDateTime'; import { isFieldEmail } from '../types/guards/isFieldEmail'; import { isFieldFullName } from '../types/guards/isFieldFullName'; @@ -53,6 +55,8 @@ export const FieldDisplay = () => { ) : isFieldEmail(fieldDefinition) ? ( ) : isFieldDateTime(fieldDefinition) ? ( + + ) : isFieldDate(fieldDefinition) ? ( ) : isFieldNumber(fieldDefinition) ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx index 97a957d4e..07f7cbe13 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx @@ -1,11 +1,13 @@ import { useContext } from 'react'; import { AddressFieldInput } from '@/object-record/record-field/meta-types/input/components/AddressFieldInput'; +import { DateFieldInput } from '@/object-record/record-field/meta-types/input/components/DateFieldInput'; import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput'; import { MultiSelectFieldInput } from '@/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx'; import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input/components/RawJsonFieldInput'; import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; +import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect.ts'; import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; @@ -15,7 +17,7 @@ import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/get import { FieldContext } from '../contexts/FieldContext'; import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput'; import { CurrencyFieldInput } from '../meta-types/input/components/CurrencyFieldInput'; -import { DateFieldInput } from '../meta-types/input/components/DateFieldInput'; +import { DateTimeFieldInput } from '../meta-types/input/components/DateTimeFieldInput'; import { EmailFieldInput } from '../meta-types/input/components/EmailFieldInput'; import { LinkFieldInput } from '../meta-types/input/components/LinkFieldInput'; import { NumberFieldInput } from '../meta-types/input/components/NumberFieldInput'; @@ -98,6 +100,12 @@ export const FieldInput = ({ onShiftTab={onShiftTab} /> ) : isFieldDateTime(fieldDefinition) ? ( + + ) : isFieldDate(fieldDefinition) ? ( { isFieldDateTime(fieldDefinition) && isFieldDateTimeValue(valueToPersist); + const fieldIsDate = + isFieldDate(fieldDefinition) && isFieldDateValue(valueToPersist); + const fieldIsLink = isFieldLink(fieldDefinition) && isFieldLinkValue(valueToPersist); @@ -108,6 +113,7 @@ export const usePersistField = () => { fieldIsProbability || fieldIsNumber || fieldIsDateTime || + fieldIsDate || fieldIsPhone || fieldIsLink || fieldIsCurrency || diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx index 975ac30a0..8b6cd134f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx @@ -1,9 +1,9 @@ import { DateDisplay } from '@/ui/field/display/components/DateDisplay'; -import { useDateTimeField } from '../../hooks/useDateTimeField'; +import { useDateField } from '../../hooks/useDateField'; export const DateFieldDisplay = () => { - const { fieldValue } = useDateTimeField(); + const { fieldValue } = useDateField(); return ; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx new file mode 100644 index 000000000..cffbd0473 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx @@ -0,0 +1,9 @@ +import { DateDisplay } from '@/ui/field/display/components/DateDisplay'; + +import { useDateTimeField } from '../../hooks/useDateTimeField'; + +export const DateTimeFieldDisplay = () => { + const { fieldValue } = useDateTimeField(); + + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateFieldDisplay.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateTimeFieldDisplay.stories.tsx similarity index 91% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateFieldDisplay.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateTimeFieldDisplay.stories.tsx index d1840916a..b34ecbaf4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateFieldDisplay.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/DateTimeFieldDisplay.stories.tsx @@ -6,7 +6,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { FieldContext } from '../../../../contexts/FieldContext'; import { useDateTimeField } from '../../../hooks/useDateTimeField'; -import { DateFieldDisplay } from '../DateFieldDisplay'; +import { DateTimeFieldDisplay } from '../DateTimeFieldDisplay'; const formattedDate = new Date('2023-04-01'); @@ -47,7 +47,7 @@ const meta: Meta = { ), ComponentDecorator, ], - component: DateFieldDisplay, + component: DateTimeFieldDisplay, argTypes: { value: { control: 'date' } }, args: { value: formattedDate, @@ -56,7 +56,7 @@ const meta: Meta = { export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateField.ts new file mode 100644 index 000000000..5505df67e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateField.ts @@ -0,0 +1,40 @@ +import { useContext } from 'react'; +import { useRecoilState } from 'recoil'; + +import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; +import { FieldDateValue } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +import { FieldContext } from '../../contexts/FieldContext'; +import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; + +export const useDateField = () => { + const { entityId, fieldDefinition, hotkeyScope, clearable } = + useContext(FieldContext); + + assertFieldMetadata(FieldMetadataType.Date, isFieldDate, fieldDefinition); + + const fieldName = fieldDefinition.metadata.fieldName; + + const [fieldValue, setFieldValue] = useRecoilState( + recordStoreFamilySelector({ + recordId: entityId, + fieldName: fieldName, + }), + ); + + const { setDraftValue } = useRecordFieldInput( + `${entityId}-${fieldName}`, + ); + + return { + fieldDefinition, + fieldValue, + setDraftValue, + setFieldValue, + hotkeyScope, + clearable, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/BooleanFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/BooleanFieldInput.tsx index f6cffa3b8..b2b5cb97d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/BooleanFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/BooleanFieldInput.tsx @@ -3,7 +3,7 @@ import { BooleanInput } from '@/ui/field/input/components/BooleanInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useBooleanField } from '../../hooks/useBooleanField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type BooleanFieldInputProps = { onSubmit?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx index 36efd7472..08ebc0601 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx @@ -5,7 +5,7 @@ import { CurrencyInput } from '@/ui/field/input/components/CurrencyInput'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { useCurrencyField } from '../../hooks/useCurrencyField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type CurrencyFieldInputProps = { onClickOutside?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateFieldInput.tsx index c1165f973..1c9b4a869 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateFieldInput.tsx @@ -1,8 +1,8 @@ +import { useDateField } from '@/object-record/record-field/meta-types/hooks/useDateField'; import { DateInput } from '@/ui/field/input/components/DateInput'; import { Nullable } from '~/types/Nullable'; import { usePersistField } from '../../../hooks/usePersistField'; -import { useDateTimeField } from '../../hooks/useDateTimeField'; export type FieldInputEvent = (persist: () => void) => void; @@ -17,8 +17,7 @@ export const DateFieldInput = ({ onEscape, onClickOutside, }: DateFieldInputProps) => { - const { fieldValue, hotkeyScope, clearable, setDraftValue } = - useDateTimeField(); + const { fieldValue, hotkeyScope, setDraftValue } = useDateField(); const persistField = usePersistField(); @@ -33,6 +32,7 @@ export const DateFieldInput = ({ }; const handleEnter = (newDate: Nullable) => { + console.log('newDate enter', newDate); onEnter?.(() => persistDate(newDate)); }; @@ -60,7 +60,7 @@ export const DateFieldInput = ({ onEnter={handleEnter} onEscape={handleEscape} value={dateValue} - clearable={clearable} + clearable onChange={handleChange} /> ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateTimeFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateTimeFieldInput.tsx new file mode 100644 index 000000000..d3b5f46ce --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/DateTimeFieldInput.tsx @@ -0,0 +1,68 @@ +import { DateInput } from '@/ui/field/input/components/DateInput'; +import { Nullable } from '~/types/Nullable'; + +import { usePersistField } from '../../../hooks/usePersistField'; +import { useDateTimeField } from '../../hooks/useDateTimeField'; + +export type FieldInputEvent = (persist: () => void) => void; + +export type DateTimeFieldInputProps = { + onClickOutside?: FieldInputEvent; + onEnter?: FieldInputEvent; + onEscape?: FieldInputEvent; +}; + +export const DateTimeFieldInput = ({ + onEnter, + onEscape, + onClickOutside, +}: DateTimeFieldInputProps) => { + const { fieldValue, hotkeyScope, clearable, setDraftValue } = + useDateTimeField(); + + const persistField = usePersistField(); + + const persistDate = (newDate: Nullable) => { + if (!newDate) { + persistField(null); + } else { + const newDateISO = newDate?.toISOString(); + + persistField(newDateISO); + } + }; + + const handleEnter = (newDate: Nullable) => { + onEnter?.(() => persistDate(newDate)); + }; + + const handleEscape = (newDate: Nullable) => { + onEscape?.(() => persistDate(newDate)); + }; + + const handleClickOutside = ( + _event: MouseEvent | TouchEvent, + newDate: Nullable, + ) => { + onClickOutside?.(() => persistDate(newDate)); + }; + + const handleChange = (newDate: Nullable) => { + setDraftValue(newDate?.toDateString() ?? ''); + }; + + const dateValue = fieldValue ? new Date(fieldValue) : null; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx index 4d6bb3862..29ed4b2f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx @@ -4,7 +4,7 @@ import { FieldInputOverlay } from '../../../../../ui/field/input/components/Fiel import { usePersistField } from '../../../hooks/usePersistField'; import { useEmailField } from '../../hooks/useEmailField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type EmailFieldInputProps = { onClickOutside?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx index 7756c1ef6..5b6e06a64 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/FullNameFieldInput.tsx @@ -5,7 +5,7 @@ import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay import { usePersistField } from '../../../hooks/usePersistField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS = 'F‌‌irst name'; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx index 255dfb086..5bf5571f1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx @@ -3,7 +3,7 @@ import { TextInput } from '@/ui/field/input/components/TextInput'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { useLinkField } from '../../hooks/useLinkField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type LinkFieldInputProps = { onClickOutside?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx index 3e6e7f894..179455da6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx @@ -3,7 +3,7 @@ import { PhoneInput } from '@/ui/field/input/components/PhoneInput'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { usePhoneField } from '../../hooks/usePhoneField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type PhoneFieldInputProps = { onClickOutside?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RatingFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RatingFieldInput.tsx index 447be4f06..873708669 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RatingFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RatingFieldInput.tsx @@ -4,7 +4,7 @@ import { RatingInput } from '@/ui/field/input/components/RatingInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useRatingField } from '../../hooks/useRatingField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type RatingFieldInputProps = { onSubmit?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFieldInput.tsx index 17d3e0365..ec80fd26c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFieldInput.tsx @@ -6,7 +6,7 @@ import { EntityForSelect } from '@/object-record/relation-picker/types/EntityFor import { usePersistField } from '../../../hooks/usePersistField'; import { useRelationField } from '../../hooks/useRelationField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; const StyledRelationPickerContainer = styled.div` left: -1px; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/TextFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/TextFieldInput.tsx index 1a9eee83e..087dc37f4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/TextFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/TextFieldInput.tsx @@ -4,7 +4,7 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useTextField } from '../../hooks/useTextField'; -import { FieldInputEvent } from './DateFieldInput'; +import { FieldInputEvent } from './DateTimeFieldInput'; export type TextFieldInputProps = { onClickOutside?: FieldInputEvent; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx similarity index 94% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateFieldInput.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx index 389e5a6f3..28bd5948f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/DateTimeFieldInput.stories.tsx @@ -7,7 +7,10 @@ import { FieldMetadataType } from '~/generated/graphql'; import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; import { useDateTimeField } from '../../../hooks/useDateTimeField'; -import { DateFieldInput, DateFieldInputProps } from '../DateFieldInput'; +import { + DateTimeFieldInput, + DateTimeFieldInputProps, +} from '../DateTimeFieldInput'; const formattedDate = new Date(2022, 1, 1); @@ -21,7 +24,7 @@ const DateFieldValueSetterEffect = ({ value }: { value: Date }) => { return <>; }; -type DateFieldInputWithContextProps = DateFieldInputProps & { +type DateFieldInputWithContextProps = DateTimeFieldInputProps & { value: Date; entityId?: string; }; @@ -55,7 +58,7 @@ const DateFieldInputWithContext = ({ entityId={entityId} > - ( fieldType: E, fieldTypeGuard: ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDate.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDate.ts new file mode 100644 index 000000000..513d41256 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDate.ts @@ -0,0 +1,6 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldDateMetadata, FieldMetadata } from '../FieldMetadata'; + +export const isFieldDate = ( + field: Pick, 'type'>, +): field is FieldDefinition => field.type === 'DATE'; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDateValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDateValue.ts new file mode 100644 index 000000000..e0fba3239 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDateValue.ts @@ -0,0 +1,10 @@ +import { isNull, isString } from '@sniptt/guards'; + +import { FieldDateValue } from '@/object-record/record-field/types/FieldMetadata'; + +// TODO: add zod +export const isFieldDateValue = ( + fieldValue: unknown, +): fieldValue is FieldDateValue => + (isString(fieldValue) && !isNaN(Date.parse(fieldValue))) || + isNull(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts index 776ae51cc..ded220c98 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts @@ -5,6 +5,7 @@ import { isFieldAddressValue } from '@/object-record/record-field/types/guards/i import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; +import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; @@ -38,6 +39,7 @@ export const isFieldValueEmpty = ({ isFieldUuid(fieldDefinition) || isFieldText(fieldDefinition) || isFieldDateTime(fieldDefinition) || + isFieldDate(fieldDefinition) || isFieldNumber(fieldDefinition) || isFieldRating(fieldDefinition) || isFieldEmail(fieldDefinition) || diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index 3ab9cc857..95c05b0d0 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -39,6 +39,9 @@ export const generateEmptyFieldValue = ( case FieldMetadataType.DateTime: { return null; } + case FieldMetadataType.Date: { + return null; + } case FieldMetadataType.Number: case FieldMetadataType.Rating: case FieldMetadataType.Position: diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts index 409a231e2..1ee3a2755 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts @@ -71,6 +71,11 @@ export const SETTINGS_FIELD_TYPE_CONFIGS: Record< Icon: IconCalendarEvent, defaultValue: DEFAULT_DATE_VALUE.toISOString(), }, + [FieldMetadataType.Date]: { + label: 'Date', + Icon: IconCalendarEvent, + defaultValue: DEFAULT_DATE_VALUE.toISOString(), + }, [FieldMetadataType.Select]: { label: 'Select', Icon: IconTag, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 95bcbe773..68c4b44f5 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -63,6 +63,7 @@ const previewableTypes = [ FieldMetadataType.Boolean, FieldMetadataType.Currency, FieldMetadataType.DateTime, + FieldMetadataType.Date, FieldMetadataType.Select, FieldMetadataType.MultiSelect, FieldMetadataType.Link, diff --git a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx index d680a07b9..ac3ffaafb 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { flip, offset, useFloating } from '@floating-ui/react'; @@ -14,8 +14,6 @@ const StyledCalendarContainer = styled.div` border-radius: ${({ theme }) => theme.border.radius.md}; box-shadow: ${({ theme }) => theme.boxShadow.strong}; - margin-top: 1px; - position: absolute; z-index: 1; @@ -38,6 +36,7 @@ export type DateInputProps = { hotkeyScope: string; clearable?: boolean; onChange?: (newDate: Nullable) => void; + isDateTimeInput?: boolean; }; export const DateInput = ({ @@ -48,6 +47,7 @@ export const DateInput = ({ onClickOutside, clearable, onChange, + isDateTimeInput, }: DateInputProps) => { const theme = useTheme(); @@ -60,7 +60,7 @@ export const DateInput = ({ middleware: [ flip(), offset({ - mainAxis: theme.spacingMultiplicator * 2, + mainAxis: theme.spacingMultiplicator * -6, }), ], }); @@ -70,10 +70,6 @@ export const DateInput = ({ onChange?.(newDate); }; - useEffect(() => { - setInternalValue(value); - }, [value]); - useRegisterInputEvents({ inputRef: wrapperRef, inputValue: internalValue, @@ -99,6 +95,7 @@ export const DateInput = ({ onEnter(newDate); }} clearable={clearable ? clearable : false} + isDateTimeInput={isDateTimeInput} /> diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index 0a436bd45..a36bad5c8 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import { useState } from 'react'; import ReactDatePicker from 'react-datepicker'; import styled from '@emotion/styled'; +import { DateTime } from 'luxon'; import { IconCalendarX } from 'twenty-ui'; import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/components/MenuItemLeftContent'; @@ -235,7 +236,7 @@ const StyledButtonContainer = styled(StyledHoverableMenuItemBase)` `; const StyledButton = styled(MenuItemLeftContent)` - justify-content: center; + justify-content: start; `; export type InternalDatePickerProps = { @@ -243,24 +244,100 @@ export type InternalDatePickerProps = { onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date) => void; clearable?: boolean; + isDateTimeInput?: boolean; }; +const StyledInputContainer = styled.div` + width: 100%; + display: flex; + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + height: ${({ theme }) => theme.spacing(8)}; +`; + +const StyledInput = styled.input` + background: ${({ theme }) => theme.background.secondary}; + border: none; + color: ${({ theme }) => theme.font.color.primary}; + outline: none; + padding: 8px; + font-weight: 500; + font-size: ${({ theme }) => theme.font.size.md}; + width: 100%; +`; + +const PICKER_DATE_FORMAT = 'MM/dd/yyyy'; + export const InternalDatePicker = ({ date, onChange, onMouseSelect, clearable = true, + isDateTimeInput, }: InternalDatePickerProps) => { const handleClear = () => { onMouseSelect?.(null); }; + const initialDate = date + ? DateTime.fromJSDate(date).toFormat(PICKER_DATE_FORMAT) + : DateTime.now().toFormat(PICKER_DATE_FORMAT); + + const [dateValue, setDateValue] = useState(initialDate); + + const dateValueAsJSDate = DateTime.fromFormat(dateValue, PICKER_DATE_FORMAT) + .isValid + ? DateTime.fromFormat(dateValue, PICKER_DATE_FORMAT).toJSDate() + : null; + return (
+ + { + const inputValue = e.target.value; + setDateValue(inputValue); + + if (!isDateTimeInput) { + const parsedInputDate = DateTime.fromFormat( + inputValue, + PICKER_DATE_FORMAT, + { zone: 'utc' }, + ); + + const isValid = parsedInputDate.isValid; + + if (isValid) { + onChange?.(parsedInputDate.toJSDate()); + } + } else { + // TODO: implement time also + const parsedInputDate = DateTime.fromFormat( + inputValue, + PICKER_DATE_FORMAT, + { zone: 'utc' }, + ); + + const isValid = parsedInputDate.isValid; + + if (isValid) { + onChange?.(parsedInputDate.toJSDate()); + } + } + }} + /> + + { @@ -268,10 +345,18 @@ export const InternalDatePicker = ({ }} customInput={<>} onSelect={(date: Date, event) => { + // Setting the time to midnight might sometimes return the previous day + // We set to 21:00 to avoid any timezone issues + const dateForDateField = new Date(date.setHours(21, 0, 0, 0)); + + setDateValue( + DateTime.fromJSDate(date).toFormat(PICKER_DATE_FORMAT), + ); + if (event?.type === 'click') { - onMouseSelect?.(date); + onMouseSelect?.(isDateTimeInput ? date : dateForDateField); } else { - onChange?.(date); + onChange?.(isDateTimeInput ? date : dateForDateField); } }} > diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index 3d5ca2f1b..9b3eb2dee 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -65,6 +65,7 @@ export class TypeMapperService { [FieldMetadataType.PHONE, GraphQLString], [FieldMetadataType.EMAIL, GraphQLString], [FieldMetadataType.DATE_TIME, dateScalar], + [FieldMetadataType.DATE, dateScalar], [FieldMetadataType.BOOLEAN, GraphQLBoolean], [FieldMetadataType.NUMBER, numberScalar], [FieldMetadataType.NUMERIC, BigFloatScalarType], @@ -96,6 +97,7 @@ export class TypeMapperService { [FieldMetadataType.PHONE, StringFilterType], [FieldMetadataType.EMAIL, StringFilterType], [FieldMetadataType.DATE_TIME, dateFilter], + [FieldMetadataType.DATE, DateFilterType], [FieldMetadataType.BOOLEAN, BooleanFilterType], [FieldMetadataType.NUMBER, numberScalar], [FieldMetadataType.NUMERIC, BigFloatFilterType], @@ -117,6 +119,7 @@ export class TypeMapperService { [FieldMetadataType.PHONE, OrderByDirectionType], [FieldMetadataType.EMAIL, OrderByDirectionType], [FieldMetadataType.DATE_TIME, OrderByDirectionType], + [FieldMetadataType.DATE, OrderByDirectionType], [FieldMetadataType.BOOLEAN, OrderByDirectionType], [FieldMetadataType.NUMBER, OrderByDirectionType], [FieldMetadataType.NUMERIC, OrderByDirectionType], diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts index 54d1fa83e..eec9a40c3 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts @@ -20,6 +20,7 @@ export const mapFieldMetadataToGraphqlQuery = ( FieldMetadataType.TEXT, FieldMetadataType.PHONE, FieldMetadataType.DATE_TIME, + FieldMetadataType.DATE, FieldMetadataType.EMAIL, FieldMetadataType.NUMBER, FieldMetadataType.SELECT, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 07401cb78..8cba00761 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -31,6 +31,7 @@ const getSchemaComponentsProperties = ( case FieldMetadataType.PHONE: case FieldMetadataType.EMAIL: case FieldMetadataType.DATE_TIME: + case FieldMetadataType.DATE: itemProperty.type = 'string'; break; case FieldMetadataType.NUMBER: diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts index c5666fda9..421227303 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts @@ -58,6 +58,12 @@ export class FieldMetadataDefaultValueDateTime { value: Date | null; } +export class FieldMetadataDefaultValueDate { + @ValidateIf((object, value) => value !== null) + @IsDate() + value: Date | null; +} + export class FieldMetadataDefaultValueLink { @ValidateIf((object, value) => value !== null) @IsQuotedString() diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index 1678b3388..ede4e44b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -23,6 +23,7 @@ export enum FieldMetadataType { PHONE = 'PHONE', EMAIL = 'EMAIL', DATE_TIME = 'DATE_TIME', + DATE = 'DATE', BOOLEAN = 'BOOLEAN', NUMBER = 'NUMBER', NUMERIC = 'NUMERIC', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts index ca96eb8f4..5ca1ce6d4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts @@ -27,6 +27,9 @@ type FieldMetadataDefaultValueMapping = { [FieldMetadataType.DATE_TIME]: | FieldMetadataDefaultValueDateTime | FieldMetadataDefaultValueNowFunction; + [FieldMetadataType.DATE]: + | FieldMetadataDefaultValueDateTime + | FieldMetadataDefaultValueNowFunction; [FieldMetadataType.BOOLEAN]: FieldMetadataDefaultValueBoolean; [FieldMetadataType.NUMBER]: FieldMetadataDefaultValueNumber; [FieldMetadataType.POSITION]: FieldMetadataDefaultValueNumber; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts index 5cd428bb0..ab22f3c00 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts @@ -20,6 +20,7 @@ import { FieldMetadataDefaultValueStringArray, FieldMetadataDefaultValueNowFunction, FieldMetadataDefaultValueUuidFunction, + FieldMetadataDefaultValueDate, } from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -35,6 +36,7 @@ export const defaultValueValidatorsMap = { FieldMetadataDefaultValueDateTime, FieldMetadataDefaultValueNowFunction, ], + [FieldMetadataType.DATE]: [FieldMetadataDefaultValueDate], [FieldMetadataType.BOOLEAN]: [FieldMetadataDefaultValueBoolean], [FieldMetadataType.NUMBER]: [FieldMetadataDefaultValueNumber], [FieldMetadataType.NUMERIC]: [FieldMetadataDefaultValueString], diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts index 5c2903f64..093b3cba9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts @@ -25,6 +25,7 @@ export type BasicFieldMetadataType = | FieldMetadataType.BOOLEAN | FieldMetadataType.POSITION | FieldMetadataType.DATE_TIME + | FieldMetadataType.DATE | FieldMetadataType.POSITION; @Injectable() diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts index c4d84d2b3..f3999fd7f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts @@ -25,6 +25,8 @@ export const fieldMetadataTypeToColumnType = ( return 'boolean'; case FieldMetadataType.DATE_TIME: return 'timestamptz'; + case FieldMetadataType.DATE: + return 'date'; case FieldMetadataType.RATING: case FieldMetadataType.SELECT: case FieldMetadataType.MULTI_SELECT: diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts index ac178277a..43eeae07a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts @@ -74,6 +74,7 @@ export class WorkspaceMigrationFactory { ], [FieldMetadataType.BOOLEAN, { factory: this.basicColumnActionFactory }], [FieldMetadataType.DATE_TIME, { factory: this.basicColumnActionFactory }], + [FieldMetadataType.DATE, { factory: this.basicColumnActionFactory }], [FieldMetadataType.RATING, { factory: this.enumColumnActionFactory }], [FieldMetadataType.SELECT, { factory: this.enumColumnActionFactory }], [ diff --git a/packages/twenty-zapier/src/utils/computeInputFields.ts b/packages/twenty-zapier/src/utils/computeInputFields.ts index dc60091c1..7b8afbc58 100644 --- a/packages/twenty-zapier/src/utils/computeInputFields.ts +++ b/packages/twenty-zapier/src/utils/computeInputFields.ts @@ -18,6 +18,8 @@ const getTypeFromFieldMetadataType = ( return 'string'; case FieldMetadataType.DATE_TIME: return 'datetime'; + case FieldMetadataType.DATE: + return 'date'; case FieldMetadataType.BOOLEAN: return 'boolean'; case FieldMetadataType.NUMBER: @@ -177,6 +179,7 @@ export const computeInputFields = ( case FieldMetadataType.PHONE: case FieldMetadataType.EMAIL: case FieldMetadataType.DATE_TIME: + case FieldMetadataType.DATE: case FieldMetadataType.BOOLEAN: case FieldMetadataType.NUMBER: case FieldMetadataType.NUMERIC: diff --git a/packages/twenty-zapier/src/utils/data.types.ts b/packages/twenty-zapier/src/utils/data.types.ts index 0d9a8056d..d7b38d487 100644 --- a/packages/twenty-zapier/src/utils/data.types.ts +++ b/packages/twenty-zapier/src/utils/data.types.ts @@ -36,6 +36,7 @@ export enum FieldMetadataType { PHONE = 'PHONE', EMAIL = 'EMAIL', DATE_TIME = 'DATE_TIME', + DATE = 'DATE', BOOLEAN = 'BOOLEAN', NUMBER = 'NUMBER', NUMERIC = 'NUMERIC',