fix: show active advanced filter count badge in dropdown button (#20229)

## Summary

Replaces the hardcoded `0` in
`ViewBarFilterDropdownAdvancedFilterButton` with the actual count of
active advanced filter rules, matching the behavior of
`AdvancedFilterChip` in the view bar.

## What changed

In
`packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx`:

- Imported `useAtomComponentSelectorValue` and
`rootLevelRecordFilterGroupComponentSelector`
- Imported `useChildRecordFiltersAndRecordFilterGroups`
- Replaced `const advancedFilterQuerySubFilterCount = 0; // TODO` with
the real computed count via the same hook pattern used in
`AdvancedFilterChip.tsx`

The pill badge will now appear on the "Advanced filter" dropdown menu
item showing the number of active advanced filter rules (e.g. "2" when
two rules are active).

## References

- Fixes #20207

---------

Co-authored-by: wadeKeith <wade@twenty.app>
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Cheng Yin
2026-05-04 23:08:30 +08:00
committed by GitHub
parent 95a34d8517
commit 97ec720d2c
2 changed files with 122 additions and 8 deletions
@@ -7,9 +7,12 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
@@ -34,7 +37,17 @@ const StyledPillContainer = styled.span`
`;
export const ViewBarFilterDropdownAdvancedFilterButton = () => {
const advancedFilterQuerySubFilterCount = 0; // TODO
const rootRecordFilterGroup = useAtomComponentSelectorValue(
rootLevelRecordFilterGroupComponentSelector,
);
const { childRecordFiltersAndRecordFilterGroups } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: rootRecordFilterGroup?.id,
});
const advancedFilterQuerySubFilterCount =
childRecordFiltersAndRecordFilterGroups.length;
const { t } = useLingui();
@@ -134,12 +147,14 @@ export const ViewBarFilterDropdownAdvancedFilterButton = () => {
onClick={handleClick}
LeftIcon={IconFilter}
focused={isSelectedItemId}
RightComponent={
advancedFilterQuerySubFilterCount > 0 ? (
<StyledPillContainer>
<Pill label={advancedFilterQuerySubFilterCount.toString()} />
</StyledPillContainer>
) : undefined
}
/>
{advancedFilterQuerySubFilterCount > 0 && (
<StyledPillContainer>
<Pill label={advancedFilterQuerySubFilterCount.toString()} />
</StyledPillContainer>
)}
</SelectableListItem>
);
};
@@ -9,16 +9,22 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { ViewBarFilterDropdown } from '@/views/components/ViewBarFilterDropdown';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import {
CoreObjectNameSingular,
RecordFilterGroupLogicalOperator,
ViewFilterOperand,
} from 'twenty-shared/types';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdownIds';
import { userEvent, within } from 'storybook/test';
import { expect, userEvent, within } from 'storybook/test';
import { ComponentDecorator } from 'twenty-ui/testing';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
@@ -191,3 +197,96 @@ export const Number: Story = {
await userEvent.click(dateFilter);
},
};
const MOCK_ROOT_FILTER_GROUP_ID = 'test-root-filter-group-id';
export const AdvancedFilterCountBadge: Story = {
decorators: [
(Story) => {
const companyObjectMetadataItem =
getTestEnrichedObjectMetadataItemsMock().find(
(item) => item.nameSingular === CoreObjectNameSingular.Company,
)!;
const instanceId = companyObjectMetadataItem.id;
const setCurrentRecordFilterGroups = useSetAtomComponentState(
currentRecordFilterGroupsComponentState,
instanceId,
);
const setCurrentRecordFilters = useSetAtomComponentState(
currentRecordFiltersComponentState,
instanceId,
);
const firstFieldMetadataItem = companyObjectMetadataItem.fields[0];
useEffect(() => {
setCurrentRecordFilterGroups([
{
id: MOCK_ROOT_FILTER_GROUP_ID,
logicalOperator: RecordFilterGroupLogicalOperator.AND,
positionInRecordFilterGroup: 0,
},
]);
setCurrentRecordFilters([
{
id: 'filter-1',
fieldMetadataId: firstFieldMetadataItem.id,
value: 'test-value-1',
displayValue: 'Test 1',
type: 'TEXT',
operand: ViewFilterOperand.CONTAINS,
label: firstFieldMetadataItem.label,
recordFilterGroupId: MOCK_ROOT_FILTER_GROUP_ID,
positionInRecordFilterGroup: 0,
},
{
id: 'filter-2',
fieldMetadataId: firstFieldMetadataItem.id,
value: 'test-value-2',
displayValue: 'Test 2',
type: 'TEXT',
operand: ViewFilterOperand.CONTAINS,
label: firstFieldMetadataItem.label,
recordFilterGroupId: MOCK_ROOT_FILTER_GROUP_ID,
positionInRecordFilterGroup: 1,
},
{
id: 'filter-3',
fieldMetadataId: firstFieldMetadataItem.id,
value: 'test-value-3',
displayValue: 'Test 3',
type: 'TEXT',
operand: ViewFilterOperand.CONTAINS,
label: firstFieldMetadataItem.label,
recordFilterGroupId: MOCK_ROOT_FILTER_GROUP_ID,
positionInRecordFilterGroup: 2,
},
]);
}, [
setCurrentRecordFilterGroups,
setCurrentRecordFilters,
firstFieldMetadataItem,
]);
return <Story />;
},
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement.ownerDocument.body);
const filterButton = await canvas.findByText('Filter');
await userEvent.click(filterButton);
const advancedFilterButton = await canvas.findByText('Advanced filter');
expect(advancedFilterButton).toBeVisible();
const pillBadge = await canvas.findByText('3');
expect(pillBadge).toBeVisible();
},
};