Add form date field (#11360)

Builder
<img width="498" alt="Capture d’écran 2025-04-02 à 19 02 20"
src="https://github.com/user-attachments/assets/29db9fa9-aae4-4d1f-98f2-0b2371f944f1"
/>

Execution
<img width="837" alt="Capture d’écran 2025-04-02 à 19 02 47"
src="https://github.com/user-attachments/assets/ce1c442c-3c06-4f7e-99d6-3eb8fb1d2428"
/>
This commit is contained in:
Thomas Trompette
2025-04-03 10:10:43 +02:00
committed by GitHub
parent 8abec309e0
commit a062a17229
12 changed files with 140 additions and 37 deletions

View File

@ -92,6 +92,7 @@ export const workflowFormActionSettingsSchema =
type: z.union([
z.literal(FieldMetadataType.TEXT),
z.literal(FieldMetadataType.NUMBER),
z.literal(FieldMetadataType.DATE),
z.literal('RECORD'),
]),
placeholder: z.string().optional(),

View File

@ -2,6 +2,7 @@ import { FormFieldInputContainer } from '@/object-record/record-field/form-types
import { FormSelectFieldInput } from '@/object-record/record-field/form-types/components/FormSelectFieldInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { WorkflowFormFieldSettingsByType } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsByType';
import { FORM_SELECT_FIELD_TYPE_OPTIONS } from '@/workflow/workflow-steps/workflow-actions/form-action/constants/FormSelectFieldTypeOptions';
import { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { WorkflowFormFieldType } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormFieldType';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
@ -9,15 +10,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import camelCase from 'lodash.camelcase';
import { FieldMetadataType } from 'twenty-shared/types';
import {
IconSettingsAutomation,
IconX,
IllustrationIconNumbers,
IllustrationIconOneToMany,
IllustrationIconText,
LightIconButton,
} from 'twenty-ui';
import { IconSettingsAutomation, IconX, LightIconButton } from 'twenty-ui';
type WorkflowEditActionFormFieldSettingsProps = {
field: WorkflowFormActionField;
@ -109,31 +102,7 @@ export const WorkflowEditActionFormFieldSettings = ({
<FormFieldInputContainer>
<InputLabel>Type</InputLabel>
<FormSelectFieldInput
options={
[
{
label: getDefaultFormFieldSettings(FieldMetadataType.TEXT)
.label,
value: FieldMetadataType.TEXT,
Icon: IllustrationIconText,
},
{
label: getDefaultFormFieldSettings(FieldMetadataType.NUMBER)
.label,
value: FieldMetadataType.NUMBER,
Icon: IllustrationIconNumbers,
},
{
label: 'Record Picker',
value: 'RECORD',
Icon: IllustrationIconOneToMany,
},
] satisfies {
label: string;
value: WorkflowFormFieldType;
Icon: React.ElementType;
}[]
}
options={FORM_SELECT_FIELD_TYPE_OPTIONS}
onChange={(newType: string | null) => {
if (newType === null) {
return;

View File

@ -158,7 +158,7 @@ export const WorkflowEditActionFormFiller = ({
value,
});
}}
defaultValue={field.value ?? ''}
defaultValue={field.value}
readonly={actionOptions.readonly}
placeholder={
field.placeholder ??

View File

@ -1,3 +1,4 @@
import { WorkflowFormFieldSettingsDate } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsDate';
import { WorkflowFormFieldSettingsRecordPicker } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsRecordPicker';
import { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { FieldMetadataType } from 'twenty-shared/types';
@ -33,6 +34,15 @@ export const WorkflowFormFieldSettingsByType = ({
}}
/>
);
case FieldMetadataType.DATE:
return (
<WorkflowFormFieldSettingsDate
label={field.label}
onChange={(fieldName, value) => {
onChange(fieldName, value);
}}
/>
);
case 'RECORD':
return (
<WorkflowFormFieldSettingsRecordPicker

View File

@ -0,0 +1,28 @@
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { FieldMetadataType } from 'twenty-shared/types';
type WorkflowFormFieldSettingsDateProps = {
label?: string;
onChange: (fieldName: string, value: string | null) => void;
};
export const WorkflowFormFieldSettingsDate = ({
label,
onChange,
}: WorkflowFormFieldSettingsDateProps) => {
return (
<FormFieldInputContainer>
<InputLabel>Label</InputLabel>
<FormTextFieldInput
onChange={(newLabel: string | null) => {
onChange('label', newLabel);
}}
defaultValue={label}
placeholder={getDefaultFormFieldSettings(FieldMetadataType.DATE).label}
/>
</FormFieldInputContainer>
);
};

View File

@ -97,8 +97,8 @@ export const SingleRecordFieldSettings: Story = {
args: {
field: {
id: 'field-3',
name: 'record',
label: 'Record',
name: 'company',
label: 'Company',
type: 'RECORD',
settings: {
objectName: 'company',
@ -121,3 +121,27 @@ export const SingleRecordFieldSettings: Story = {
expect(args.onClose).toHaveBeenCalled();
},
};
export const DateFieldSettings: Story = {
args: {
field: {
id: 'field-4',
name: 'date',
label: 'Date Field',
type: FieldMetadataType.DATE,
placeholder: 'Enter date',
settings: {},
},
onClose: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const typeSelect = await canvas.findByText('Date');
expect(typeSelect).toBeVisible();
const closeButton = await canvas.findByTestId('close-button');
await userEvent.click(closeButton);
expect(args.onClose).toHaveBeenCalled();
},
};

View File

@ -64,6 +64,14 @@ const mockAction: WorkflowFormAction = {
objectName: 'company',
},
},
{
id: 'field-4',
name: 'date',
label: 'Date',
type: FieldMetadataType.DATE,
placeholder: 'mm/dd/yyyy',
settings: {},
},
],
outputSchema: {},
errorHandlingOptions: {
@ -91,6 +99,9 @@ export const Default: Story = {
const recordField = await canvas.findByText('Record');
expect(recordField).toBeVisible();
const dateField = await canvas.findByText('Date');
expect(dateField).toBeVisible();
},
};
@ -110,6 +121,9 @@ export const ReadonlyMode: Story = {
const numberInput = await canvas.findByPlaceholderText('Enter number');
expect(numberInput).toBeDisabled();
const dateInput = await canvas.findByPlaceholderText('mm/dd/yyyy');
expect(dateInput).toBeDisabled();
const submitButton = await canvas.queryByText('Submit');
expect(submitButton).not.toBeInTheDocument();
},

View File

@ -0,0 +1,34 @@
import { WorkflowFormFieldType } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormFieldType';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { FieldMetadataType } from 'twenty-shared/types';
import {
IllustrationIconCalendarEvent,
IllustrationIconNumbers,
IllustrationIconOneToMany,
IllustrationIconText,
SelectOption,
} from 'twenty-ui';
export const FORM_SELECT_FIELD_TYPE_OPTIONS: SelectOption<WorkflowFormFieldType>[] =
[
{
label: getDefaultFormFieldSettings(FieldMetadataType.TEXT).label,
value: FieldMetadataType.TEXT,
Icon: IllustrationIconText,
},
{
label: getDefaultFormFieldSettings(FieldMetadataType.NUMBER).label,
value: FieldMetadataType.NUMBER,
Icon: IllustrationIconNumbers,
},
{
label: getDefaultFormFieldSettings(FieldMetadataType.DATE).label,
value: FieldMetadataType.DATE,
Icon: IllustrationIconCalendarEvent,
},
{
label: getDefaultFormFieldSettings('RECORD').label,
value: 'RECORD',
Icon: IllustrationIconOneToMany,
},
];

View File

@ -3,4 +3,5 @@ import { FieldMetadataType } from 'twenty-shared/types';
export type WorkflowFormFieldType =
| FieldMetadataType.TEXT
| FieldMetadataType.NUMBER
| FieldMetadataType.DATE
| 'RECORD';

View File

@ -19,6 +19,13 @@ export const getDefaultFormFieldSettings = (type: WorkflowFormFieldType) => {
label: 'Number',
placeholder: '1000',
};
case FieldMetadataType.DATE:
return {
id: v4(),
name: 'date',
label: 'Date',
placeholder: 'mm/dd/yyyy',
};
case 'RECORD':
return {
id: v4(),

View File

@ -40,6 +40,13 @@ describe('generateFakeFormResponse', () => {
objectName: 'company',
},
},
{
id: '96939213-49ac-4dee-949d-56e6c7be98e9',
name: 'date',
type: FieldMetadataType.DATE,
label: 'Date',
placeholder: 'mm/dd/yyyy',
},
];
const result = await generateFakeFormResponse({
@ -79,6 +86,13 @@ describe('generateFakeFormResponse', () => {
},
},
},
date: {
isLeaf: true,
label: 'Date',
type: FieldMetadataType.DATE,
value: '01/23/2025',
icon: undefined,
},
});
});
});

View File

@ -3,4 +3,5 @@ import { FieldMetadataType } from 'twenty-shared/types';
export type WorkflowFormFieldType =
| FieldMetadataType.TEXT
| FieldMetadataType.NUMBER
| FieldMetadataType.DATE
| 'RECORD';