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
@@ -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"
/>
@@ -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} />
);
};
@@ -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>
</>
);
};
@@ -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
@@ -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>
</>
);
};
@@ -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>
);
};
@@ -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,
};
};
@@ -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,
};
};
@@ -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,
};
};
@@ -27,11 +27,11 @@ export const MultipleFiltersDropdownContent = ({
const shouldShowCompositeSelectionSubMenu =
objectFilterDropdownIsSelectingCompositeField;
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
const shouldShowFilterInput = objectFilterDropdownFilterIsSelected;
return (
<>
{shoudShowFilterInput ? (
{shouldShowFilterInput ? (
<ObjectFilterOperandSelectAndInput
filterDropdownId={filterDropdownId}
/>
@@ -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
@@ -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,
});
@@ -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,
});
@@ -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}
/>
);
};
@@ -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,
});
@@ -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,
});
@@ -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,
});