diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterCompositeSubFieldSelectMenu.tsx
similarity index 83%
rename from packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx
rename to packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterCompositeSubFieldSelectMenu.tsx
index 5fc3a0af8e3..923726f6d19 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterCompositeSubFieldSelectMenu.tsx
@@ -1,16 +1,6 @@
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
-
-import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
-import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
-import { AdvancedFilterRelationSubMenu } from '@/object-record/advanced-filter/components/AdvancedFilterRelationSubMenu';
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
-import { RELATION_SUB_MENU_FIELD_TYPE } from '@/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
@@ -23,21 +13,26 @@ import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/Com
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
+import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
+import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { t } from '@lingui/core/macro';
+import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft, useIcons } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
-type AdvancedFilterSubFieldSelectMenuProps = {
+type AdvancedFilterCompositeSubFieldSelectMenuProps = {
recordFilterId: string;
};
-export const AdvancedFilterSubFieldSelectMenu = ({
+export const AdvancedFilterCompositeSubFieldSelectMenu = ({
recordFilterId,
-}: AdvancedFilterSubFieldSelectMenuProps) => {
+}: AdvancedFilterCompositeSubFieldSelectMenuProps) => {
const { getIcon } = useIcons();
const fieldMetadataItemUsedInDropdown = useAtomComponentSelectorValue(
@@ -63,20 +58,14 @@ export const AdvancedFilterSubFieldSelectMenu = ({
const handleSelectFilter = ({
fieldMetadataItem,
subFieldName,
- relationTargetFieldMetadataItem,
}: {
fieldMetadataItem: FieldMetadataItem;
subFieldName?: CompositeFieldSubFieldName | null;
- relationTargetFieldMetadataItem?: FieldMetadataItem | null;
}) => {
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: fieldMetadataItem.id,
recordFilterId,
subFieldName,
- relationTargetFieldMetadataItem,
- // The value picker that opens next is for a sub-field or target
- // field, not the source — its dropdown manages its own focus.
- skipFocusPush: true,
});
closeAdvancedFilterFieldSelectDropdown();
@@ -99,35 +88,8 @@ export const AdvancedFilterSubFieldSelectMenu = ({
return null;
}
- if (
- objectFilterDropdownSubMenuFieldType === RELATION_SUB_MENU_FIELD_TYPE &&
- isDefined(fieldMetadataItemUsedInDropdown) &&
- isManyToOneRelationField(fieldMetadataItemUsedInDropdown)
- ) {
- return (
-
- );
- }
-
- const compositeSubMenuFieldType =
- objectFilterDropdownSubMenuFieldType === RELATION_SUB_MENU_FIELD_TYPE
- ? null
- : objectFilterDropdownSubMenuFieldType;
-
- if (!isDefined(compositeSubMenuFieldType)) {
- return null;
- }
-
const subFieldNames = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
- compositeSubMenuFieldType
+ objectFilterDropdownSubMenuFieldType
].subFields
.filter((subField) => subField.isFilterable === true)
.map((subField) => subField.subFieldName);
@@ -216,7 +178,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
});
}}
text={getCompositeSubFieldLabel(
- compositeSubMenuFieldType,
+ objectFilterDropdownSubMenuFieldType,
subFieldName,
)}
LeftIcon={getIcon(
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent.tsx
index 0c37640650d..12f3539d717 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownContent.tsx
@@ -1,7 +1,9 @@
+import { AdvancedFilterCompositeSubFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterCompositeSubFieldSelectMenu';
import { AdvancedFilterFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu';
-import { AdvancedFilterSubFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu';
+import { AdvancedFilterRelationTargetFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterRelationTargetFieldSelectMenu';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
-import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
+import { objectFilterDropdownIsSelectingRelationTargetFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
type AdvancedFilterFieldSelectDropdownContentProps = {
recordFilterId: string;
@@ -10,16 +12,29 @@ type AdvancedFilterFieldSelectDropdownContentProps = {
export const AdvancedFilterFieldSelectDropdownContent = ({
recordFilterId,
}: AdvancedFilterFieldSelectDropdownContentProps) => {
- const [objectFilterDropdownIsSelectingCompositeField] = useAtomComponentState(
+ const isSelectingCompositeField = useAtomComponentStateValue(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
- const shouldShowCompositeSelectionSubMenu =
- objectFilterDropdownIsSelectingCompositeField;
-
- return shouldShowCompositeSelectionSubMenu ? (
-
- ) : (
-
+ const isSelectingRelationTargetField = useAtomComponentStateValue(
+ objectFilterDropdownIsSelectingRelationTargetFieldComponentState,
);
+
+ if (isSelectingRelationTargetField) {
+ return (
+
+ );
+ }
+
+ if (isSelectingCompositeField) {
+ return (
+
+ );
+ }
+
+ return ;
};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
index 48409771e40..b742e6a2f80 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
@@ -16,9 +16,8 @@ import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/co
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
-import { RELATION_SUB_MENU_FIELD_TYPE } from '@/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType';
+import { objectFilterDropdownIsSelectingRelationTargetFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
-import { type ObjectFilterDropdownSubMenuFieldType } from '@/object-record/record-filter/types/ObjectFilterDropdownSubMenuFieldType';
import { isCompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFilterableFieldType';
import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
@@ -26,6 +25,8 @@ import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/h
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
+import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
+import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { useLingui } from '@lingui/react/macro';
@@ -102,10 +103,17 @@ export const AdvancedFilterFieldSelectMenu = ({
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
+ const [, setObjectFilterDropdownIsSelectingRelationTargetField] =
+ useAtomComponentState(
+ objectFilterDropdownIsSelectingRelationTargetFieldComponentState,
+ );
+
const setFieldMetadataItemIdUsedInDropdown = useSetAtomComponentState(
fieldMetadataItemIdUsedInDropdownComponentState,
);
+ const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+
const handleFieldMetadataItemSelect = (
selectedFieldMetadataItem: FieldMetadataItem,
) => {
@@ -115,30 +123,50 @@ export const AdvancedFilterFieldSelectMenu = ({
selectedFieldMetadataItem.type,
);
- const subMenuType: ObjectFilterDropdownSubMenuFieldType | null =
- isManyToOneRelationField(selectedFieldMetadataItem)
- ? RELATION_SUB_MENU_FIELD_TYPE
- : isCompositeFilterableFieldType(filterType)
- ? filterType
- : null;
+ const isRelationTraversalField = isManyToOneRelationField(
+ selectedFieldMetadataItem,
+ );
+
+ const compositeSubMenuFieldType =
+ !isRelationTraversalField && isCompositeFilterableFieldType(filterType)
+ ? filterType
+ : null;
- // Sub-menus (composite or relation traversal) drive their own focus
- // scope; pushing the source field id on the focus stack would shadow
- // their selectable list hotkeys.
selectFieldUsedInAdvancedFilterDropdown({
fieldMetadataItemId: selectedFieldMetadataItem.id,
recordFilterId,
- skipFocusPush: subMenuType !== null,
});
- if (subMenuType === null) {
- closeAdvancedFilterFieldSelectDropdown();
+ if (isRelationTraversalField) {
+ setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
+ setObjectFilterDropdownIsSelectingRelationTargetField(true);
return;
}
- setObjectFilterDropdownSubMenuFieldType(subMenuType);
- setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
- setObjectFilterDropdownIsSelectingCompositeField(true);
+ if (compositeSubMenuFieldType !== null) {
+ setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
+ setObjectFilterDropdownSubMenuFieldType(compositeSubMenuFieldType);
+ setObjectFilterDropdownIsSelectingCompositeField(true);
+ return;
+ }
+
+ // Leaf RELATION/SELECT fields open a value picker keyed by the source
+ // field id — push it on the focus stack so the picker's keyboard
+ // hotkeys are active when it opens.
+ if (
+ selectedFieldMetadataItem.type === 'RELATION' ||
+ selectedFieldMetadataItem.type === 'SELECT'
+ ) {
+ pushFocusItemToFocusStack({
+ focusId: selectedFieldMetadataItem.id,
+ component: {
+ type: FocusComponentType.DROPDOWN,
+ instanceId: selectedFieldMetadataItem.id,
+ },
+ });
+ }
+
+ closeAdvancedFilterFieldSelectDropdown();
};
const shouldShowVisibleFields = visibleFieldMetadataItems.length > 0;
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationSubMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationSubMenu.tsx
deleted file mode 100644
index 6fb0b2ed04a..00000000000
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationSubMenu.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
-import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
-import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
-import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
-import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
-import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
-import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
-import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
-import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
-import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { IconChevronLeft, useIcons } from 'twenty-ui/display';
-import { MenuItem } from 'twenty-ui/navigation';
-
-type AdvancedFilterRelationSubMenuProps = {
- recordFilterId: string;
- relationFieldMetadataItem: FieldMetadataItem;
- targetObjectMetadataId: string;
- onBack: () => void;
- onSelectTargetField: (params: {
- fieldMetadataItem: FieldMetadataItem;
- relationTargetFieldMetadataItem: FieldMetadataItem;
- }) => void;
-};
-
-export const AdvancedFilterRelationSubMenu = ({
- recordFilterId,
- relationFieldMetadataItem,
- targetObjectMetadataId,
- onBack,
- onSelectTargetField,
-}: AdvancedFilterRelationSubMenuProps) => {
- const { getIcon } = useIcons();
-
- const { advancedFilterFieldSelectDropdownId } =
- useAdvancedFilterFieldSelectDropdown(recordFilterId);
-
- const selectedItemId = useAtomComponentStateValue(
- selectedItemIdComponentState,
- advancedFilterFieldSelectDropdownId,
- );
-
- const { filterableFieldMetadataItems: relationTargetFields } =
- useFilterableFieldMetadataItems(targetObjectMetadataId);
-
- const selectableItemIdArray = relationTargetFields.map((field) => field.id);
-
- return (
-
-
- }
- >
- {relationFieldMetadataItem.label}
-
-
-
- {relationTargetFields.map((targetField, index) => (
- {
- onSelectTargetField({
- fieldMetadataItem: relationFieldMetadataItem,
- relationTargetFieldMetadataItem: targetField,
- });
- }}
- >
-
- ))}
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationTargetFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationTargetFieldSelectMenu.tsx
new file mode 100644
index 00000000000..15727c3a569
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRelationTargetFieldSelectMenu.tsx
@@ -0,0 +1,151 @@
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
+import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
+import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
+import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
+import { objectFilterDropdownIsSelectingRelationTargetFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState';
+import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
+import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
+import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
+import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
+import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
+import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
+import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
+import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
+import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
+import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
+import { isDefined } from 'twenty-shared/utils';
+import { IconChevronLeft, useIcons } from 'twenty-ui/display';
+import { MenuItem } from 'twenty-ui/navigation';
+
+type AdvancedFilterRelationTargetFieldSelectMenuProps = {
+ recordFilterId: string;
+};
+
+export const AdvancedFilterRelationTargetFieldSelectMenu = ({
+ recordFilterId,
+}: AdvancedFilterRelationTargetFieldSelectMenuProps) => {
+ const { getIcon } = useIcons();
+
+ const sourceFieldMetadataItem = useAtomComponentSelectorValue(
+ fieldMetadataItemUsedInDropdownComponentSelector,
+ );
+
+ const setIsSelectingRelationTargetField = useSetAtomComponentState(
+ objectFilterDropdownIsSelectingRelationTargetFieldComponentState,
+ );
+
+ const { closeAdvancedFilterFieldSelectDropdown } =
+ useAdvancedFilterFieldSelectDropdown(recordFilterId);
+
+ const { selectFieldUsedInAdvancedFilterDropdown } =
+ useSelectFieldUsedInAdvancedFilterDropdown();
+
+ const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+
+ const { advancedFilterFieldSelectDropdownId } =
+ useAdvancedFilterFieldSelectDropdown(recordFilterId);
+
+ const selectedItemId = useAtomComponentStateValue(
+ selectedItemIdComponentState,
+ advancedFilterFieldSelectDropdownId,
+ );
+
+ const targetObjectMetadataId =
+ isDefined(sourceFieldMetadataItem) &&
+ isManyToOneRelationField(sourceFieldMetadataItem)
+ ? sourceFieldMetadataItem.relation.targetObjectMetadata.id
+ : null;
+
+ const { filterableFieldMetadataItems: relationTargetFields } =
+ useFilterableFieldMetadataItems(targetObjectMetadataId ?? '');
+
+ if (
+ !isDefined(sourceFieldMetadataItem) ||
+ !isManyToOneRelationField(sourceFieldMetadataItem)
+ ) {
+ return null;
+ }
+
+ const handleSubMenuBack = () => {
+ setIsSelectingRelationTargetField(false);
+ };
+
+ const handleSelectTargetField = (
+ relationTargetFieldMetadataItem: FieldMetadataItem,
+ ) => {
+ selectFieldUsedInAdvancedFilterDropdown({
+ fieldMetadataItemId: sourceFieldMetadataItem.id,
+ recordFilterId,
+ relationTargetFieldMetadataItem,
+ });
+
+ // Leaf RELATION/SELECT target fields open a value picker keyed by the
+ // target field id — push it on the focus stack so the picker's
+ // keyboard hotkeys are active when it opens.
+ if (
+ relationTargetFieldMetadataItem.type === 'RELATION' ||
+ relationTargetFieldMetadataItem.type === 'SELECT'
+ ) {
+ pushFocusItemToFocusStack({
+ focusId: relationTargetFieldMetadataItem.id,
+ component: {
+ type: FocusComponentType.DROPDOWN,
+ instanceId: relationTargetFieldMetadataItem.id,
+ },
+ });
+ }
+
+ setIsSelectingRelationTargetField(false);
+ closeAdvancedFilterFieldSelectDropdown();
+ };
+
+ const selectableItemIdArray = relationTargetFields.map((field) => field.id);
+
+ return (
+
+
+ }
+ >
+ {sourceFieldMetadataItem.label}
+
+
+
+ {relationTargetFields.map((targetField, index) => (
+ {
+ handleSelectTargetField(targetField);
+ }}
+ >
+
+ ))}
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx
index 7a98fed6057..90efd0e171a 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx
@@ -82,7 +82,7 @@ export const AdvancedFilterValueInput = ({
setObjectFilterDropdownCurrentRecordFilter(recordFilter);
setFieldMetadataItemIdUsedInDropdown(recordFilter.fieldMetadataId);
setRelationTargetFieldMetadataIdUsedInDropdown(
- recordFilter.relationTargetField?.id ?? null,
+ recordFilter.relationTargetFieldMetadataId ?? null,
);
};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
index 130909244be..bd964992612 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
@@ -15,8 +15,6 @@ import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField';
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
-import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
-import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
@@ -26,10 +24,6 @@ type SelectFilterParams = {
recordFilterId: string;
subFieldName?: CompositeFieldSubFieldName | null | undefined;
relationTargetFieldMetadataItem?: FieldMetadataItem | null | undefined;
- // Set when the next step is another dropdown that manages its own focus
- // (e.g. composite or relation-traversal sub-menus). The default push of
- // the source field id on the focus stack would shadow it.
- skipFocusPush?: boolean;
};
export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
@@ -49,8 +43,6 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
currentRecordFiltersComponentState,
);
- const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
-
const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();
@@ -75,7 +67,6 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
recordFilterId,
subFieldName,
relationTargetFieldMetadataItem,
- skipFocusPush,
}: SelectFilterParams) => {
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
@@ -86,20 +77,6 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
return;
}
- if (
- skipFocusPush !== true &&
- (fieldMetadataItem.type === 'RELATION' ||
- fieldMetadataItem.type === 'SELECT')
- ) {
- pushFocusItemToFocusStack({
- focusId: fieldMetadataItem.id,
- component: {
- type: FocusComponentType.DROPDOWN,
- instanceId: fieldMetadataItem.id,
- },
- });
- }
-
const isRelationTraversal = isDefined(relationTargetFieldMetadataItem);
const filterType = getFilterTypeFromFieldType(
@@ -157,13 +134,8 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
? `${fieldMetadataItem.label} → ${relationTargetFieldMetadataItem.label}`
: fieldMetadataItem.label;
- const relationTargetField = isRelationTraversal
- ? {
- id: relationTargetFieldMetadataItem.id,
- name: relationTargetFieldMetadataItem.name,
- type: relationTargetFieldMetadataItem.type,
- label: relationTargetFieldMetadataItem.label,
- }
+ const relationTargetFieldMetadataId = isRelationTraversal
+ ? relationTargetFieldMetadataItem.id
: null;
const newAdvancedFilter = {
@@ -178,12 +150,12 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
type: filterType,
label,
subFieldName: subFieldNameToUse,
- relationTargetField,
+ relationTargetFieldMetadataId,
} satisfies RecordFilter;
setSubFieldNameUsedInDropdown(subFieldNameToUse);
setRelationTargetFieldMetadataIdUsedInDropdown(
- relationTargetField?.id ?? null,
+ relationTargetFieldMetadataId,
);
setObjectFilterDropdownSearchInput('');
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType.ts
deleted file mode 100644
index 2173a9fae80..00000000000
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const RELATION_SUB_MENU_FIELD_TYPE = 'RELATION' as const;
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState.ts
new file mode 100644
index 00000000000..29664c96beb
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState.ts
@@ -0,0 +1,9 @@
+import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
+import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
+
+export const objectFilterDropdownIsSelectingRelationTargetFieldComponentState =
+ createAtomComponentState({
+ key: 'objectFilterDropdownIsSelectingRelationTargetFieldComponentState',
+ defaultValue: false,
+ componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRecordFilterDisplayValue.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRecordFilterDisplayValue.ts
index 88760ec9414..c71bf010c0e 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRecordFilterDisplayValue.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRecordFilterDisplayValue.ts
@@ -156,7 +156,8 @@ export const useGetRecordFilterDisplayValue = () => {
}
const { fieldMetadataItem } = getFieldMetadataItemByIdOrThrow(
- recordFilter.relationTargetField?.id ?? recordFilter.fieldMetadataId,
+ recordFilter.relationTargetFieldMetadataId ??
+ recordFilter.fieldMetadataId,
);
const fieldMetadataItemOptions = fieldMetadataItem.options;
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/ObjectFilterDropdownSubMenuFieldType.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/ObjectFilterDropdownSubMenuFieldType.ts
index 8650220a01a..c4deab78ec3 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/types/ObjectFilterDropdownSubMenuFieldType.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/types/ObjectFilterDropdownSubMenuFieldType.ts
@@ -1,6 +1,3 @@
-import { RELATION_SUB_MENU_FIELD_TYPE } from '@/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType';
import { type CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType';
-export type ObjectFilterDropdownSubMenuFieldType =
- | CompositeFilterableFieldType
- | typeof RELATION_SUB_MENU_FIELD_TYPE;
+export type ObjectFilterDropdownSubMenuFieldType = CompositeFilterableFieldType;
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
index dd6786b86c9..bd0fbb7701c 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
@@ -1,7 +1,6 @@
import { type FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import {
- type FieldMetadataType,
type FilterableAndTSVectorFieldType,
type ViewFilterOperand,
} from 'twenty-shared/types';
@@ -25,16 +24,7 @@ export type RecordFilter = {
positionInRecordFilterGroup?: number | null;
label: string;
subFieldName?: CompositeFieldSubFieldName | null | undefined;
- // Resolved at filter construction time (frontend mapping or chip edit) so
- // the GraphQL builder doesn't need a cross-object field lookup. Null for
- // non-relation filters; also null when the target field can no longer be
- // resolved (e.g. it was deleted).
- relationTargetField?: {
- id: string;
- name: string;
- type: FieldMetadataType;
- label: string;
- } | null;
+ relationTargetFieldMetadataId?: string | null;
rlsDynamicValue?: RLSDynamicValue | null;
};
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
index 927677f92d5..e25d22cb318 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
@@ -31,7 +31,6 @@ import { hasInitializedCurrentRecordFieldsComponentFamilyState } from '@/views/s
import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState';
import { hasInitializedCurrentRecordSortsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordSortsComponentFamilyState';
import { type View } from '@/views/types/View';
-import { getFilterableFields } from '@/views/utils/getFilterableFields';
import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
@@ -129,13 +128,11 @@ export const useLoadRecordIndexStates = () => {
.map(mapViewFieldToRecordField)
.filter(isDefined);
- const allFilterableFields = getFilterableFields(objectMetadataItem);
const flattenedFieldMetadataItems = store.get(
flattenedFieldMetadataItemsSelector.atom,
);
const recordFilters = mapViewFiltersToFilters(
view.viewFilters,
- allFilterableFields,
flattenedFieldMetadataItems,
);
@@ -145,7 +142,6 @@ export const useLoadRecordIndexStates = () => {
const contextStoreFilters = mapViewFiltersToFilters(
view.viewFilters,
- filterableFieldMetadataItems,
flattenedFieldMetadataItems,
);
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectFieldMenu.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectFieldMenu.tsx
index 3cb09b297d9..f722314d4b9 100644
--- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectFieldMenu.tsx
+++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectFieldMenu.tsx
@@ -24,6 +24,8 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
+import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
+import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { useContext } from 'react';
@@ -89,6 +91,8 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectF
fieldMetadataItemIdUsedInDropdownComponentState,
);
+ const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
+
const handleFieldSelect = (
selectedFieldMetadataItem: FieldMetadataItem,
) => {
@@ -102,13 +106,31 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectF
setObjectFilterDropdownSubMenuFieldType(filterType);
setFieldMetadataItemIdUsedInDropdown(selectedFieldMetadataItem.id);
setObjectFilterDropdownIsSelectingCompositeField(true);
- } else {
- selectFieldUsedInAdvancedFilterDropdown({
- fieldMetadataItemId: selectedFieldMetadataItem.id,
- recordFilterId,
- });
- closeAdvancedFilterFieldSelectDropdown();
+ return;
}
+
+ selectFieldUsedInAdvancedFilterDropdown({
+ fieldMetadataItemId: selectedFieldMetadataItem.id,
+ recordFilterId,
+ });
+
+ // RELATION/SELECT leaf fields open a value picker keyed by the
+ // source field id — push it on the focus stack so the picker's
+ // keyboard hotkeys are active when it opens.
+ if (
+ selectedFieldMetadataItem.type === FieldMetadataType.RELATION ||
+ selectedFieldMetadataItem.type === FieldMetadataType.SELECT
+ ) {
+ pushFocusItemToFocusStack({
+ focusId: selectedFieldMetadataItem.id,
+ component: {
+ type: FocusComponentType.DROPDOWN,
+ instanceId: selectedFieldMetadataItem.id,
+ },
+ });
+ }
+
+ closeAdvancedFilterFieldSelectDropdown();
};
const selectableItemIdArray = filteredFieldMetadataItems.map(
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectSubFieldMenu.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectSubFieldMenu.tsx
index b31b179f5ae..9ec48ac47c1 100644
--- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectSubFieldMenu.tsx
+++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectSubFieldMenu.tsx
@@ -13,7 +13,6 @@ import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-f
import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
-import { RELATION_SUB_MENU_FIELD_TYPE } from '@/object-record/object-filter-dropdown/constants/RelationSubMenuFieldType';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
import { ICON_NAME_BY_SUB_FIELD } from '@/object-record/record-filter/constants/IconNameBySubField';
@@ -95,10 +94,7 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectS
advancedFilterFieldSelectDropdownId,
);
- if (
- !isDefined(objectFilterDropdownSubMenuFieldType) ||
- objectFilterDropdownSubMenuFieldType === RELATION_SUB_MENU_FIELD_TYPE
- ) {
+ if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
return null;
}
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFilters.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFilters.tsx
index daa3d4d1019..15c99172931 100644
--- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFilters.tsx
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFilters.tsx
@@ -71,10 +71,7 @@ export const RecordTableSettingsFilters = ({
isWorkflowFindRecords={false}
onUpdate={handleFilterUpdate}
/>
-
+
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx
index 5b3cd9cf0c0..887042fc1f9 100644
--- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx
@@ -1,5 +1,4 @@
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { useSetAdvancedFilterDropdownStates } from '@/object-record/advanced-filter/hooks/useSetAdvancedFilterDropdownAllRowsStates';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
@@ -7,7 +6,6 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { type View } from '@/views/types/View';
-import { getFilterableFields } from '@/views/utils/getFilterableFields';
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useEffect, useState } from 'react';
@@ -15,12 +13,10 @@ import { isDefined } from 'twenty-shared/utils';
type RecordTableSettingsFiltersInitializeStateEffectProps = {
view: View;
- objectMetadataItem: EnrichedObjectMetadataItem;
};
export const RecordTableSettingsFiltersInitializeStateEffect = ({
view,
- objectMetadataItem,
}: RecordTableSettingsFiltersInitializeStateEffectProps) => {
const setCurrentRecordFilters = useSetAtomComponentState(
currentRecordFiltersComponentState,
@@ -60,10 +56,8 @@ export const RecordTableSettingsFiltersInitializeStateEffect = ({
return;
}
- const filterableFields = getFilterableFields(objectMetadataItem);
const recordFilters = mapViewFiltersToFilters(
view.viewFilters,
- filterableFields,
flattenedFieldMetadataItems,
);
@@ -78,7 +72,6 @@ export const RecordTableSettingsFiltersInitializeStateEffect = ({
setHasInitializedFilters(true);
}, [
view,
- objectMetadataItem,
flattenedFieldMetadataItems,
hasInitializedFilters,
stateAlreadyHasFilters,
diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx
index f271a1dfa23..bed324298fb 100644
--- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx
+++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx
@@ -125,7 +125,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
label: mockFieldMetadataItem.label,
type: getFilterTypeFromFieldType(mockFieldMetadataItem.type),
subFieldName: null,
- relationTargetField: null,
+ relationTargetFieldMetadataId: null,
} satisfies RecordFilter,
]);
});
diff --git a/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts
index 844f2f49763..07bbc8af9f7 100644
--- a/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts
@@ -1,14 +1,10 @@
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
-import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { type ViewFilter as GqlViewFilter } from '~/generated-metadata/graphql';
import { type ViewFilter } from '@/views/types/ViewFilter';
-import { getFilterableFields } from '@/views/utils/getFilterableFields';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
export const useMapViewFiltersToFilters = () => {
- const { objectMetadataItem } = useRecordIndexContextOrThrow();
-
const flattenedFieldMetadataItems = useAtomStateValue(
flattenedFieldMetadataItemsSelector,
);
@@ -16,13 +12,7 @@ export const useMapViewFiltersToFilters = () => {
const mapViewFiltersToRecordFilters = (
viewFilters: ViewFilter[] | GqlViewFilter[],
) => {
- const filterableFieldMetadataItems =
- getFilterableFields(objectMetadataItem);
- return mapViewFiltersToFilters(
- viewFilters,
- filterableFieldMetadataItems,
- flattenedFieldMetadataItems,
- );
+ return mapViewFiltersToFilters(viewFilters, flattenedFieldMetadataItems);
};
return { mapViewFiltersToRecordFilters };
diff --git a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
index 843d5344eef..391dd32fc85 100644
--- a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
@@ -67,7 +67,7 @@ export const useSetEditableFilterChipDropdownStates = () => {
recordFilterId: recordFilter.id,
}),
}),
- recordFilter.relationTargetField?.id ?? null,
+ recordFilter.relationTargetFieldMetadataId ?? null,
);
},
[store, filterableFieldMetadataItems],
diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts
index 8fa0538ae7a..fde0083b11a 100644
--- a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts
+++ b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts
@@ -44,7 +44,7 @@ describe('mapViewFiltersToFilters', () => {
positionInRecordFilterGroup: undefined,
recordFilterGroupId: undefined,
subFieldName: undefined,
- relationTargetField: null,
+ relationTargetFieldMetadataId: null,
},
];
expect(
diff --git a/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts b/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts
index ac6d9ccd449..53d53e4d5bd 100644
--- a/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts
+++ b/packages/twenty-front/src/modules/views/utils/mapRecordFilterToViewFilter.ts
@@ -13,6 +13,7 @@ export const mapRecordFilterToViewFilter = (
positionInViewFilterGroup: recordFilter.positionInRecordFilterGroup,
viewFilterGroupId: recordFilter.recordFilterGroupId,
subFieldName: recordFilter.subFieldName,
- relationTargetFieldMetadataId: recordFilter.relationTargetField?.id ?? null,
+ relationTargetFieldMetadataId:
+ recordFilter.relationTargetFieldMetadataId ?? null,
};
};
diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
index 718fc019f86..337387afc78 100644
--- a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
+++ b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
@@ -14,25 +14,24 @@ import { type ViewFilter } from '@/views/types/ViewFilter';
export const mapViewFiltersToFilters = (
viewFilters: ViewFilter[] | GqlViewFilter[],
- availableFieldMetadataItems: FieldMetadataItem[],
- allFieldMetadataItems: FieldMetadataItem[] = availableFieldMetadataItems,
+ fieldMetadataItems: FieldMetadataItem[],
): RecordFilter[] => {
return viewFilters
.map((viewFilter) => {
- const availableFieldMetadataItem = availableFieldMetadataItems.find(
+ const sourceFieldMetadataItem = fieldMetadataItems.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === viewFilter.fieldMetadataId,
);
- if (!isDefined(availableFieldMetadataItem)) {
- // Todo: we we don't throw an error yet as we have race condition on view change
+ if (!isDefined(sourceFieldMetadataItem)) {
+ // Todo: we don't throw an error yet as we have race condition on view change
return undefined;
}
const relationTargetFieldMetadataItem = isDefined(
viewFilter.relationTargetFieldMetadataId,
)
- ? allFieldMetadataItems.find(
+ ? fieldMetadataItems.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === viewFilter.relationTargetFieldMetadataId,
)
@@ -40,13 +39,13 @@ export const mapViewFiltersToFilters = (
const filterType = isDefined(relationTargetFieldMetadataItem)
? getFilterTypeFromFieldType(relationTargetFieldMetadataItem.type)
- : getFilterTypeFromFieldType(availableFieldMetadataItem.type);
+ : getFilterTypeFromFieldType(sourceFieldMetadataItem.type);
- const label = isSystemSearchVectorField(availableFieldMetadataItem.name)
+ const label = isSystemSearchVectorField(sourceFieldMetadataItem.name)
? 'Search'
: isDefined(relationTargetFieldMetadataItem)
- ? `${availableFieldMetadataItem.label} → ${relationTargetFieldMetadataItem.label}`
- : availableFieldMetadataItem.label;
+ ? `${sourceFieldMetadataItem.label} → ${relationTargetFieldMetadataItem.label}`
+ : sourceFieldMetadataItem.label;
const operand = viewFilter.operand;
@@ -66,14 +65,8 @@ export const mapViewFiltersToFilters = (
label,
type: filterType,
subFieldName: viewFilter.subFieldName as CompositeFieldSubFieldName,
- relationTargetField: isDefined(relationTargetFieldMetadataItem)
- ? {
- id: relationTargetFieldMetadataItem.id,
- name: relationTargetFieldMetadataItem.name,
- type: relationTargetFieldMetadataItem.type,
- label: relationTargetFieldMetadataItem.label,
- }
- : null,
+ relationTargetFieldMetadataId:
+ viewFilter.relationTargetFieldMetadataId ?? null,
} satisfies RecordFilter;
})
.filter(isDefined);
diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts
index 72fa9e98e5c..c139786ea8e 100644
--- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts
+++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts
@@ -222,34 +222,17 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
);
}
- const relationTargetField = isDefined(
- viewFilter.relationTargetFieldMetadataId,
- )
- ? findFlatEntityByIdInFlatEntityMaps({
- flatEntityId: viewFilter.relationTargetFieldMetadataId,
- flatEntityMaps: flatFieldMetadataMaps,
- })
- : null;
-
return {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
value: convertViewFilterValueToString(viewFilter.value),
- type: getFilterTypeFromFieldType(
- relationTargetField?.type ?? fieldMetadata.type,
- ),
+ type: getFilterTypeFromFieldType(fieldMetadata.type),
operand: viewFilter.operand,
recordFilterGroupId: viewFilter.viewFilterGroupId,
positionInRecordFilterGroup: viewFilter.positionInViewFilterGroup,
subFieldName: viewFilter.subFieldName as CompositeFieldSubFieldName,
- relationTargetField: isDefined(relationTargetField)
- ? {
- id: relationTargetField.id,
- name: relationTargetField.name,
- type: relationTargetField.type,
- label: relationTargetField.label,
- }
- : null,
+ relationTargetFieldMetadataId:
+ viewFilter.relationTargetFieldMetadataId ?? null,
};
});
diff --git a/packages/twenty-server/src/engine/metadata-modules/view/services/view-query-params.service.ts b/packages/twenty-server/src/engine/metadata-modules/view/services/view-query-params.service.ts
index 28fd4cde760..b50809a9048 100644
--- a/packages/twenty-server/src/engine/metadata-modules/view/services/view-query-params.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/view/services/view-query-params.service.ts
@@ -82,31 +82,16 @@ export class ViewQueryParamsService {
if (!field) return null;
- const relationTargetField = isDefined(
- viewFilter.relationTargetFieldMetadataId,
- )
- ? findFlatEntityByIdInFlatEntityMaps({
- flatEntityId: viewFilter.relationTargetFieldMetadataId,
- flatEntityMaps: flatFieldMetadataMaps,
- })
- : null;
-
return {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
value: viewFilter.value ?? '',
- type: relationTargetField?.type ?? field.type,
+ type: field.type,
recordFilterGroupId: viewFilter.viewFilterGroupId,
operand: viewFilter.operand,
subFieldName: viewFilter.subFieldName,
- relationTargetField: isDefined(relationTargetField)
- ? {
- id: relationTargetField.id,
- name: relationTargetField.name,
- type: relationTargetField.type,
- label: relationTargetField.label,
- }
- : null,
+ relationTargetFieldMetadataId:
+ viewFilter.relationTargetFieldMetadataId ?? null,
} as RecordFilter;
})
.filter(isDefined);
diff --git a/packages/twenty-shared/src/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter.ts b/packages/twenty-shared/src/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter.ts
index f2fe240ca45..8d7f0810ca0 100644
--- a/packages/twenty-shared/src/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter.ts
+++ b/packages/twenty-shared/src/utils/filter/turnRecordFilterGroupIntoGqlOperationFilter.ts
@@ -1,6 +1,5 @@
import {
type CompositeFieldSubFieldName,
- type FieldMetadataType,
type FilterableAndTSVectorFieldType,
type PartialFieldMetadataItem,
RecordFilterGroupLogicalOperator,
@@ -20,12 +19,7 @@ export type RecordFilter = {
recordFilterGroupId?: string | null;
operand: ViewFilterOperand;
subFieldName?: CompositeFieldSubFieldName | null | undefined;
- // Resolved at filter construction time so traversal doesn't need a
- // cross-object field lookup downstream.
- relationTargetField?:
- | { id: string; name: string; type: FieldMetadataType; label: string }
- | null
- | undefined;
+ relationTargetFieldMetadataId?: string | null | undefined;
};
export type RecordFilterGroup = {
diff --git a/packages/twenty-shared/src/utils/filter/turnRecordFilterIntoGqlOperationFilter.ts b/packages/twenty-shared/src/utils/filter/turnRecordFilterIntoGqlOperationFilter.ts
index c08dee7547b..aa607ce277d 100644
--- a/packages/twenty-shared/src/utils/filter/turnRecordFilterIntoGqlOperationFilter.ts
+++ b/packages/twenty-shared/src/utils/filter/turnRecordFilterIntoGqlOperationFilter.ts
@@ -89,25 +89,28 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
// Must run before the emptiness shortcut so an "is empty" filter on
// `company.name` is evaluated against `Company.name`, not the FK column.
- // If `relationTargetField` is missing the filter is dropped, not fed
- // through the legacy relation-by-record path — that path parses `value`
- // as a UUID list and would silently mishandle a target-field value like
- // "Acme", risking broadening of destructive operations.
+ // If the target field can't be resolved the filter is dropped rather than
+ // fed to the per-type switch as a RELATION (which would parse `value` as
+ // a UUID list and silently mishandle target-field values like "Acme").
if (
correspondingFieldMetadataItem.type === FieldMetadataType.RELATION &&
- isDefined(recordFilter.relationTargetField)
+ isDefined(recordFilter.relationTargetFieldMetadataId)
) {
- const targetField = recordFilter.relationTargetField;
+ const targetFieldMetadataItem = fieldMetadataItems.find(
+ (field) => field.id === recordFilter.relationTargetFieldMetadataId,
+ );
+
+ if (!isDefined(targetFieldMetadataItem)) {
+ return;
+ }
const innerFilter = turnRecordFilterIntoRecordGqlOperationFilter({
recordFilter: {
...recordFilter,
- fieldMetadataId: targetField.id,
- relationTargetField: null,
+ fieldMetadataId: targetFieldMetadataItem.id,
+ relationTargetFieldMetadataId: null,
},
- // Inject the target so the recursive lookup succeeds even when the
- // caller only supplied the current object's fields.
- fieldMetadataItems: [...fieldMetadataItems, targetField],
+ fieldMetadataItems,
filterValueDependencies,
});