Fix remaining field issues for find record action (#12628)

- Handle currency code and country multiselect fields

Before / After
<img width="252" alt="Capture d’écran 2025-06-16 à 15 24 35"
src="https://github.com/user-attachments/assets/3e921ffa-33cb-41dd-82d7-ef3a1aef3510"
/> <img width="252" alt="Capture d’écran 2025-06-16 à 15 24 47"
src="https://github.com/user-attachments/assets/115d18b8-7a15-46b1-8786-bd63b7bb1989"
/>

- Use action button rather than light icon button to match figma. Asked
@Bonapara, we want it for both workflow and index page

Before
<img width="252" alt="Capture d’écran 2025-06-16 à 15 25 02"
src="https://github.com/user-attachments/assets/ec376c70-d2df-417b-aefc-625e965dded1"
/>

After
<img width="252" alt="Capture d’écran 2025-06-16 à 15 23 50"
src="https://github.com/user-attachments/assets/1824ff86-b5f1-47ad-8b5c-7ea84e0e3ac6"
/> <img width="400" alt="Capture d’écran 2025-06-16 à 15 25 40"
src="https://github.com/user-attachments/assets/f2daba64-0982-40ee-9662-a23f86385a8f"
/>

- Remove `isRelative` from date field option for workflows
This commit is contained in:
Thomas Trompette
2025-06-16 16:32:10 +02:00
committed by GitHub
parent e922843afb
commit 22dade6ef7
9 changed files with 255 additions and 105 deletions

View File

@ -1,3 +1,4 @@
import { ActionButton } from '@/action-menu/actions/display/components/ActionButton';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter';
@ -17,7 +18,6 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { isDefined } from 'twenty-shared/utils';
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
import { LightButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import { v4 } from 'uuid';
@ -137,9 +137,13 @@ export const AdvancedFilterAddFilterRuleSelect = ({
if (!isFilterRuleGroupOptionVisible) {
return (
<LightButton
Icon={IconPlus}
title="Add filter rule"
<ActionButton
action={{
Icon: IconPlus,
label: 'Add rule',
shortLabel: 'Add rule',
key: 'add-rule',
}}
onClick={handleAddFilter}
/>
);
@ -149,7 +153,14 @@ export const AdvancedFilterAddFilterRuleSelect = ({
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<LightButton Icon={IconPlus} title="Add filter rule" />
<ActionButton
action={{
Icon: IconPlus,
label: 'Add filter rule',
shortLabel: 'Add filter rule',
key: 'add-filter-rule',
}}
/>
}
dropdownComponents={
<DropdownContent>

View File

@ -1,39 +1,23 @@
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand';
import { AdvancedFilterRecordFilterOperandSelectContent } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation';
const StyledContainer = styled.div<{ width?: string }>`
width: ${({ width }) => width ?? '100px'};
const StyledContainer = styled.div`
width: 100px;
`;
type AdvancedFilterRecordFilterOperandSelectProps = {
recordFilterId: string;
widthFromProps?: string;
};
export const AdvancedFilterRecordFilterOperandSelect = ({
recordFilterId,
widthFromProps,
}: AdvancedFilterRecordFilterOperandSelectProps) => {
const dropdownId = `advanced-filter-view-filter-operand-${recordFilterId}`;
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
@ -44,17 +28,6 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
const isDisabled = !filter?.fieldMetadataId;
const { closeDropdown } = useDropdown(dropdownId);
const { applyObjectFilterDropdownOperand } =
useApplyObjectFilterDropdownOperand();
const handleOperandChange = (operand: ViewFilterOperand) => {
closeDropdown();
applyObjectFilterDropdownOperand(operand);
};
const filterType = filter?.type;
const operandsForFilterType = isDefined(filterType)
@ -64,12 +37,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
})
: [];
const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
dropdownId,
);
if (isDisabled === true) {
if (isDisabled) {
return (
<SelectControl
selectedOption={{
@ -84,53 +52,11 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
}
return (
<StyledContainer width={widthFromProps}>
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: filter.operand
? getOperandLabel(filter.operand)
: 'Select operand',
value: null,
}}
/>
}
dropdownComponents={
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={dropdownId}
selectableItemIdArray={operandsForFilterType.map(
(operand) => operand,
)}
selectableListInstanceId={dropdownId}
>
{operandsForFilterType.map((filterOperand, index) => (
<SelectableListItem
itemId={filterOperand}
key={`select-filter-operand-${index}`}
onEnter={() => {
handleOperandChange(filterOperand);
}}
>
<MenuItem
focused={selectedItemId === filterOperand}
onClick={() => {
handleOperandChange(filterOperand);
}}
text={getOperandLabel(filterOperand)}
/>
</SelectableListItem>
))}
</SelectableList>
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
<StyledContainer>
<AdvancedFilterRecordFilterOperandSelectContent
recordFilterId={recordFilterId}
filter={filter}
operandsForFilterType={operandsForFilterType}
/>
</StyledContainer>
);

View File

@ -0,0 +1,98 @@
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { MenuItem } from 'twenty-ui/navigation';
type AdvancedFilterRecordFilterOperandSelectContentProps = {
recordFilterId: string;
filter: RecordFilter;
operandsForFilterType: readonly RecordFilterOperand[];
};
export const AdvancedFilterRecordFilterOperandSelectContent = ({
recordFilterId,
filter,
operandsForFilterType,
}: AdvancedFilterRecordFilterOperandSelectContentProps) => {
const dropdownId = `advanced-filter-view-filter-operand-${recordFilterId}`;
const { closeDropdown } = useDropdown(dropdownId);
const { applyObjectFilterDropdownOperand } =
useApplyObjectFilterDropdownOperand();
const handleOperandChange = (operand: ViewFilterOperand) => {
closeDropdown();
applyObjectFilterDropdownOperand(operand);
};
const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
dropdownId,
);
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
value: null,
}}
/>
}
dropdownComponents={
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={dropdownId}
selectableItemIdArray={operandsForFilterType.map(
(operand) => operand,
)}
selectableListInstanceId={dropdownId}
>
{operandsForFilterType.map((filterOperand, index) => (
<SelectableListItem
itemId={filterOperand}
key={`select-filter-operand-${index}`}
onEnter={() => {
handleOperandChange(filterOperand);
}}
>
<MenuItem
focused={selectedItemId === filterOperand}
onClick={() => {
handleOperandChange(filterOperand);
}}
text={getOperandLabel(filterOperand)}
/>
</SelectableListItem>
))}
</SelectableList>
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
/>
);
};

View File

@ -0,0 +1,58 @@
import { useMemo } from 'react';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput';
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { IconComponentProps } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
export const FormCountryMultiSelectInput = ({
onChange,
defaultValue,
label,
readonly = false,
VariablePicker,
}: {
onChange: (value: string | FieldMultiSelectValue) => void;
defaultValue?: string | FieldMultiSelectValue;
label?: string;
readonly?: boolean;
VariablePicker?: VariablePickerComponent;
}) => {
const countries = useCountries();
const options: SelectOption[] = useMemo(
() =>
countries.map<SelectOption>(({ countryName, Flag }) => ({
label: countryName,
value: countryName,
Icon: (props: IconComponentProps) =>
Flag({ width: props.size, height: props.size }),
})),
[countries],
);
const onCountryChange = (value: string | FieldMultiSelectValue) => {
if (readonly) {
return;
}
if (value === null) {
onChange('');
} else {
onChange(value);
}
};
return (
<FormMultiSelectFieldInput
label={label}
onChange={onCountryChange}
options={options}
defaultValue={defaultValue}
readonly={readonly}
VariablePicker={VariablePicker}
/>
);
};

View File

@ -367,7 +367,9 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
subFieldName,
)
) {
const parsedCurrencyCodes = JSON.parse(recordFilter.value) as string[];
const parsedCurrencyCodes = arrayOfStringsOrVariablesSchema.parse(
recordFilter.value,
);
if (parsedCurrencyCodes.length === 0) return undefined;
@ -543,14 +545,11 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
};
} else {
if (subFieldName === 'addressCountry') {
const parsedCountryCodes = JSON.parse(
const parsedCountryCodes = arrayOfStringsOrVariablesSchema.parse(
recordFilter.value,
) as string[];
);
if (
recordFilter.value === '[]' ||
parsedCountryCodes.length === 0
) {
if (parsedCountryCodes.length === 0) {
return {};
}

View File

@ -150,7 +150,7 @@ export const COMPOSITE_FIELD_FILTER_OPERANDS_MAP = {
export const getRecordFilterOperands = ({
filterType,
subFieldName,
}: GetRecordFilterOperandsParams) => {
}: GetRecordFilterOperandsParams): readonly RecordFilterOperand[] => {
switch (filterType) {
case 'TEXT':
case 'EMAILS':

View File

@ -1,5 +1,4 @@
import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButton';
import { AdvancedFilterRecordFilterOperandSelect } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelect';
import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown';
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
@ -8,6 +7,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { WorkflowAdvancedFilterDropdownColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterDropdownColumn';
import { WorkflowAdvancedFilterValueFormInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput';
import { WorkflowAdvancedFilterLogicalOperatorCell } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell';
import { WorkflowAdvancedFilterRecordFilterOperandSelect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterOperandSelect';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
@ -47,9 +47,8 @@ export const WorkflowAdvancedFilterRecordFilterColumn = ({
<AdvancedFilterFieldSelectDropdownButton
recordFilterId={recordFilter.id}
/>
<AdvancedFilterRecordFilterOperandSelect
<WorkflowAdvancedFilterRecordFilterOperandSelect
recordFilterId={recordFilter.id}
widthFromProps="auto"
/>
<WorkflowAdvancedFilterValueFormInput
recordFilterId={recordFilter.id}

View File

@ -0,0 +1,57 @@
import { AdvancedFilterRecordFilterOperandSelectContent } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
type WorkflowAdvancedFilterRecordFilterOperandSelectProps = {
recordFilterId: string;
};
export const WorkflowAdvancedFilterRecordFilterOperandSelect = ({
recordFilterId,
}: WorkflowAdvancedFilterRecordFilterOperandSelectProps) => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const filter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const isDisabled = !filter?.fieldMetadataId;
const filterType = filter?.type;
const operandsForFilterType = isDefined(filterType)
? getRecordFilterOperands({
filterType,
subFieldName: filter?.subFieldName,
}).filter((operand) => operand !== RecordFilterOperand.IsRelative)
: [];
if (isDisabled) {
return (
<SelectControl
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
value: null,
}}
isDisabled
/>
);
}
return (
<AdvancedFilterRecordFilterOperandSelectContent
recordFilterId={recordFilterId}
filter={filter}
operandsForFilterType={operandsForFilterType}
/>
);
};

View File

@ -1,9 +1,10 @@
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { FormCountryCodeSelectInput } from '@/object-record/record-field/form-types/components/FormCountryCodeSelectInput';
import { FormCountrySelectInput } from '@/object-record/record-field/form-types/components/FormCountrySelectInput';
import { FormCountryMultiSelectInput } from '@/object-record/record-field/form-types/components/FormCountryMultiSelectInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput';
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { JsonValue } from 'type-fest';
@ -25,8 +26,8 @@ export const WorkflowAdvancedFilterValueFormCompositeFieldInput = ({
<>
{filterType === 'ADDRESS' ? (
subFieldNameUsedInDropdown === 'addressCountry' ? (
<FormCountrySelectInput
selectedCountryName={recordFilter.value}
<FormCountryMultiSelectInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
/>
@ -39,10 +40,11 @@ export const WorkflowAdvancedFilterValueFormCompositeFieldInput = ({
)
) : filterType === 'CURRENCY' ? (
recordFilter.subFieldName === 'currencyCode' ? (
<FormCountryCodeSelectInput
selectedCountryCode={recordFilter.value}
<FormMultiSelectFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
options={CURRENCIES}
/>
) : recordFilter.subFieldName === 'amountMicros' ? (
<FormNumberFieldInput