Fix advanced filter dropdown input components (#11381)

This PR fixes the filter value input components that are used in
advanced dropdown filter, which slightly differ from the classic object
filter dropdown in the view bar.

We notably needed the same experience as other text inputs in the
application, for entering filter values.

## New text and number filter experience : 

<img width="681" alt="image"
src="https://github.com/user-attachments/assets/b373bb6b-dc00-4396-9294-9b866b91fa02"
/>

## New date filter experience : 

<img width="683" alt="image"
src="https://github.com/user-attachments/assets/8aea22e2-6f3b-4641-9f3d-6d7ba537bc27"
/>

To obtain the same experience for date input as in workflow forms, it
would require to duplicate or factorize a lot of complex code that
manipulates dates and user events with the input, it would be better
tackled in another issue related to a larger refactor effort :
https://github.com/twentyhq/core-team-issues/issues/736

Fixes https://github.com/twentyhq/core-team-issues/issues/674
This commit is contained in:
Lucas Bordeau
2025-04-04 14:45:21 +02:00
committed by GitHub
parent ad2357a6fd
commit 59e8e0633b
13 changed files with 478 additions and 63 deletions

View File

@ -1,21 +1,23 @@
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';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { v4 } from 'uuid';
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';
type AdvancedFilterAddFilterRuleSelectProps = {
recordFilterGroup: RecordFilterGroup;
@ -45,6 +47,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({
const { defaultFieldMetadataItemForFilter } =
useDefaultFieldMetadataItemForFilter();
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
const handleAddFilter = () => {
if (!isDefined(defaultFieldMetadataItemForFilter)) {
throw new Error('Missing default field metadata item for filter');
@ -56,7 +61,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({
defaultFieldMetadataItemForFilter.type,
);
upsertRecordFilter({
const newRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
type: filterType,
@ -68,7 +73,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({
recordFilterGroupId: recordFilterGroup.id,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItemForFilter.label,
});
};
upsertRecordFilter(newRecordFilter);
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
};
const handleAddFilterGroup = () => {
@ -97,7 +106,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({
defaultFieldMetadataItemForFilter.type,
);
upsertRecordFilter({
const newRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
type: filterType,
@ -109,7 +118,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({
recordFilterGroupId: newRecordFilterGroupId,
positionInRecordFilterGroup: 1,
label: defaultFieldMetadataItemForFilter.label,
});
};
upsertRecordFilter(newRecordFilter);
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
};
const isFilterRuleGroupOptionVisible = !isDefined(

View File

@ -0,0 +1,122 @@
import { v4 } from 'uuid';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
import {
resolveDateViewFilterValue,
VariableDateViewFilterValueDirection,
VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const AdvancedFilterDropdownDateInput = () => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const selectedOperandInDropdown = useRecoilComponentValueV2(
selectedOperandInDropdownComponentState,
);
const selectedFilter = useRecoilComponentValueV2(
selectedFilterComponentState,
);
const { applyRecordFilter } = useApplyRecordFilter();
const initialFilterValue = selectedFilter
? resolveDateViewFilterValue(selectedFilter)
: null;
const [internalDate, setInternalDate] = useState<Date | null>(
initialFilterValue instanceof Date ? initialFilterValue : null,
);
const isDateTimeInput =
fieldMetadataItemUsedInDropdown?.type === FieldMetadataType.DATE_TIME;
const handleAbsoluteDateChange = (newDate: Date | null) => {
setInternalDate(newDate);
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
const newDisplayValue = isDefined(newDate)
? newDate.toLocaleDateString()
: '';
applyRecordFilter({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
value: newDate?.toISOString() ?? '',
operand: selectedOperandInDropdown,
displayValue: newDisplayValue,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
});
};
const handleRelativeDateChange = (
relativeDate: {
direction: VariableDateViewFilterValueDirection;
amount?: number;
unit: VariableDateViewFilterValueUnit;
} | null,
) => {
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
const value = relativeDate
? computeVariableDateViewFilterValue(
relativeDate.direction,
relativeDate.amount,
relativeDate.unit,
)
: '';
applyRecordFilter({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
value,
operand: selectedOperandInDropdown,
displayValue: getRelativeDateDisplayValue(relativeDate),
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
});
};
const isRelativeOperand =
selectedOperandInDropdown === ViewFilterOperand.IsRelative;
const resolvedValue = selectedFilter
? resolveDateViewFilterValue(selectedFilter)
: null;
const relativeDate =
resolvedValue && !(resolvedValue instanceof Date)
? resolvedValue
: undefined;
return (
<DateTimePicker
relativeDate={relativeDate}
highlightedDateRange={relativeDate}
isRelative={isRelativeOperand}
date={internalDate}
onChange={handleAbsoluteDateChange}
onRelativeDateChange={handleRelativeDateChange}
isDateTimeInput={isDateTimeInput}
/>
);
};

View File

@ -0,0 +1,83 @@
import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect';
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { AdvancedFilterDropdownDateInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput';
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
type AdvancedFilterDropdownFilterInputProps = {
filterDropdownId?: string;
recordFilterId?: string;
};
export const AdvancedFilterDropdownFilterInput = ({
filterDropdownId,
recordFilterId,
}: AdvancedFilterDropdownFilterInputProps) => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
filterDropdownId,
);
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
subFieldNameUsedInDropdownComponentState,
filterDropdownId,
);
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
return null;
}
const filterType = getFilterTypeFromFieldType(
fieldMetadataItemUsedInDropdown.type,
);
const isActorSourceCompositeFilter = isFilterOnActorSourceSubField(
subFieldNameUsedInDropdown,
);
return (
<>
{filterType === 'RATING' && <ObjectFilterDropdownRatingInput />}
{DATE_FILTER_TYPES.includes(filterType) && (
<AdvancedFilterDropdownDateInput />
)}
{filterType === 'RELATION' && (
<>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} />
</>
)}
{filterType === 'ACTOR' &&
(isActorSourceCompositeFilter ? (
<>
<ObjectFilterDropdownSourceSelect />
</>
) : (
<>
<ObjectFilterDropdownTextInput />
</>
))}
{['SELECT', 'MULTI_SELECT'].includes(filterType) && (
<>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect />
</>
)}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
</>
);
};

View File

@ -0,0 +1,64 @@
import { useState } from 'react';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { v4 } from 'uuid';
export const AdvancedFilterDropdownNumberInput = () => {
const selectedOperandInDropdown = useRecoilComponentValueV2(
selectedOperandInDropdownComponentState,
);
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const selectedFilter = useRecoilComponentValueV2(
selectedFilterComponentState,
);
const { applyRecordFilter } = useApplyRecordFilter();
const [inputValue, setInputValue] = useState(
() => selectedFilter?.value || '',
);
const handleChange = (newValue: string) => {
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) {
return;
}
setInputValue(newValue);
applyRecordFilter({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
value: newValue,
operand: selectedOperandInDropdown,
displayValue: newValue,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
});
};
if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) {
return null;
}
return (
<TextInputV2
value={inputValue}
onChange={handleChange}
placeholder="Enter value"
fullWidth
type="number"
/>
);
};

View File

@ -0,0 +1,63 @@
import { useState } from 'react';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { v4 } from 'uuid';
export const AdvancedFilterDropdownTextInput = () => {
const selectedOperandInDropdown = useRecoilComponentValueV2(
selectedOperandInDropdownComponentState,
);
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const selectedFilter = useRecoilComponentValueV2(
selectedFilterComponentState,
);
const { applyRecordFilter } = useApplyRecordFilter();
const [inputValue, setInputValue] = useState(
() => selectedFilter?.value || '',
);
const handleChange = (newValue: string) => {
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) {
return;
}
setInputValue(newValue);
applyRecordFilter({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
value: newValue,
operand: selectedOperandInDropdown,
displayValue: newValue,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
});
};
if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) {
return null;
}
return (
<TextInputV2
value={inputValue}
onChange={handleChange}
placeholder="Enter value"
fullWidth
/>
);
};

View File

@ -3,7 +3,8 @@ import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advance
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRecordFilterOperandSelect } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelect';
import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown';
import { AdvancedFilterValueInputDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButton';
import { AdvancedFilterValueInput } from '@/object-record/advanced-filter/components/AdvancedFilterValueInput';
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
@ -19,7 +20,11 @@ export const AdvancedFilterRecordFilterRow = ({
}) => {
return (
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: `advanced-filter-${recordFilter.id}` }}
value={{
instanceId: getAdvancedFilterObjectFilterDropdownComponentInstanceId(
recordFilter.id,
),
}}
>
<AdvancedFilterDropdownRow>
<AdvancedFilterLogicalOperatorCell
@ -32,9 +37,7 @@ export const AdvancedFilterRecordFilterRow = ({
<AdvancedFilterRecordFilterOperandSelect
recordFilterId={recordFilter.id}
/>
<AdvancedFilterValueInputDropdownButton
recordFilterId={recordFilter.id}
/>
<AdvancedFilterValueInput recordFilterId={recordFilter.id} />
<AdvancedFilterRecordFilterOptionsDropdown
recordFilterId={recordFilter.id}
/>

View File

@ -1,18 +1,14 @@
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
@ -35,14 +31,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
fieldMetadataItemUsedInDropdownComponentSelector,
);
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
);
const [, setSubFieldNameUsedInDropdown] = useRecoilComponentStateV2(
subFieldNameUsedInDropdownComponentState,
);
const [, setObjectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
@ -55,10 +43,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
objectFilterDropdownSubMenuFieldTypeComponentState,
);
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
fieldMetadataItemIdUsedInDropdownComponentState,
);
const { closeAdvancedFilterFieldSelectDropdown } =
useAdvancedFilterFieldSelectDropdown(recordFilterId);
@ -83,11 +67,8 @@ export const AdvancedFilterSubFieldSelectMenu = ({
};
const handleSubMenuBack = () => {
setFieldMetadataItemIdUsedInDropdown(null);
setObjectFilterDropdownSubMenuFieldType(null);
setObjectFilterDropdownIsSelectingCompositeField(false);
setObjectFilterDropdownFilterIsSelected(false);
setSubFieldNameUsedInDropdown(null);
};
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {

View File

@ -1,64 +1,61 @@
import { AdvancedFilterDropdownFilterInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput';
import { AdvancedFilterDropdownTextInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput';
import { AdvancedFilterValueInputDropdownButtonClickableSelect } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
const StyledValueDropdownContainer = styled.div`
flex: 3;
`;
type AdvancedFilterValueInputDropdownButtonProps = {
type AdvancedFilterValueInputProps = {
recordFilterId: string;
};
export const AdvancedFilterValueInputDropdownButton = ({
export const AdvancedFilterValueInput = ({
recordFilterId,
}: AdvancedFilterValueInputDropdownButtonProps) => {
}: AdvancedFilterValueInputProps) => {
const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`;
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const filter = currentRecordFilters.find(
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const isDisabled = !filter?.fieldMetadataId || !filter.operand;
const isDisabled = !recordFilter?.fieldMetadataId || !recordFilter.operand;
const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2(
objectFilterDropdownSearchInputComponentState,
);
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
fieldMetadataItemIdUsedInDropdownComponentState,
);
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
selectedOperandInDropdownComponentState,
);
const setSelectedFilter = useSetRecoilComponentStateV2(
selectedFilterComponentState,
);
const operandHasNoInput =
filter && !configurableViewFilterOperands.has(filter.operand);
recordFilter && !configurableViewFilterOperands.has(recordFilter.operand);
const handleFilterValueDropdownClose = () => {
setObjectFilterDropdownSearchInput('');
};
const filterType = recordFilter?.type;
const dropdownContentOffset =
filterType === 'DATE' || filterType === 'DATE_TIME'
? ({ y: -33, x: 0 } satisfies DropdownOffset)
: DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET;
return (
<StyledValueDropdownContainer>
{operandHasNoInput ? (
@ -67,6 +64,10 @@ export const AdvancedFilterValueInputDropdownButton = ({
<AdvancedFilterValueInputDropdownButtonClickableSelect
recordFilterId={recordFilterId}
/>
) : isDefined(filterType) &&
(TEXT_FILTER_TYPES.includes(filterType) ||
NUMBER_FILTER_TYPES.includes(filterType)) ? (
<AdvancedFilterDropdownTextInput />
) : (
<Dropdown
dropdownId={dropdownId}
@ -75,16 +76,13 @@ export const AdvancedFilterValueInputDropdownButton = ({
recordFilterId={recordFilterId}
/>
}
onOpen={() => {
setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId);
setSelectedOperandInDropdown(filter.operand);
setSelectedFilter(filter);
}}
dropdownComponents={
<ObjectFilterDropdownFilterInput recordFilterId={filter.id} />
<AdvancedFilterDropdownFilterInput
recordFilterId={recordFilter.id}
/>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownOffset={dropdownContentOffset}
dropdownPlacement="bottom-start"
dropdownMenuWidth={280}
onClose={handleFilterValueDropdownClose}

View File

@ -3,10 +3,30 @@ import { getAdvancedFilterInputPlaceholderText } from '@/object-record/advanced-
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isNonEmptyString } from '@sniptt/guards';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
const StyledControlContainer = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(1)};
box-sizing: border-box;
height: ${({ theme }) => theme.spacing(8)};
max-width: 100%;
padding: 0 ${({ theme }) => theme.spacing(2)};
background-color: ${({ theme }) => theme.background.transparent.lighter};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.primary};
cursor: pointer;
text-align: left;
`;
type AdvancedFilterValueInputDropdownButtonClickableSelectProps = {
recordFilterId: string;
};
@ -42,7 +62,12 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
? placeholderText
: (recordFilter?.displayValue ?? '');
return (
const isDateTimeType =
recordFilter?.type === 'DATE' || recordFilter?.type === 'DATE_TIME';
return isDateTimeType ? (
<StyledControlContainer>{advancedFilterInputText}</StyledControlContainer>
) : (
<SelectControl
selectedOption={{
label: advancedFilterInputText,
@ -50,6 +75,7 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
disabled: isDisabled,
}}
textAccent={shouldUsePlaceholder ? 'placeholder' : 'default'}
isDisabled={isDateTimeType}
/>
);
};

View File

@ -0,0 +1,47 @@
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilCallback } from 'recoil';
export const useSetRecordFilterUsedInAdvancedFilterDropdownRow = () => {
const setRecordFilterUsedInAdvancedFilterDropdownRow = useRecoilCallback(
({ set }) =>
(recordFilter: RecordFilter) => {
const advancedFilterRowObjectFilterDropdownComponentInstanceId =
getAdvancedFilterObjectFilterDropdownComponentInstanceId(
recordFilter.id,
);
set(
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
instanceId:
advancedFilterRowObjectFilterDropdownComponentInstanceId,
}),
recordFilter.fieldMetadataId,
);
set(
selectedOperandInDropdownComponentState.atomFamily({
instanceId:
advancedFilterRowObjectFilterDropdownComponentInstanceId,
}),
recordFilter.operand,
);
set(
selectedFilterComponentState.atomFamily({
instanceId:
advancedFilterRowObjectFilterDropdownComponentInstanceId,
}),
recordFilter,
);
},
[],
);
return {
setRecordFilterUsedInAdvancedFilterDropdownRow,
};
};

View File

@ -0,0 +1,5 @@
export const getAdvancedFilterObjectFilterDropdownComponentInstanceId = (
recordFilterId: string,
) => {
return `advanced-filter-${recordFilterId}`;
};

View File

@ -8,6 +8,8 @@ import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
@ -16,11 +18,11 @@ import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLog
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { Pill } from 'twenty-ui/components';
import { IconFilter } from 'twenty-ui/display';
import { MenuItemLeftContent, StyledMenuItemBase } from 'twenty-ui/navigation';
import { Pill } from 'twenty-ui/components';
import { v4 } from 'uuid';
export const StyledContainer = styled.div`
align-items: center;
@ -80,6 +82,9 @@ export const AdvancedFilterButton = () => {
currentRecordFilterGroupsComponentState,
);
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
const handleClick = () => {
if (!isDefined(currentView)) {
throw new Error('Missing current view id');
@ -118,7 +123,7 @@ export const AdvancedFilterButton = () => {
filterType,
})[0];
upsertRecordFilter({
const newRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: defaultFieldMetadataItem.id,
operand: firstOperand,
@ -128,7 +133,11 @@ export const AdvancedFilterButton = () => {
type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type),
label: defaultFieldMetadataItem.label,
positionInRecordFilterGroup: 1,
});
};
upsertRecordFilter(newRecordFilter);
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
}
closeObjectFilterDropdown();

View File

@ -7,6 +7,7 @@ import { SelectOption } from 'twenty-ui/input';
export type SelectControlTextAccent = 'default' | 'placeholder';
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
const StyledControlContainer = styled.div<{
disabled?: boolean;
hasIcon: boolean;