diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx
index 337f4d3ca..36b15a8c7 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown.tsx
@@ -1,5 +1,6 @@
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
+import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -20,6 +21,8 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
const { removeRecordFilter } = useRemoveRecordFilter();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
+ const { removeRootRecordFilterGroupIfEmpty } =
+ useRemoveRootRecordFilterGroupIfEmpty();
const { childRecordFilters } = useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId,
@@ -32,6 +35,8 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
removeRecordFilterGroup(recordFilterGroupId);
+ removeRootRecordFilterGroupIfEmpty();
+
closeDropdown();
};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx
index bbec255b8..390015f69 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown.tsx
@@ -1,5 +1,6 @@
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
+import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
@@ -38,6 +39,9 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
recordFilterGroupId: currentRecordFilter?.recordFilterGroupId,
});
+ const { removeRootRecordFilterGroupIfEmpty } =
+ useRemoveRootRecordFilterGroupIfEmpty();
+
const handleRemove = async () => {
closeDropdown();
@@ -51,6 +55,8 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
}
removeRecordFilter({ recordFilterId: recordFilterId });
+
+ removeRootRecordFilterGroupIfEmpty();
};
return (
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx
index a7f6fe0c9..8c694ea7c 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup.tsx
@@ -5,8 +5,8 @@ import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-re
import { AdvancedFilterRecordFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup';
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
+import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
-import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
@@ -32,20 +32,14 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
`;
export const AdvancedFilterRootLevelViewFilterGroup = () => {
- const currentRecordFilterGroups = useRecoilComponentValueV2(
- currentRecordFilterGroupsComponentState,
+ const rootLevelRecordFilterGroup = useRecoilComponentValueV2(
+ rootLevelRecordFilterGroupComponentSelector,
);
- const rootRecordFilterGroupId = currentRecordFilterGroups.find(
- (recordFilterGroup) => !recordFilterGroup.parentRecordFilterGroupId,
- )?.id;
-
- const {
- currentRecordFilterGroup: rootLevelRecordFilterGroup,
- childRecordFiltersAndRecordFilterGroups,
- } = useChildRecordFiltersAndRecordFilterGroups({
- recordFilterGroupId: rootRecordFilterGroupId,
- });
+ const { childRecordFiltersAndRecordFilterGroups } =
+ useChildRecordFiltersAndRecordFilterGroups({
+ recordFilterGroupId: rootLevelRecordFilterGroup?.id,
+ });
if (!isDefined(rootLevelRecordFilterGroup)) {
return null;
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts
index 13552ab83..0bdc1257b 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups.ts
@@ -1,5 +1,7 @@
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared';
@@ -23,9 +25,11 @@ export const useChildRecordFiltersAndRecordFilterGroups = ({
if (!isDefined(currentRecordFilterGroup)) {
return {
currentRecordFilterGroup: undefined,
- childRecordFiltersAndRecordFilterGroups: [],
- childRecordFilters: [],
- childRecordFilterGroups: [],
+ childRecordFiltersAndRecordFilterGroups: [] as Array<
+ RecordFilter | RecordFilterGroup
+ >,
+ childRecordFilters: [] as RecordFilter[],
+ childRecordFilterGroups: [] as RecordFilterGroup[],
lastChildPosition: 0,
};
}
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector.ts
new file mode 100644
index 000000000..2a86b65ab
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector.ts
@@ -0,0 +1,24 @@
+import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
+import { isDefined } from 'twenty-shared';
+
+export const rootLevelRecordFilterGroupComponentSelector =
+ createComponentSelectorV2({
+ key: 'rootLevelRecordFilterGroupComponentSelector',
+ get:
+ ({ instanceId }) =>
+ ({ get }) => {
+ const currentRecordFilterGroups = get(
+ currentRecordFilterGroupsComponentState.atomFamily({ instanceId }),
+ );
+
+ const rootLevelRecordFilterGroup = currentRecordFilterGroups.find(
+ (recordFilterGroup) =>
+ !isDefined(recordFilterGroup.parentRecordFilterGroupId),
+ );
+
+ return rootLevelRecordFilterGroup;
+ },
+ componentInstanceContext: RecordFilterGroupsComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown.tsx
index d422773ab..681d4566c 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown.tsx
@@ -35,7 +35,7 @@ export const ObjectFilterDropdownOperandDropdown = ({
clickableComponent={
}
>
{getOperandLabel(selectedOperandInDropdown)}
diff --git a/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty.ts b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty.ts
new file mode 100644
index 000000000..d7fed5bc4
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty.ts
@@ -0,0 +1,71 @@
+import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
+import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
+import { useRecoilCallback } from 'recoil';
+import { isDefined } from 'twenty-shared';
+
+export const useRemoveRootRecordFilterGroupIfEmpty = () => {
+ const currentRecordFilterGroupsCallbackState =
+ useRecoilComponentCallbackStateV2(currentRecordFilterGroupsComponentState);
+
+ const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2(
+ currentRecordFiltersComponentState,
+ );
+
+ const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
+
+ const removeRootRecordFilterGroupIfEmpty = useRecoilCallback(
+ ({ snapshot }) =>
+ () => {
+ const currentRecordFilterGroups = getSnapshotValue(
+ snapshot,
+ currentRecordFilterGroupsCallbackState,
+ );
+
+ const currentRecordFilters = getSnapshotValue(
+ snapshot,
+ currentRecordFiltersCallbackState,
+ );
+
+ const rootRecordFilterGroup = currentRecordFilterGroups.find(
+ (existingRecordFilterGroup) =>
+ !isDefined(existingRecordFilterGroup.parentRecordFilterGroupId),
+ );
+
+ if (isDefined(rootRecordFilterGroup)) {
+ const recordFilterGroupsInRootRecordFilterGroup =
+ currentRecordFilterGroups.filter(
+ (recordFilterGroupToFilter) =>
+ recordFilterGroupToFilter.parentRecordFilterGroupId ===
+ rootRecordFilterGroup.id,
+ );
+
+ const recordFiltersInRootRecordFilterGroup =
+ currentRecordFilters.filter(
+ (recordFilterToFilter) =>
+ recordFilterToFilter.recordFilterGroupId ===
+ rootRecordFilterGroup.id,
+ );
+
+ const rootRecordFilterGroupIsEmpty =
+ recordFilterGroupsInRootRecordFilterGroup.length === 0 &&
+ recordFiltersInRootRecordFilterGroup.length === 0;
+
+ if (rootRecordFilterGroupIsEmpty) {
+ removeRecordFilterGroup(rootRecordFilterGroup.id);
+ }
+ }
+ },
+ [
+ removeRecordFilterGroup,
+ currentRecordFilterGroupsCallbackState,
+ currentRecordFiltersCallbackState,
+ ],
+ );
+
+ return {
+ removeRootRecordFilterGroupIfEmpty,
+ };
+};
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader.tsx
index 760f5e10d..dc39b99ee 100644
--- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader.tsx
@@ -1,6 +1,5 @@
import styled from '@emotion/styled';
import { ComponentProps, MouseEvent } from 'react';
-import { IconComponent } from 'twenty-ui';
const StyledHeader = styled.li`
align-items: center;
@@ -44,13 +43,13 @@ const StyledEndComponent = styled.div`
`;
type DropdownMenuHeaderProps = ComponentProps<'li'> & {
- EndIcon?: IconComponent;
onClick?: (event: MouseEvent) => void;
testId?: string;
className?: string;
StartComponent?: React.ReactNode;
EndComponent?: React.ReactNode;
};
+
export const DropdownMenuHeader = ({
children,
StartComponent,
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuHeader.stories.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuHeader.stories.tsx
index 3ed73e189..012ebc9be 100644
--- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuHeader.stories.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuHeader.stories.tsx
@@ -1,7 +1,7 @@
import { Meta, StoryObj } from '@storybook/react';
import {
- Avatar,
AVATAR_URL_MOCK,
+ Avatar,
ComponentDecorator,
IconChevronLeft,
IconChevronRight,
@@ -9,11 +9,11 @@ import {
MenuItem,
} from 'twenty-ui';
-import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
+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';
const meta: Meta = {
title: 'UI/Layout/Dropdown/DropdownMenuHeader',
@@ -40,7 +40,7 @@ export const StartIcon: Story = {
export const StartAndEndIcon: Story = {
args: {
StartComponent: ,
- EndIcon: IconChevronRight,
+ EndComponent: ,
children: 'Start and End Icon',
},
};
diff --git a/packages/twenty-front/src/modules/views/components/AdvancedFilterChip.tsx b/packages/twenty-front/src/modules/views/components/AdvancedFilterChip.tsx
index 915ac7c99..9e0cb5dc9 100644
--- a/packages/twenty-front/src/modules/views/components/AdvancedFilterChip.tsx
+++ b/packages/twenty-front/src/modules/views/components/AdvancedFilterChip.tsx
@@ -1,17 +1,20 @@
import { IconFilterCog } from 'twenty-ui';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
+import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-filter-group/hooks/useRemoveRootRecordFilterGroupIfEmpty';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { plural } from 'pluralize';
import { isDefined } from 'twenty-shared';
-import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
export const AdvancedFilterChip = () => {
+ const { closeDropdown } = useDropdown(ADVANCED_FILTER_DROPDOWN_ID);
+
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
@@ -27,10 +30,11 @@ export const AdvancedFilterChip = () => {
const { removeRecordFilter } = useRemoveRecordFilter();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
+ const { removeRootRecordFilterGroupIfEmpty } =
+ useRemoveRootRecordFilterGroupIfEmpty();
+
const handleRemoveClick = () => {
- if (!isNonEmptyArray(advancedRecordFilterIds)) {
- throw new Error('No advanced view filters to remove');
- }
+ closeDropdown();
const viewFilterGroupIds = currentRecordFilterGroups.map(
(recordFilterGroup) => recordFilterGroup.id,
@@ -43,6 +47,8 @@ export const AdvancedFilterChip = () => {
for (const recordFilterId of advancedRecordFilterIds) {
removeRecordFilter({ recordFilterId });
}
+
+ removeRootRecordFilterGroupIfEmpty();
};
const advancedFilterCount = advancedRecordFilterIds.length;
diff --git a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx
index 59729a31a..8ecb837cc 100644
--- a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx
+++ b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx
@@ -1,21 +1,18 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { AdvancedFilterRootLevelViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup';
-import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { AdvancedFilterChip } from '@/views/components/AdvancedFilterChip';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
+import { isDefined } from 'twenty-shared';
export const AdvancedFilterDropdownButton = () => {
- const currentRecordFilterGroups = useRecoilComponentValueV2(
- currentRecordFilterGroupsComponentState,
+ const rootLevelRecordFilterGroup = useRecoilComponentValueV2(
+ rootLevelRecordFilterGroupComponentSelector,
);
- const outermostRecordFilterGroupId = currentRecordFilterGroups.find(
- (recordFilterGroup) => !recordFilterGroup.parentRecordFilterGroupId,
- )?.id;
-
- if (!outermostRecordFilterGroupId) {
+ if (!isDefined(rootLevelRecordFilterGroup)) {
return null;
}