diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordFieldChip.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordFieldChip.tsx similarity index 87% rename from packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordFieldChip.tsx rename to packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordFieldChip.tsx index 38168e0e5..5d290c4b5 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordFieldChip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordFieldChip.tsx @@ -1,11 +1,11 @@ import { RecordChip } from '@/object-record/components/RecordChip'; -import { VariableChipStandalone } from '@/object-record/record-field/form-types/components/VariableChipStandalone'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { RecordId, Variable, -} from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker'; +} from '@/object-record/record-field/form-types/components/FormSingleRecordPicker'; +import { VariableChipStandalone } from '@/object-record/record-field/form-types/components/VariableChipStandalone'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import styled from '@emotion/styled'; const StyledRecordChip = styled(RecordChip)` @@ -18,7 +18,7 @@ const StyledPlaceholder = styled.div` margin: ${({ theme }) => theme.spacing(2)}; `; -type WorkflowSingleRecordFieldChipProps = { +type FormSingleRecordFieldChipProps = { draftValue: | { type: 'static'; @@ -34,13 +34,13 @@ type WorkflowSingleRecordFieldChipProps = { disabled?: boolean; }; -export const WorkflowSingleRecordFieldChip = ({ +export const FormSingleRecordFieldChip = ({ draftValue, selectedRecord, objectNameSingular, onRemove, disabled, -}: WorkflowSingleRecordFieldChipProps) => { +}: FormSingleRecordFieldChipProps) => { if ( !!draftValue && draftValue.type === 'variable' && diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx similarity index 74% rename from packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker.tsx rename to packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx index 7db091805..12f4507ca 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx @@ -2,6 +2,8 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer'; import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer'; import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer'; +import { FormSingleRecordFieldChip } from '@/object-record/record-field/form-types/components/FormSingleRecordFieldChip'; +import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent'; import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker'; import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState'; import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; @@ -12,13 +14,10 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; -import { WorkflowSingleRecordFieldChip } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordFieldChip'; -import { WorkflowVariablesDropdown } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdown'; -import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { useCallback } from 'react'; -import { IconChevronDown, IconForbid, LightIconButton } from 'twenty-ui'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; +import { IconChevronDown, IconForbid, LightIconButton } from 'twenty-ui'; const StyledFormSelectContainer = styled(FormFieldInputInputContainer)` justify-content: space-between; @@ -26,27 +25,10 @@ const StyledFormSelectContainer = styled(FormFieldInputInputContainer)` padding-right: ${({ theme }) => theme.spacing(1)}; `; -const StyledSearchVariablesDropdownContainer = styled.div` - align-items: center; - display: flex; - justify-content: center; - ${({ theme }) => css` - :hover { - background-color: ${theme.background.transparent.light}; - } - `} - ${({ theme }) => css` - background-color: ${theme.background.transparent.lighter}; - border-top-right-radius: ${theme.border.radius.sm}; - border-bottom-right-radius: ${theme.border.radius.sm}; - border: 1px solid ${theme.border.color.medium}; - `} -`; - export type RecordId = string; export type Variable = string; -type WorkflowSingleRecordPickerValue = +type FormSingleRecordPickerValue = | { type: 'static'; value: RecordId; @@ -56,33 +38,36 @@ type WorkflowSingleRecordPickerValue = value: Variable; }; -export type WorkflowSingleRecordPickerProps = { +export type FormSingleRecordPickerProps = { label?: string; defaultValue: RecordId | Variable; onChange: (value: RecordId | Variable) => void; objectNameSingular: string; disabled?: boolean; testId?: string; + VariablePicker?: VariablePickerComponent; }; -export const WorkflowSingleRecordPicker = ({ +export const FormSingleRecordPicker = ({ label, defaultValue, objectNameSingular, onChange, disabled, testId, -}: WorkflowSingleRecordPickerProps) => { - const draftValue: WorkflowSingleRecordPickerValue = - isStandaloneVariableString(defaultValue) - ? { - type: 'variable', - value: defaultValue, - } - : { - type: 'static', - value: defaultValue || '', - }; + VariablePicker, +}: FormSingleRecordPickerProps) => { + const draftValue: FormSingleRecordPickerValue = isStandaloneVariableString( + defaultValue, + ) + ? { + type: 'variable', + value: defaultValue, + } + : { + type: 'static', + value: defaultValue || '', + }; const { record: selectedRecord } = useFindOneRecord({ objectRecordId: @@ -94,8 +79,8 @@ export const WorkflowSingleRecordPicker = ({ skip: !isValidUuid(defaultValue), }); - const dropdownId = `workflow-record-picker-${objectNameSingular}`; - const variablesDropdownId = `workflow-record-picker-${objectNameSingular}-variables`; + const dropdownId = `form-record-picker-${objectNameSingular}`; + const variablesDropdownId = `form-record-picker-${objectNameSingular}-variables`; const { closeDropdown } = useDropdown(dropdownId); @@ -144,8 +129,10 @@ export const WorkflowSingleRecordPicker = ({ {label ? {label} : null} - - + )} - - {!disabled && ( - - - + {isDefined(VariablePicker) && !disabled && ( + )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/types/VariablePickerComponent.ts b/packages/twenty-front/src/modules/object-record/record-field/form-types/types/VariablePickerComponent.ts index 8e4d4371e..ad2d9cad8 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/types/VariablePickerComponent.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/types/VariablePickerComponent.ts @@ -3,4 +3,5 @@ export type VariablePickerComponent = React.FC<{ disabled?: boolean; multiline?: boolean; onVariableSelect: (variableName: string) => void; + objectNameSingularToSelect?: string; }>; diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts index e5a442d38..116034053 100644 --- a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -92,6 +92,7 @@ export const workflowFormActionSettingsSchema = type: z.union([ z.literal(FieldMetadataType.TEXT), z.literal(FieldMetadataType.NUMBER), + z.literal('RECORD'), ]), placeholder: z.string().optional(), settings: z.record(z.any()).optional(), diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx index e532bbcb2..af5f9e06d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx @@ -1,14 +1,15 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { FormSingleRecordPicker } from '@/object-record/record-field/form-types/components/FormSingleRecordPicker'; import { Select } from '@/ui/input/components/Select'; import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; -import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker'; import { useEffect, useState } from 'react'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow'; import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; +import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { isDefined } from 'twenty-shared/utils'; import { HorizontalSeparator, SelectOption, useIcons } from 'twenty-ui'; import { JsonValue } from 'type-fest'; @@ -154,7 +155,7 @@ export const WorkflowEditActionDeleteRecord = ({ - handleFieldChange('objectRecordId', objectRecordId) @@ -163,6 +164,7 @@ export const WorkflowEditActionDeleteRecord = ({ defaultValue={formData.objectRecordId} testId="workflow-edit-action-record-delete-object-record-id" disabled={isFormDisabled} + VariablePicker={WorkflowVariablePicker} /> diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx index 4cf9414fc..2e6061323 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx @@ -6,9 +6,9 @@ import { useEffect, useState } from 'react'; import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition'; import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput'; import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput'; +import { FormSingleRecordPicker } from '@/object-record/record-field/form-types/components/FormSingleRecordPicker'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; -import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker'; import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow'; import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; @@ -208,7 +208,7 @@ export const WorkflowEditActionUpdateRecord = ({ - @@ -217,6 +217,7 @@ export const WorkflowEditActionUpdateRecord = ({ objectNameSingular={formData.objectName} defaultValue={formData.objectRecordId} disabled={isFormDisabled} + VariablePicker={WorkflowVariablePicker} /> Type { if (newType === null) { return; } - const type = newType as - | FieldMetadataType.TEXT - | FieldMetadataType.NUMBER; - const { label, placeholder } = getDefaultFormFieldSettings(type); + const type = newType as WorkflowFormFieldType; + const { name, label, placeholder, settings } = + getDefaultFormFieldSettings(type); onChange({ ...field, type, + name, label, placeholder, + settings, }); }} defaultValue={field.type} diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller.tsx index 80fc59082..37b8cc210 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller.tsx @@ -1,6 +1,7 @@ import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput'; +import { FormSingleRecordPicker } from '@/object-record/record-field/form-types/components/FormSingleRecordPicker'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter'; import { useWorkflowStepContextOrThrow } from '@/workflow/states/context/WorkflowStepContext'; @@ -114,28 +115,58 @@ export const WorkflowEditActionFormFiller = ({ disabled /> - {formData.map((field) => ( - { - onFieldUpdate({ - fieldId: field.id, - value, - }); - }} - defaultValue={field.value ?? ''} - readonly={actionOptions.readonly} - placeholder={ - field.placeholder ?? - getDefaultFormFieldSettings(field.type).placeholder + {formData.map((field) => { + if (field.type === 'RECORD') { + const objectNameSingular = field.settings?.objectName; + + if (!isDefined(objectNameSingular)) { + return null; } - /> - ))} + + const recordId = field.value?.id; + + return ( + { + onFieldUpdate({ + fieldId: field.id, + value: { + id: recordId, + }, + }); + }} + objectNameSingular={objectNameSingular} + disabled={actionOptions.readonly} + /> + ); + } + + return ( + { + onFieldUpdate({ + fieldId: field.id, + value, + }); + }} + defaultValue={field.value ?? ''} + readonly={actionOptions.readonly} + placeholder={ + field.placeholder ?? + getDefaultFormFieldSettings(field.type).placeholder + } + /> + ); + })} {!actionOptions.readonly && ( void; + onChange: (fieldName: string, value: unknown) => void; }) => { switch (field.type) { case FieldMetadataType.TEXT: @@ -32,6 +33,16 @@ export const WorkflowFormFieldSettingsByType = ({ }} /> ); + case 'RECORD': + return ( + { + onChange(fieldName, value); + }} + /> + ); default: return assertUnreachable(field.type, 'Unknown form field type'); } diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsRecordPicker.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsRecordPicker.tsx new file mode 100644 index 000000000..b995b7908 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormFieldSettingsRecordPicker.tsx @@ -0,0 +1,69 @@ +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +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 { Select } from '@/ui/input/components/Select'; +import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings'; +import styled from '@emotion/styled'; +import { SelectOption, useIcons } from 'twenty-ui'; + +type WorkflowFormFieldSettingsRecordPickerProps = { + label?: string; + settings?: Record; + onChange: (fieldName: string, value: unknown) => void; +}; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +export const WorkflowFormFieldSettingsRecordPicker = ({ + label, + settings, + onChange, +}: WorkflowFormFieldSettingsRecordPickerProps) => { + const { getIcon } = useIcons(); + + const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); + + const availableMetadata: Array> = + activeObjectMetadataItems.map((item) => ({ + Icon: getIcon(item.icon), + label: item.labelPlural, + value: item.nameSingular, + })); + + return ( + + +