From f7396e3531eaf94fb7bb9930ecb9a65019b8856c Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 17 Jun 2025 11:02:13 +0200 Subject: [PATCH] Enable find records action + Implement readonly (#12636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add readonly in context - Avoid using it in AdvancedFilters component directly. Keeping it workflow related Capture d’écran 2025-06-16 à 18 23 34 --- ...eldSelectDropdownButtonClickableSelect.tsx | 49 ++---------------- .../hooks/useRecordFilterField.ts | 51 +++++++++++++++++++ .../states/context/AdvancedFilterContext.ts | 1 + .../constants/RecordActions.ts | 11 ++-- ...kflowAdvancedFilterFieldSelectDisabled.tsx | 23 +++++++++ .../WorkflowAdvancedFilterFormInput.tsx | 7 +++ ...kflowAdvancedFilterLogicalOperatorCell.tsx | 26 ++++++++-- ...rkflowAdvancedFilterRecordFilterColumn.tsx | 23 +++++++-- ...dvancedFilterRecordFilterGroupChildren.tsx | 11 ++-- ...wAdvancedFilterRecordFilterGroupColumn.tsx | 12 +++-- ...dvancedFilterRecordFilterOperandSelect.tsx | 7 ++- ...ncedFilterValueFormCompositeFieldInput.tsx | 11 ++++ .../WorkflowEditActionFindRecords.tsx | 5 ++ .../WorkflowFindRecordsAddFilterButton.tsx | 4 ++ .../components/WorkflowFindRecordsFilters.tsx | 13 +++-- 15 files changed, 183 insertions(+), 71 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFieldSelectDisabled.tsx diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButtonClickableSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButtonClickableSelect.tsx index 4b3270489..fe5aff566 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButtonClickableSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButtonClickableSelect.tsx @@ -1,13 +1,5 @@ -import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById'; -import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; -import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { isValidSubFieldName } from '@/settings/data-model/utils/isValidSubFieldName'; +import { useRecordFilterField } from '@/object-record/advanced-filter/hooks/useRecordFilterField'; import { SelectControl } from '@/ui/input/components/SelectControl'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { isNonEmptyString } from '@sniptt/guards'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; type AdvancedFilterFieldSelectDropdownButtonClickableSelectProps = { recordFilterId: string; @@ -16,47 +8,14 @@ type AdvancedFilterFieldSelectDropdownButtonClickableSelectProps = { export const AdvancedFilterFieldSelectDropdownButtonClickableSelect = ({ recordFilterId, }: AdvancedFilterFieldSelectDropdownButtonClickableSelectProps) => { - const currentRecordFilters = useRecoilComponentValueV2( - currentRecordFiltersComponentState, - ); - - const recordFilter = currentRecordFilters.find( - (recordFilter) => recordFilter.id === recordFilterId, - ); - - const { getFieldMetadataItemById } = useGetFieldMetadataItemById(); - - const fieldMetadataItem = isNonEmptyString(recordFilter?.fieldMetadataId) - ? getFieldMetadataItemById(recordFilter?.fieldMetadataId) - : undefined; - - const { getIcon } = useIcons(); - - const fieldIcon = isDefined(fieldMetadataItem?.icon) - ? getIcon(fieldMetadataItem?.icon) - : undefined; - - const subFieldLabel = - isDefined(fieldMetadataItem) && - isCompositeFieldType(fieldMetadataItem.type) && - isNonEmptyString(recordFilter?.subFieldName) && - isValidSubFieldName(recordFilter.subFieldName) - ? getCompositeSubFieldLabel( - fieldMetadataItem.type, - recordFilter.subFieldName, - ) - : ''; - - const fieldNameLabel = isNonEmptyString(subFieldLabel) - ? `${recordFilter?.label} / ${subFieldLabel}` - : (recordFilter?.label ?? ''); + const { label, icon } = useRecordFilterField(recordFilterId); return ( ); diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts new file mode 100644 index 000000000..b18990b83 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts @@ -0,0 +1,51 @@ +import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById'; +import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; +import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { isValidSubFieldName } from '@/settings/data-model/utils/isValidSubFieldName'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isNonEmptyString } from '@sniptt/guards'; +import { isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; + +export const useRecordFilterField = (recordFilterId: string) => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const recordFilter = currentRecordFilters.find( + (recordFilter) => recordFilter.id === recordFilterId, + ); + + const { getFieldMetadataItemById } = useGetFieldMetadataItemById(); + + const fieldMetadataItem = isNonEmptyString(recordFilter?.fieldMetadataId) + ? getFieldMetadataItemById(recordFilter?.fieldMetadataId) + : undefined; + + const { getIcon } = useIcons(); + + const icon = isDefined(fieldMetadataItem?.icon) + ? getIcon(fieldMetadataItem?.icon) + : undefined; + + const subFieldLabel = + isDefined(fieldMetadataItem) && + isCompositeFieldType(fieldMetadataItem.type) && + isNonEmptyString(recordFilter?.subFieldName) && + isValidSubFieldName(recordFilter.subFieldName) + ? getCompositeSubFieldLabel( + fieldMetadataItem.type, + recordFilter.subFieldName, + ) + : ''; + + const label = isNonEmptyString(subFieldLabel) + ? `${recordFilter?.label} / ${subFieldLabel}` + : (recordFilter?.label ?? ''); + + return { + label, + icon, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts index c72176191..d175fb514 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts @@ -3,6 +3,7 @@ import { createContext } from 'react'; type AdvancedFilterContextType = { onUpdate?: () => void; isWorkflowFindRecords?: boolean; + readonly?: boolean; }; export const AdvancedFilterContext = createContext( diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/RecordActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/RecordActions.ts index 2d27e9761..37c374a39 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/RecordActions.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/RecordActions.ts @@ -23,10 +23,9 @@ export const RECORD_ACTIONS: Array<{ type: 'DELETE_RECORD', icon: 'IconTrash', }, - // TODO: Add search records action - // { - // label: 'Search Records', - // type: 'FIND_RECORDS', - // icon: 'IconSearch', - // }, + { + label: 'Search Records', + type: 'FIND_RECORDS', + icon: 'IconSearch', + }, ]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFieldSelectDisabled.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFieldSelectDisabled.tsx new file mode 100644 index 000000000..a0c1ff0c9 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFieldSelectDisabled.tsx @@ -0,0 +1,23 @@ +import { useRecordFilterField } from '@/object-record/advanced-filter/hooks/useRecordFilterField'; +import { SelectControl } from '@/ui/input/components/SelectControl'; + +type WorkflowAdvancedFilterFieldSelectDisabledProps = { + recordFilterId: string; +}; + +export const WorkflowAdvancedFilterFieldSelectDisabled = ({ + recordFilterId, +}: WorkflowAdvancedFilterFieldSelectDisabledProps) => { + const { label, icon } = useRecordFilterField(recordFilterId); + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput.tsx index 401cbb916..6bb66285d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput.tsx @@ -1,4 +1,5 @@ import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition'; +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { shouldShowFilterTextInput } from '@/object-record/advanced-filter/utils/shouldShowFilterTextInput'; import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; @@ -18,6 +19,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { WorkflowAdvancedFilterValueFormCompositeFieldInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterValueFormCompositeFieldInput'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { isObject } from '@sniptt/guards'; +import { useContext } from 'react'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { JsonValue } from 'type-fest'; @@ -27,6 +29,8 @@ export const WorkflowAdvancedFilterValueFormInput = ({ }: { recordFilterId: string; }) => { + const { readonly } = useContext(AdvancedFilterContext); + const currentRecordFilters = useRecoilComponentValueV2( currentRecordFiltersComponentState, ); @@ -99,6 +103,7 @@ export const WorkflowAdvancedFilterValueFormInput = ({ label={''} defaultValue={recordFilter.value} onChange={handleChange} + readonly={readonly} VariablePicker={WorkflowVariablePicker} /> ); @@ -123,6 +128,7 @@ export const WorkflowAdvancedFilterValueFormInput = ({ label={''} defaultValue={recordFilter.value} onChange={handleChange} + readonly={readonly} VariablePicker={WorkflowVariablePicker} options={metadata?.options ?? []} /> @@ -140,6 +146,7 @@ export const WorkflowAdvancedFilterValueFormInput = ({ field={field} defaultValue={recordFilter.value} onChange={handleChange} + readonly={readonly} // VariablePicker is not supported for date filters yet VariablePicker={ isFilterableByDateValue ? undefined : WorkflowVariablePicker diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell.tsx index 459f573bb..ea7004d7f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell.tsx @@ -1,7 +1,13 @@ import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown'; +import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions'; +import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset'; +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { Select } from '@/ui/input/components/Select'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import styled from '@emotion/styled'; +import { useContext } from 'react'; import { capitalize } from 'twenty-shared/utils'; const StyledText = styled.div` @@ -26,14 +32,28 @@ export const WorkflowAdvancedFilterLogicalOperatorCell = ({ index, recordFilterGroup, }: WorkflowAdvancedFilterLogicalOperatorCellProps) => { + const { readonly } = useContext(AdvancedFilterContext); + return ( {index === 0 ? ( Where ) : index === 1 ? ( - + readonly ? ( +