Fix bug and refactored advanced filter field selection dropdown (#11103)

This PR is a first step towards isolating each filter dropdown use case,
here we isolate advanced filter field selection dropdown from view bar
filter field selection dropdown.

## Isolation of advanced filter field selection logic

We reimplement the previously generic logic into
AdvancedFilterFieldSelectMenu and AdvancedFilterSubFieldSelectMenu
components.

For now it contains duplicated code but, the end goal is to factorize
what's common to all object filter dropdowns in small hooks where
possible, at the end of the code path isolation first step, which will
be done for applyFilter and selectFilter logic that will be able to be
deleted after code path isolation.

A new component ObjectFilterDropdownFilterSelectMenuItemV2 has been
created to expose an onClick method instead of computing logic that
tries to guess where it is located, which allows to verticalize what
happens when we select a field in each specific use case, one layer
above.

Created the hook useSelectFieldUsedInAdvancedFilterDropdown which
contains the logic for field selection for advanced filter field select
dropdown specific use case, the first example of a good verticalized and
unique place to handle field selection in the context of advanced
filter.

The naming useAdvancedFilterDropdown was lying and is now
useAdvancedFilterFieldSelectDropdown which is now self-explanatory.

useAdvancedFilterFieldSelectDropdown has been removed from the main
object filter dropdown, thus isolating advanced filters field select
dropdown from the generic object filter field select dropdown.

## Miscellaneous

Removed states that were used in the generic filter dropdown to guess
whether it was in advanced filter context or not.
This commit is contained in:
Lucas Bordeau
2025-03-24 15:18:53 +01:00
committed by GitHub
parent 8b2a90dea1
commit e83e7b3b40
17 changed files with 529 additions and 230 deletions

View File

@ -1,5 +1,5 @@
import { AdvancedFilterFieldSelectDrodownContent } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDrodownContent';
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { AdvancedFilterFieldSelectDropdownContent } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent';
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
@ -11,15 +11,15 @@ const StyledContainer = styled.div`
flex: 2;
`;
type AdvancedFilterFieldSelectDrodownButtonProps = {
type AdvancedFilterFieldSelectDropdownButtonProps = {
recordFilterId: string;
};
export const AdvancedFilterFieldSelectDrodownButton = ({
export const AdvancedFilterFieldSelectDropdownButton = ({
recordFilterId,
}: AdvancedFilterFieldSelectDrodownButtonProps) => {
const { advancedFilterDropdownId } =
useAdvancedFilterDropdown(recordFilterId);
}: AdvancedFilterFieldSelectDropdownButtonProps) => {
const { advancedFilterFieldSelectDropdownId } =
useAdvancedFilterFieldSelectDropdown(recordFilterId);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
@ -34,7 +34,7 @@ export const AdvancedFilterFieldSelectDrodownButton = ({
return (
<StyledContainer>
<Dropdown
dropdownId={advancedFilterDropdownId}
dropdownId={advancedFilterFieldSelectDropdownId}
clickableComponent={
<SelectControl
selectedOption={{
@ -43,8 +43,12 @@ export const AdvancedFilterFieldSelectDrodownButton = ({
}}
/>
}
dropdownComponents={<AdvancedFilterFieldSelectDrodownContent />}
dropdownHotkeyScope={{ scope: advancedFilterDropdownId }}
dropdownComponents={
<AdvancedFilterFieldSelectDropdownContent
recordFilterId={recordFilterId}
/>
}
dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>

View File

@ -1,9 +1,15 @@
import { ObjectFilterDropdownFilterSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
import { AdvancedFilterFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu';
import { AdvancedFilterSubFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
export const AdvancedFilterFieldSelectDrodownContent = () => {
type AdvancedFilterFieldSelectDropdownContentProps = {
recordFilterId: string;
};
export const AdvancedFilterFieldSelectDropdownContent = ({
recordFilterId,
}: AdvancedFilterFieldSelectDropdownContentProps) => {
const [objectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
@ -13,8 +19,8 @@ export const AdvancedFilterFieldSelectDrodownContent = () => {
objectFilterDropdownIsSelectingCompositeField;
return shouldShowCompositeSelectionSubMenu ? (
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
<AdvancedFilterSubFieldSelectMenu recordFilterId={recordFilterId} />
) : (
<ObjectFilterDropdownFilterSelect />
<AdvancedFilterFieldSelectMenu recordFilterId={recordFilterId} />
);
};

View File

@ -0,0 +1,182 @@
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { AdvancedFilterFieldSelectSearchInput } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectSearchInput';
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
import { ObjectFilterDropdownFilterSelectMenuItemV2 } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItemV2';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
type AdvancedFilterFieldSelectMenuProps = {
recordFilterId: string;
};
export const AdvancedFilterFieldSelectMenu = ({
recordFilterId,
}: AdvancedFilterFieldSelectMenuProps) => {
const { recordIndexId } = useRecordIndexContextOrThrow();
const { closeAdvancedFilterFieldSelectDropdown } =
useAdvancedFilterFieldSelectDropdown(recordFilterId);
const [objectFilterDropdownSearchInput] = useRecoilComponentStateV2(
objectFilterDropdownSearchInputComponentState,
);
const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext();
const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector,
recordIndexId,
);
const visibleColumnsIds = visibleTableColumns.map(
(column) => column.fieldMetadataId,
);
const filteredSearchInputFieldMetadataItems =
filterableFieldMetadataItems.filter((fieldMetadataItem) =>
fieldMetadataItem.label
.toLocaleLowerCase()
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()),
);
const visibleColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems
.sort((a, b) => {
return visibleColumnsIds.indexOf(a.id) - visibleColumnsIds.indexOf(b.id);
})
.filter((fieldMetadataItem) =>
visibleColumnsIds.includes(fieldMetadataItem.id),
);
const hiddenColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems
.sort((a, b) => a.label.localeCompare(b.label))
.filter(
(fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
);
const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map(
(fieldMetadataItem) => fieldMetadataItem.id,
);
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
const { selectFieldUsedInAdvancedFilterDropdown } =
useSelectFieldUsedInAdvancedFilterDropdown();
const [, setObjectFilterDropdownSubMenuFieldType] = useRecoilComponentStateV2(
objectFilterDropdownSubMenuFieldTypeComponentState,
);
const [, setObjectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
fieldMetadataItemIdUsedInDropdownComponentState,
);
const handleEnter = (fieldMetadataItemId: string) => {
const selectedFieldMetadataItem = filterableFieldMetadataItems.find(
(fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId,
);
if (!isDefined(selectedFieldMetadataItem)) {
return;
}
handleFieldMetadataItemSelect(selectedFieldMetadataItem);
};
const handleFieldMetadataItemSelect = (
selectedFieldMetadataItem: FieldMetadataItem,
) => {
resetSelectedItem();
const filterType = getFilterTypeFromFieldType(
selectedFieldMetadataItem.type,
);
if (isCompositeField(filterType)) {
setObjectFilterDropdownSubMenuFieldType(filterType);
setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
setObjectFilterDropdownIsSelectingCompositeField(true);
} else {
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: selectedFieldMetadataItem.id,
recordFilterId,
});
closeAdvancedFilterFieldSelectDropdown();
}
};
const shouldShowSeparator =
visibleColumnsFieldMetadataItems.length > 0 &&
hiddenColumnsFieldMetadataItems.length > 0;
return (
<>
<AdvancedFilterFieldSelectSearchInput />
<SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={selectableFieldMetadataItemIds}
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
onEnter={handleEnter}
>
<DropdownMenuItemsContainer>
{visibleColumnsFieldMetadataItems.map(
(visibleFieldMetadataItem, index) => (
<SelectableItem
itemId={visibleFieldMetadataItem.id}
key={`visible-select-filter-${index}`}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={visibleFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableItem>
),
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{hiddenColumnsFieldMetadataItems.map(
(hiddenFieldMetadataItem, index) => (
<SelectableItem
itemId={hiddenFieldMetadataItem.id}
key={`hidden-select-filter-${index}`}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableItem>
),
)}
</DropdownMenuItemsContainer>
</SelectableList>
</>
);
};

View File

@ -1,5 +1,5 @@
import { AdvancedFilterDropdownRow } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownRow';
import { AdvancedFilterFieldSelectDrodownButton } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDrodownButton';
import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButton';
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';
@ -26,7 +26,7 @@ export const AdvancedFilterRecordFilterRow = ({
index={recordFilterIndex}
recordFilterGroup={recordFilterGroup}
/>
<AdvancedFilterFieldSelectDrodownButton
<AdvancedFilterFieldSelectDropdownButton
recordFilterId={recordFilter.id}
/>
<AdvancedFilterRecordFilterOperandSelect

View File

@ -0,0 +1,140 @@
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 { 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';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { IconApps, IconChevronLeft, MenuItem, useIcons } from 'twenty-ui';
type AdvancedFilterSubFieldSelectMenuProps = {
recordFilterId: string;
};
export const AdvancedFilterSubFieldSelectMenu = ({
recordFilterId,
}: AdvancedFilterSubFieldSelectMenuProps) => {
const { getIcon } = useIcons();
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
);
const [, setObjectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const [
objectFilterDropdownSubMenuFieldType,
setObjectFilterDropdownSubMenuFieldType,
] = useRecoilComponentStateV2(
objectFilterDropdownSubMenuFieldTypeComponentState,
);
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
fieldMetadataItemIdUsedInDropdownComponentState,
);
const { closeAdvancedFilterFieldSelectDropdown } =
useAdvancedFilterFieldSelectDropdown(recordFilterId);
const { selectFieldUsedInAdvancedFilterDropdown } =
useSelectFieldUsedInAdvancedFilterDropdown();
const handleSelectFilter = (
selectedFieldMetadataItem: FieldMetadataItem | null | undefined,
subFieldName?: string | null | undefined,
) => {
if (!isDefined(selectedFieldMetadataItem)) {
return;
}
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: selectedFieldMetadataItem.id,
recordFilterId,
subFieldName,
});
closeAdvancedFilterFieldSelectDropdown();
};
const handleSubMenuBack = () => {
setFieldMetadataItemIdUsedInDropdown(null);
setObjectFilterDropdownSubMenuFieldType(null);
setObjectFilterDropdownIsSelectingCompositeField(false);
setObjectFilterDropdownFilterIsSelected(false);
};
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
return null;
}
const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
objectFilterDropdownSubMenuFieldType
].filterableSubFields.sort((a, b) => a.localeCompare(b));
return (
<>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
onClick={handleSubMenuBack}
Icon={IconChevronLeft}
/>
}
>
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<MenuItem
key={`select-filter-${-1}`}
testId={`select-filter-${-1}`}
onClick={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
/>
{/* TODO: fix this with a backend field on ViewFilter for composite field filter */}
{fieldMetadataItemUsedInDropdown?.type === 'ACTOR' &&
options.map((subFieldName, index) => (
<MenuItem
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
if (isDefined(fieldMetadataItemUsedInDropdown)) {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}
}}
text={getCompositeSubFieldLabel(
objectFilterDropdownSubMenuFieldType,
subFieldName,
)}
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
/>
))}
</DropdownMenuItemsContainer>
</>
);
};

View File

@ -1,84 +0,0 @@
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { ObjectFilterDropdownFilterSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
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';
const StyledContainer = styled.div`
flex: 2;
`;
type AdvancedFilterViewFilterFieldSelectProps = {
viewFilterId: string;
};
export const AdvancedFilterViewFilterFieldSelect = ({
viewFilterId,
}: AdvancedFilterViewFilterFieldSelectProps) => {
const { advancedFilterDropdownId } = useAdvancedFilterDropdown(viewFilterId);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === viewFilterId,
);
const selectedFieldLabel = recordFilter?.label ?? '';
const setAdvancedFilterViewFilterId = useSetRecoilComponentStateV2(
advancedFilterViewFilterIdComponentState,
);
const setAdvancedFilterViewFilterGroupId = useSetRecoilComponentStateV2(
advancedFilterViewFilterGroupIdComponentState,
);
const [objectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const shouldShowCompositeSelectionSubMenu =
objectFilterDropdownIsSelectingCompositeField;
return (
<StyledContainer>
<Dropdown
dropdownId={advancedFilterDropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: selectedFieldLabel,
value: null,
}}
/>
}
onOpen={() => {
setAdvancedFilterViewFilterId(recordFilter?.id);
setAdvancedFilterViewFilterGroupId(recordFilter?.recordFilterGroupId);
}}
dropdownComponents={
shouldShowCompositeSelectionSubMenu ? (
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
) : (
<ObjectFilterDropdownFilterSelect />
)
}
dropdownHotkeyScope={{ scope: advancedFilterDropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>
</StyledContainer>
);
};

View File

@ -1,14 +0,0 @@
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
export const useAdvancedFilterDropdown = (viewFilterId?: string) => {
const advancedFilterDropdownId = `advanced-filter-view-filter-field-${viewFilterId}`;
const { closeDropdown: closeAdvancedFilterDropdown } = useDropdown(
advancedFilterDropdownId,
);
return {
closeAdvancedFilterDropdown,
advancedFilterDropdownId,
};
};

View File

@ -0,0 +1,14 @@
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
export const useAdvancedFilterFieldSelectDropdown = (viewFilterId?: string) => {
const advancedFilterFieldSelectDropdownId = `advanced-filter-view-filter-field-${viewFilterId}`;
const { closeDropdown: closeAdvancedFilterFieldSelectDropdown } = useDropdown(
advancedFilterFieldSelectDropdownId,
);
return {
closeAdvancedFilterFieldSelectDropdown,
advancedFilterFieldSelectDropdownId,
};
};

View File

@ -0,0 +1,110 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
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';
type SelectFilterParams = {
fieldMetadataItemId: string;
recordFilterId: string;
subFieldName?: string | null | undefined;
};
export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
selectedOperandInDropdownComponentState,
);
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
fieldMetadataItemIdUsedInDropdownComponentState,
);
const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2(
objectFilterDropdownSearchInputComponentState,
);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const setHotkeyScope = useSetHotkeyScope();
const { applyRecordFilter } = useApplyRecordFilter();
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2(
subFieldNameUsedInDropdownComponentState,
);
const selectFieldUsedInAdvancedFilterDropdown = ({
fieldMetadataItemId,
recordFilterId,
subFieldName,
}: SelectFilterParams) => {
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
const fieldMetadataItem = getFieldMetadataItemById(fieldMetadataItemId);
if (!isDefined(fieldMetadataItem)) {
return;
}
if (
fieldMetadataItem.type === 'RELATION' ||
fieldMetadataItem.type === 'SELECT'
) {
setHotkeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker);
}
const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
const firstOperand = getRecordFilterOperands({
filterType,
})[0];
setSelectedOperandInDropdown(firstOperand);
const { value, displayValue } = getInitialFilterValue(
filterType,
firstOperand,
);
const existingRecordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
applyRecordFilter({
id: recordFilterId,
fieldMetadataId: fieldMetadataItem.id,
displayValue,
operand: firstOperand,
value,
recordFilterGroupId: existingRecordFilter?.recordFilterGroupId,
type: filterType,
label: fieldMetadataItem.label,
subFieldName,
});
if (isDefined(subFieldName)) {
setSubFieldNameUsedInDropdown(subFieldName);
}
setObjectFilterDropdownSearchInput('');
};
return {
selectFieldUsedInAdvancedFilterDropdown,
};
};

View File

@ -27,11 +27,11 @@ export const MultipleFiltersDropdownContent = ({
const shouldShowCompositeSelectionSubMenu =
objectFilterDropdownIsSelectingCompositeField;
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
const shouldShowFilterInput = objectFilterDropdownFilterIsSelected;
return (
<>
{shoudShowFilterInput ? (
{shouldShowFilterInput ? (
<ObjectFilterOperandSelectAndInput
filterDropdownId={filterDropdownId}
/>

View File

@ -2,7 +2,6 @@ import styled from '@emotion/styled';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { AdvancedFilterButton } from '@/object-record/object-filter-dropdown/components/AdvancedFilterButton';
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
@ -21,7 +20,6 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
@ -65,17 +63,9 @@ export const ObjectFilterDropdownFilterSelect = ({
}: ObjectFilterDropdownFilterSelectProps) => {
const { recordIndexId } = useRecordIndexContextOrThrow();
const advancedFilterViewFilterId = useRecoilComponentValueV2(
advancedFilterViewFilterIdComponentState,
);
const [objectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput] =
useRecoilComponentStateV2(objectFilterDropdownSearchInputComponentState);
const { closeAdvancedFilterDropdown } = useAdvancedFilterDropdown(
advancedFilterViewFilterId,
);
const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext();
@ -136,11 +126,9 @@ export const ObjectFilterDropdownFilterSelect = ({
});
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
closeAdvancedFilterDropdown();
};
const shoudShowSeparator =
const shouldShowSeparator =
visibleColumnsFieldMetadataItems.length > 0 &&
hiddenColumnsFieldMetadataItems.length > 0;
@ -186,7 +174,7 @@ export const ObjectFilterDropdownFilterSelect = ({
</SelectableItem>
),
)}
{shoudShowSeparator && <DropdownMenuSeparator />}
{shouldShowSeparator && <DropdownMenuSeparator />}
{hiddenColumnsFieldMetadataItems.map(
(hiddenFieldMetadataItem, index) => (
<SelectableItem

View File

@ -1,9 +1,5 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
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';
@ -15,8 +11,6 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
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 { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
@ -72,20 +66,6 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
objectFilterDropdownSearchInputComponentState,
);
const advancedFilterViewFilterId = useRecoilComponentValueV2(
advancedFilterViewFilterIdComponentState,
);
const advancedFilterViewFilterGroupId = useRecoilComponentValueV2(
advancedFilterViewFilterGroupIdComponentState,
);
const { applyRecordFilter } = useApplyRecordFilter();
const { closeAdvancedFilterDropdown } = useAdvancedFilterDropdown(
advancedFilterViewFilterId,
);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
@ -109,30 +89,6 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
subFieldName: subFieldName,
})[0];
if (
isDefined(advancedFilterViewFilterId) &&
isDefined(advancedFilterViewFilterGroupId)
) {
closeAdvancedFilterDropdown();
const { value, displayValue } = getInitialFilterValue(
type,
defaultOperand,
);
applyRecordFilter({
id: advancedFilterViewFilterId,
fieldMetadataId: fieldMetadataItem.id,
value,
operand: defaultOperand,
displayValue,
type,
label: fieldMetadataItem.label,
recordFilterGroupId: advancedFilterViewFilterGroupId,
subFieldName: subFieldName,
});
}
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
setSubFieldNameUsedInDropdown(subFieldName);
@ -152,9 +108,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
duplicateFilterInCurrentRecordFilters,
);
const isSimpleFilter = !isDefined(advancedFilterViewFilterId);
if (isSimpleFilter && filterIsAlreadyInCurrentRecordFilters) {
if (filterIsAlreadyInCurrentRecordFilters) {
setSelectedFilter({
...duplicateFilterInCurrentRecordFilters,
});

View File

@ -1,6 +1,4 @@
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
@ -61,16 +59,8 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
selectedOperandInDropdownComponentState,
);
const advancedFilterViewFilterId = useRecoilComponentValueV2(
advancedFilterViewFilterIdComponentState,
);
const setHotkeyScope = useSetHotkeyScope();
const { closeAdvancedFilterDropdown } = useAdvancedFilterDropdown(
advancedFilterViewFilterId,
);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
@ -80,8 +70,6 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
);
const handleSelectFilter = (fieldMetadataItem: FieldMetadataItem) => {
closeAdvancedFilterDropdown();
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
@ -106,9 +94,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
duplicateFilterInCurrentRecordFilters,
);
const isSimpleFilter = !isDefined(advancedFilterViewFilterId);
if (isSimpleFilter && filterIsAlreadyInCurrentRecordFilters) {
if (filterIsAlreadyInCurrentRecordFilters) {
setSelectedFilter({
...duplicateFilterInCurrentRecordFilters,
});

View File

@ -0,0 +1,48 @@
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilValue } from 'recoil';
import { MenuItemSelect, useIcons } from 'twenty-ui';
export type ObjectFilterDropdownFilterSelectMenuItemV2Props = {
fieldMetadataItemToSelect: FieldMetadataItem;
onClick: (selectedFieldMetadataItem: FieldMetadataItem) => void;
};
export const ObjectFilterDropdownFilterSelectMenuItemV2 = ({
fieldMetadataItemToSelect,
onClick,
}: ObjectFilterDropdownFilterSelectMenuItemV2Props) => {
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList(
OBJECT_FILTER_DROPDOWN_ID,
);
const isSelectedItem = useRecoilValue(
isSelectedItemIdSelector(fieldMetadataItemToSelect.id),
);
const { getIcon } = useIcons();
const Icon = getIcon(fieldMetadataItemToSelect.icon);
const shouldShowSubMenu = isCompositeField(fieldMetadataItemToSelect.type);
const handleClick = () => {
resetSelectedItem();
onClick(fieldMetadataItemToSelect);
};
return (
<MenuItemSelect
selected={false}
hovered={isSelectedItem}
onClick={handleClick}
LeftIcon={Icon}
text={fieldMetadataItemToSelect.label}
hasSubMenu={shouldShowSubMenu}
/>
);
};

View File

@ -1,7 +1,5 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
@ -11,7 +9,6 @@ import { getRecordFilterOperands } from '@/object-record/record-filter/utils/get
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
@ -36,16 +33,6 @@ export const useSelectFilterUsedInDropdown = (componentInstanceId?: string) => {
componentInstanceId,
);
const advancedFilterViewFilterGroupId = useRecoilComponentValueV2(
advancedFilterViewFilterGroupIdComponentState,
componentInstanceId,
);
const advancedFilterViewFilterId = useRecoilComponentValueV2(
advancedFilterViewFilterIdComponentState,
componentInstanceId,
);
const setHotkeyScope = useSetHotkeyScope();
const { applyRecordFilter } = useApplyRecordFilter(componentInstanceId);
@ -83,16 +70,13 @@ export const useSelectFilterUsedInDropdown = (componentInstanceId?: string) => {
firstOperand,
);
const isAdvancedFilter = isDefined(advancedFilterViewFilterId);
if (isAdvancedFilter || value !== '') {
if (value !== '') {
applyRecordFilter({
id: advancedFilterViewFilterId ?? v4(),
id: v4(),
fieldMetadataId: fieldMetadataItem.id,
displayValue,
operand: firstOperand,
value,
recordFilterGroupId: advancedFilterViewFilterGroupId,
type: filterType,
label: fieldMetadataItem.label,
});

View File

@ -1,9 +0,0 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const advancedFilterViewFilterGroupIdComponentState =
createComponentStateV2<string | undefined>({
key: 'advancedFilterViewFilterGroupIdComponentState',
defaultValue: undefined,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});

View File

@ -1,10 +0,0 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const advancedFilterViewFilterIdComponentState = createComponentStateV2<
string | undefined
>({
key: 'advancedFilterViewFilterIdComponentState',
defaultValue: undefined,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});