diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx
index 9bdedea1d..b1e75599e 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx
@@ -8,6 +8,7 @@ import { RecordFilterGroup } from '@/object-record/record-filter-group/types/Rec
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -61,6 +62,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({
defaultFieldMetadataItemForFilter.type,
);
+ const defaultSubFieldName =
+ getDefaultSubFieldNameForCompositeFilterableFieldType(filterType);
+
const newRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
@@ -73,6 +77,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({
recordFilterGroupId: recordFilterGroup.id,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItemForFilter.label,
+ subFieldName: defaultSubFieldName,
};
upsertRecordFilter(newRecordFilter);
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx
index 4fde4314e..aeb598099 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx
@@ -1,20 +1,17 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
-import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
-import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
-import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
-import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
+import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
-import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
@@ -85,6 +82,9 @@ export const AdvancedFilterButton = () => {
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
+ const { createEmptyRecordFilterFromFieldMetadataItem } =
+ useCreateEmptyRecordFilterFromFieldMetadataItem();
+
const handleClick = () => {
if (!isDefined(currentView)) {
throw new Error('Missing current view id');
@@ -96,13 +96,10 @@ export const AdvancedFilterButton = () => {
const newRecordFilterGroup = {
id: v4(),
viewId: currentView.id,
- logicalOperator: ViewFilterGroupLogicalOperator.AND,
+ logicalOperator: RecordFilterGroupLogicalOperator.AND,
};
- upsertRecordFilterGroup({
- id: newRecordFilterGroup.id,
- logicalOperator: RecordFilterGroupLogicalOperator.AND,
- });
+ upsertRecordFilterGroup(newRecordFilterGroup);
const defaultFieldMetadataItem =
availableFieldMetadataItemsForFilter.find(
@@ -115,25 +112,11 @@ export const AdvancedFilterButton = () => {
throw new Error('Missing default filter definition');
}
- const filterType = getFilterTypeFromFieldType(
- defaultFieldMetadataItem.type,
+ const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem(
+ defaultFieldMetadataItem,
);
- const firstOperand = getRecordFilterOperands({
- filterType,
- })[0];
-
- const newRecordFilter: RecordFilter = {
- id: v4(),
- fieldMetadataId: defaultFieldMetadataItem.id,
- operand: firstOperand,
- value: '',
- displayValue: '',
- recordFilterGroupId: newRecordFilterGroup.id,
- type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type),
- label: defaultFieldMetadataItem.label,
- positionInRecordFilterGroup: 1,
- };
+ newRecordFilter.recordFilterGroupId = newRecordFilterGroup.id;
upsertRecordFilter(newRecordFilter);
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts
index fc7c63e35..eaec15bd8 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts
@@ -64,16 +64,9 @@ describe('getOperandsForFilterType', () => {
['DATE', [...dateOperands, ...emptyOperands]],
['DATE_TIME', [...dateOperands, ...emptyOperands]],
['RELATION', [...relationOperand, ...emptyOperands]],
- [undefined, []],
- [null, []],
- ['UNKNOWN_TYPE', []],
] satisfies (
- | [
- FieldType | null | undefined | 'UNKNOWN_TYPE',
- RecordFilterOperand[],
- CompositeFieldSubFieldName,
- ]
- | [FieldType | null | undefined | 'UNKNOWN_TYPE', RecordFilterOperand[]]
+ | [FieldType, RecordFilterOperand[], CompositeFieldSubFieldName]
+ | [FieldType, RecordFilterOperand[]]
)[];
testCases.forEach(([filterType, expectedOperands, subFieldName]) => {
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts
new file mode 100644
index 000000000..f3bf9d98d
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem.ts
@@ -0,0 +1,40 @@
+import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
+import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { v4 } from 'uuid';
+
+export const useCreateEmptyRecordFilterFromFieldMetadataItem = () => {
+ const createEmptyRecordFilterFromFieldMetadataItem = (
+ fieldMetadataItem: FieldMetadataItem,
+ ) => {
+ const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
+
+ const availableOperandsForFilter = getRecordFilterOperands({
+ filterType,
+ });
+
+ const defaultOperand = availableOperandsForFilter[0];
+
+ const defaultSubFieldName =
+ getDefaultSubFieldNameForCompositeFilterableFieldType(filterType);
+
+ const newRecordFilter: RecordFilter = {
+ id: v4(),
+ fieldMetadataId: fieldMetadataItem.id,
+ operand: defaultOperand,
+ displayValue: '',
+ label: fieldMetadataItem.label,
+ type: filterType,
+ value: '',
+ subFieldName: defaultSubFieldName,
+ };
+
+ return { newRecordFilter };
+ };
+
+ return {
+ createEmptyRecordFilterFromFieldMetadataItem,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
index f7186e1e4..236ab6f35 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
@@ -4,6 +4,7 @@ import { FilterableFieldType } from '@/object-record/record-filter/types/Filtera
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
import { FieldMetadataType } from 'twenty-shared/types';
+import { assertUnreachable } from 'twenty-shared/utils';
export type GetRecordFilterOperandsParams = {
filterType: FilterableFieldType;
@@ -208,6 +209,6 @@ export const getRecordFilterOperands = ({
case 'BOOLEAN':
return FILTER_OPERANDS_MAP.BOOLEAN;
default:
- return [];
+ assertUnreachable(filterType, `Unknown filter type ${filterType}`);
}
};
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
index 2a9a80d38..b5d3d902e 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
@@ -3,7 +3,6 @@ import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
-import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
@@ -16,13 +15,7 @@ import { isDefined } from 'twenty-shared/utils';
export const RecordIndexTableContainerEffect = () => {
const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow();
- const viewBarId = recordIndexId;
-
- const {
- setAvailableTableColumns,
- setOnToggleColumnFilter,
- setOnToggleColumnSort,
- } = useRecordTable({
+ const { setAvailableTableColumns, setOnToggleColumnSort } = useRecordTable({
recordTableId: recordIndexId,
});
@@ -37,24 +30,12 @@ export const RecordIndexTableContainerEffect = () => {
setAvailableTableColumns(columnDefinitions);
}, [columnDefinitions, setAvailableTableColumns]);
- const handleToggleColumnFilter = useHandleToggleColumnFilter({
- objectNameSingular,
- viewBarId,
- });
-
const handleToggleColumnSort = useHandleToggleColumnSort({
objectNameSingular,
});
const { currentView } = useGetCurrentViewOnly();
- useEffect(() => {
- setOnToggleColumnFilter(
- () => (fieldMetadataId: string) =>
- handleToggleColumnFilter(fieldMetadataId),
- );
- }, [setOnToggleColumnFilter, handleToggleColumnFilter]);
-
useEffect(() => {
setOnToggleColumnSort(
() => (fieldMetadataId: string) =>
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts
deleted file mode 100644
index 1c41a3ae0..000000000
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import { useCallback } from 'react';
-import { v4 } from 'uuid';
-
-import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-
-import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
-import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
-
-import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown';
-import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
-import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
-import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
-import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
-import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
-import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
-import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
-import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
-import { useRecoilCallback, useRecoilValue } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
-
-type UseHandleToggleColumnFilterProps = {
- objectNameSingular: string;
- viewBarId: string;
-};
-
-export const useHandleToggleColumnFilter = ({
- objectNameSingular,
- viewBarId,
-}: UseHandleToggleColumnFilterProps) => {
- const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular,
- });
-
- const { columnDefinitions } =
- useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
-
- const { upsertRecordFilter } = useUpsertRecordFilter();
-
- const { setActiveDropdownFocusIdAndMemorizePrevious } =
- useSetActiveDropdownFocusIdAndMemorizePrevious();
-
- const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
-
- const openDropdown = useRecoilCallback(
- ({ set }) => {
- return (dropdownId: string) => {
- const dropdownOpenState = extractComponentState(
- isDropdownOpenComponentState,
- dropdownId,
- );
-
- setActiveDropdownFocusIdAndMemorizePrevious(dropdownId);
- setHotkeyScopeAndMemorizePreviousScope(dropdownId);
-
- set(dropdownOpenState, true);
- };
- },
- [
- setActiveDropdownFocusIdAndMemorizePrevious,
- setHotkeyScopeAndMemorizePreviousScope,
- ],
- );
-
- const availableFieldMetadataItemsForFilter = useRecoilValue(
- availableFieldMetadataItemsForFilterFamilySelector({
- objectMetadataItemId: objectMetadataItem.id,
- }),
- );
-
- const { selectFilterUsedInDropdown } =
- useSelectFilterUsedInDropdown(viewBarId);
-
- const currentRecordFilters = useRecoilComponentValueV2(
- currentRecordFiltersComponentState,
- );
-
- const handleToggleColumnFilter = useCallback(
- async (fieldMetadataId: string) => {
- const correspondingColumnDefinition = columnDefinitions.find(
- (columnDefinition) =>
- columnDefinition.fieldMetadataId === fieldMetadataId,
- );
-
- if (!isDefined(correspondingColumnDefinition)) return;
-
- const newFilterId = v4();
-
- const existingRecordFilter = currentRecordFilters.find(
- (recordFilter) => recordFilter.fieldMetadataId === fieldMetadataId,
- );
-
- if (!isDefined(existingRecordFilter)) {
- const fieldMetadataItem = availableFieldMetadataItemsForFilter.find(
- (fieldMetadataItemToFind) =>
- fieldMetadataItemToFind.id === fieldMetadataId,
- );
-
- if (!isDefined(fieldMetadataItem)) {
- throw new Error('Field metadata item not found');
- }
-
- const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
-
- const defaultSubFieldName =
- getDefaultSubFieldNameForCompositeFilterableFieldType(
- fieldMetadataItem.type,
- );
-
- const availableOperandsForFilter = getRecordFilterOperands({
- filterType,
- subFieldName: defaultSubFieldName,
- });
-
- const defaultOperand = availableOperandsForFilter[0];
-
- const newFilter: RecordFilter = {
- id: newFilterId,
- fieldMetadataId,
- operand: defaultOperand,
- displayValue: '',
- label: fieldMetadataItem.label,
- type: filterType,
- value: '',
- subFieldName: defaultSubFieldName,
- };
-
- upsertRecordFilter(newFilter);
-
- selectFilterUsedInDropdown({ fieldMetadataItemId: fieldMetadataId });
- }
-
- openDropdown(existingRecordFilter?.id ?? newFilterId);
- },
- [
- openDropdown,
- columnDefinitions,
- selectFilterUsedInDropdown,
- currentRecordFilters,
- availableFieldMetadataItemsForFilter,
- upsertRecordFilter,
- ],
- );
-
- return handleToggleColumnFilter;
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts
index 941b47c0a..5d307d279 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts
@@ -19,7 +19,7 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState';
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
-import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
+
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
@@ -73,10 +73,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
recordTableId,
);
- const setOnToggleColumnFilter = useSetRecoilComponentStateV2(
- onToggleColumnFilterComponentState,
- recordTableId,
- );
const setOnToggleColumnSort = useSetRecoilComponentStateV2(
onToggleColumnSortComponentState,
recordTableId,
@@ -226,7 +222,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setRecordTableLastRowVisible,
setFocusPosition,
setHasUserSelectedAllRows,
- setOnToggleColumnFilter,
setOnToggleColumnSort,
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
index 083467c79..cf134538f 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
@@ -3,7 +3,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
-import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
+import { useOpenRecordFilterChipFromTableHeader } from '@/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
@@ -76,9 +76,6 @@ export const RecordTableColumnHeadDropdownMenu = ({
handleColumnVisibilityChange(column);
};
- const onToggleColumnFilter = useRecoilComponentValueV2(
- onToggleColumnFilterComponentState,
- );
const onToggleColumnSort = useRecoilComponentValueV2(
onToggleColumnSortComponentState,
);
@@ -89,10 +86,13 @@ export const RecordTableColumnHeadDropdownMenu = ({
onToggleColumnSort?.(column.fieldMetadataId);
};
+ const { openRecordFilterChipFromTableHeader } =
+ useOpenRecordFilterChipFromTableHeader();
+
const handleFilterClick = () => {
closeDropdownAndToggleScroll();
- onToggleColumnFilter?.(column.fieldMetadataId);
+ openRecordFilterChipFromTableHeader(column.fieldMetadataId);
};
const isSortable = column.isSortable === true;
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts
new file mode 100644
index 000000000..9aef27c50
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader.ts
@@ -0,0 +1,70 @@
+import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown';
+import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem';
+import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
+import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
+import { useOpenDropdownFromOutside } from '@/ui/layout/dropdown/hooks/useOpenDropdownFromOutside';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useOpenRecordFilterChipFromTableHeader = () => {
+ const { recordIndexId } = useRecordIndexContextOrThrow();
+
+ const { filterableFieldMetadataItems } =
+ useFilterableFieldMetadataItemsInRecordIndexContext();
+
+ const { selectFilterUsedInDropdown } =
+ useSelectFilterUsedInDropdown(recordIndexId);
+
+ const currentRecordFilters = useRecoilComponentValueV2(
+ currentRecordFiltersComponentState,
+ );
+
+ const { createEmptyRecordFilterFromFieldMetadataItem } =
+ useCreateEmptyRecordFilterFromFieldMetadataItem();
+
+ const { upsertRecordFilter } = useUpsertRecordFilter();
+
+ const { openDropdownFromOutside } = useOpenDropdownFromOutside();
+
+ const { setEditableFilterChipDropdownStates } =
+ useSetEditableFilterChipDropdownStates();
+
+ const openRecordFilterChipFromTableHeader = (fieldMetadataItemId: string) => {
+ const correspondingFieldMetadataItem = filterableFieldMetadataItems.find(
+ (fieldMetadataItemToFind) =>
+ fieldMetadataItemToFind.id === fieldMetadataItemId,
+ );
+
+ if (!isDefined(correspondingFieldMetadataItem)) {
+ throw new Error(
+ `Cannot find field metadata item with id : ${fieldMetadataItemId}`,
+ );
+ }
+
+ const existingRecordFilter = currentRecordFilters.find(
+ (recordFilter) => recordFilter.fieldMetadataId === fieldMetadataItemId,
+ );
+
+ if (isDefined(existingRecordFilter)) {
+ setEditableFilterChipDropdownStates(existingRecordFilter);
+ openDropdownFromOutside(existingRecordFilter.id);
+ return;
+ }
+
+ const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem(
+ correspondingFieldMetadataItem,
+ );
+
+ upsertRecordFilter(newRecordFilter);
+
+ selectFilterUsedInDropdown({ fieldMetadataItemId });
+
+ setEditableFilterChipDropdownStates(newRecordFilter);
+ openDropdownFromOutside(newRecordFilter.id);
+ };
+
+ return { openRecordFilterChipFromTableHeader };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts
deleted file mode 100644
index c0e0a972b..000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/states/onToggleColumnFilterComponentState.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
-import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
-
-export const onToggleColumnFilterComponentState = createComponentStateV2<
- ((fieldMetadataId: string) => void) | undefined
->({
- key: 'onToggleColumnFilterComponentState',
- defaultValue: undefined,
- componentInstanceContext: RecordTableComponentInstanceContext,
-});
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts
new file mode 100644
index 000000000..8ea2bae6d
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/hooks/useOpenDropdownFromOutside.ts
@@ -0,0 +1,34 @@
+import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
+import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
+import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
+import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
+import { useRecoilCallback } from 'recoil';
+
+export const useOpenDropdownFromOutside = () => {
+ const { setActiveDropdownFocusIdAndMemorizePrevious } =
+ useSetActiveDropdownFocusIdAndMemorizePrevious();
+
+ const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
+
+ const openDropdownFromOutside = useRecoilCallback(
+ ({ set }) => {
+ return (dropdownId: string) => {
+ const dropdownOpenState = extractComponentState(
+ isDropdownOpenComponentState,
+ dropdownId,
+ );
+
+ setActiveDropdownFocusIdAndMemorizePrevious(dropdownId);
+ setHotkeyScopeAndMemorizePreviousScope(dropdownId);
+
+ set(dropdownOpenState, true);
+ };
+ },
+ [
+ setActiveDropdownFocusIdAndMemorizePrevious,
+ setHotkeyScopeAndMemorizePreviousScope,
+ ],
+ );
+
+ return { openDropdownFromOutside };
+};
diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
index a4a6606eb..1f8116448 100644
--- a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
+++ b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
@@ -13,11 +13,13 @@ import { useIcons } from 'twenty-ui/display';
type EditableFilterChipProps = {
recordFilter: RecordFilter;
onRemove: () => void;
+ onClick?: () => void;
};
export const EditableFilterChip = ({
recordFilter,
onRemove,
+ onClick,
}: EditableFilterChipProps) => {
const { getIcon } = useIcons();
@@ -58,6 +60,7 @@ export const EditableFilterChip = ({
labelValue={recordFilter.displayValue}
Icon={FieldMetadataItemIcon}
onRemove={onRemove}
+ onClick={onClick}
/>
);
};
diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx
index e33d38c54..b7225195e 100644
--- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx
+++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx
@@ -9,7 +9,7 @@ import { EditableFilterChip } from '@/views/components/EditableFilterChip';
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
-import { EditableFilterDropdownButtonEffect } from '@/views/components/EditableFilterDropdownButtonEffect';
+import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates';
type EditableFilterDropdownButtonProps = {
recordFilter: RecordFilter;
@@ -38,15 +38,22 @@ export const EditableFilterDropdownButton = ({
}
}, [recordFilter, removeRecordFilter]);
+ const { setEditableFilterChipDropdownStates } =
+ useSetEditableFilterChipDropdownStates();
+
+ const handleFilterChipClick = () => {
+ setEditableFilterChipDropdownStates(recordFilter);
+ };
+
return (
<>
-
}
dropdownComponents={
diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx
deleted file mode 100644
index 272f2628e..000000000
--- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButtonEffect.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { useEffect } from 'react';
-
-import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
-
-import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
-import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
-import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
-import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
-import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
-import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
-import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { isDefined } from 'twenty-shared/utils';
-
-type EditableFilterDropdownButtonEffectProps = {
- recordFilter: RecordFilter;
-};
-
-export const EditableFilterDropdownButtonEffect = ({
- recordFilter,
-}: EditableFilterDropdownButtonEffectProps) => {
- const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
- fieldMetadataItemIdUsedInDropdownComponentState,
- );
-
- const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
- selectedOperandInDropdownComponentState,
- recordFilter.id,
- );
-
- const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2(
- subFieldNameUsedInDropdownComponentState,
- recordFilter.id,
- );
-
- const setSelectedFilter = useSetRecoilComponentStateV2(
- selectedFilterComponentState,
- recordFilter.id,
- );
-
- const setObjectFilterDropdownSelectedRecordIds = useSetRecoilComponentStateV2(
- objectFilterDropdownSelectedRecordIdsComponentState,
- recordFilter.id,
- );
-
- const { filterableFieldMetadataItems } =
- useFilterableFieldMetadataItemsInRecordIndexContext();
-
- useEffect(() => {
- const fieldMetadataItem = filterableFieldMetadataItems.find(
- (fieldMetadataItem) =>
- fieldMetadataItem.id === recordFilter.fieldMetadataId,
- );
-
- if (!isDefined(fieldMetadataItem)) {
- return;
- }
-
- setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
- setSelectedOperandInDropdown(recordFilter.operand);
- setSelectedFilter(recordFilter);
- setSubFieldNameUsedInDropdown(recordFilter.subFieldName);
-
- try {
- const selectedOptions = JSON.parse(recordFilter.value);
-
- setObjectFilterDropdownSelectedRecordIds(selectedOptions);
- } catch {
- setObjectFilterDropdownSelectedRecordIds([]);
- }
- }, [
- filterableFieldMetadataItems,
- setFieldMetadataItemIdUsedInDropdown,
- recordFilter,
- setSelectedOperandInDropdown,
- setSelectedFilter,
- setSubFieldNameUsedInDropdown,
- setObjectFilterDropdownSelectedRecordIds,
- ]);
-
- return null;
-};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx
index 9cf6bb322..449601e3f 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx
@@ -10,7 +10,6 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton';
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
import { EditableSortChip } from '@/views/components/EditableSortChip';
-import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
@@ -232,7 +231,6 @@ export const ViewBarDetails = ({
value={{ instanceId: recordFilter.id }}
>
-
{
+ const { filterableFieldMetadataItems } =
+ useFilterableFieldMetadataItemsInRecordIndexContext();
+
+ const setEditableFilterChipDropdownStates = useRecoilCallback(
+ ({ set }) =>
+ (recordFilter: RecordFilter) => {
+ const fieldMetadataItem = filterableFieldMetadataItems.find(
+ (fieldMetadataItem) =>
+ fieldMetadataItem.id === recordFilter.fieldMetadataId,
+ );
+
+ if (!isDefined(fieldMetadataItem)) {
+ return;
+ }
+
+ set(
+ fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ fieldMetadataItem.id,
+ );
+
+ set(
+ selectedOperandInDropdownComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ recordFilter.operand,
+ );
+
+ set(
+ selectedFilterComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ recordFilter,
+ );
+
+ set(
+ subFieldNameUsedInDropdownComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ recordFilter.subFieldName,
+ );
+
+ if (recordFilter.type === 'RELATION') {
+ const { selectedRecordIds } = jsonRelationFilterValueSchema
+ .catch({
+ isCurrentWorkspaceMemberSelected: false,
+ selectedRecordIds: simpleRelationFilterValueSchema.parse(
+ recordFilter.value,
+ ),
+ })
+ .parse(recordFilter.value);
+
+ set(
+ objectFilterDropdownSelectedRecordIdsComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ selectedRecordIds,
+ );
+ } else if (['SELECT', 'MULTI_SELECT'].includes(recordFilter.type)) {
+ try {
+ const selectedOptions = JSON.parse(recordFilter.value);
+
+ set(
+ objectFilterDropdownSelectedOptionValuesComponentState.atomFamily(
+ {
+ instanceId: recordFilter.id,
+ },
+ ),
+ selectedOptions,
+ );
+ } catch {
+ set(
+ objectFilterDropdownSelectedOptionValuesComponentState.atomFamily(
+ {
+ instanceId: recordFilter.id,
+ },
+ ),
+ [],
+ );
+ }
+ }
+ },
+ [filterableFieldMetadataItems],
+ );
+
+ return {
+ setEditableFilterChipDropdownStates,
+ };
+};
diff --git a/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts b/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts
index 9f47fac16..752370d76 100644
--- a/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts
+++ b/packages/twenty-front/src/modules/views/utils/areViewFilterGroupsEqual.ts
@@ -9,7 +9,7 @@ export const areViewFilterGroupsEqual = (
'positionInViewFilterGroup',
'logicalOperator',
'parentViewFilterGroupId',
- 'viewId',
+ 'id',
];
return propertiesToCompare.every((property) =>