Feat: Advanced filter (#7700)

Design:


![twenty-advanced-filters-design](https://github.com/user-attachments/assets/7d99971c-9ee1-4a78-a2fb-7ae5a9b3a836)

Not ready to be merged yet!

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
ad-elias
2024-10-24 16:59:59 +02:00
committed by GitHub
parent 1dfeba39eb
commit 315820ec86
99 changed files with 3349 additions and 1079 deletions

View File

@ -0,0 +1,27 @@
import { IconFilterCog } from 'twenty-ui';
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { plural } from 'pluralize';
type AdvancedFilterChipProps = {
onRemove: () => void;
advancedFilterCount?: number;
};
export const AdvancedFilterChip = ({
onRemove,
advancedFilterCount,
}: AdvancedFilterChipProps) => {
const labelText = 'advanced rule';
const chipLabel = `${advancedFilterCount ?? 0} ${advancedFilterCount === 1 ? labelText : plural(labelText)}`;
return (
<SortOrFilterChip
testId={ADVANCED_FILTER_DROPDOWN_ID}
labelKey={chipLabel}
labelValue=""
Icon={IconFilterCog}
onRemove={onRemove}
/>
);
};

View File

@ -0,0 +1,83 @@
import { useCallback } from 'react';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { AdvancedFilterRootLevelViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup';
import { useDeleteCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useDeleteCombinedViewFilterGroup';
import { AdvancedFilterChip } from '@/views/components/AdvancedFilterChip';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from 'twenty-ui';
export const AdvancedFilterDropdownButton = () => {
const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters();
const { deleteCombinedViewFilterGroup } = useDeleteCombinedViewFilterGroup();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const advancedViewFilterIds =
currentViewWithCombinedFiltersAndSorts?.viewFilters
.filter((viewFilter) => isDefined(viewFilter.viewFilterGroupId))
.map((viewFilter) => viewFilter.id);
const handleDropdownClickOutside = useCallback(() => {}, []);
const handleDropdownClose = () => {};
const removeAdvancedFilter = useCallback(async () => {
if (!advancedViewFilterIds) {
throw new Error('No advanced view filters to remove');
}
const viewFilterGroupIds =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups?.map(
(viewFilter) => viewFilter.id,
) ?? [];
for (const viewFilterGroupId of viewFilterGroupIds) {
await deleteCombinedViewFilterGroup(viewFilterGroupId);
}
for (const viewFilterId of advancedViewFilterIds) {
await deleteCombinedViewFilter(viewFilterId);
}
}, [
advancedViewFilterIds,
deleteCombinedViewFilter,
deleteCombinedViewFilterGroup,
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups,
]);
const outermostViewFilterGroupId =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.find(
(viewFilterGroup) => !viewFilterGroup.parentViewFilterGroupId,
)?.id;
if (!outermostViewFilterGroupId) {
return null;
}
return (
<Dropdown
dropdownId={ADVANCED_FILTER_DROPDOWN_ID}
clickableComponent={
<AdvancedFilterChip
onRemove={removeAdvancedFilter}
advancedFilterCount={advancedViewFilterIds?.length}
/>
}
dropdownComponents={
<AdvancedFilterRootLevelViewFilterGroup
rootLevelViewFilterGroupId={outermostViewFilterGroupId}
/>
}
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
dropdownMenuWidth={800}
onClickOutside={handleDropdownClickOutside}
onClose={handleDropdownClose}
/>
);
};

View File

@ -1,6 +1,5 @@
import { useCallback, useEffect } from 'react';
import { MultipleFiltersDropdownContent } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterOperand } from '@/object-record/object-filter-dropdown/types/FilterOperand';
@ -11,6 +10,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { EditableFilterChip } from '@/views/components/EditableFilterChip';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { isDefined } from '~/utils/isDefined';
@ -98,7 +98,7 @@ export const EditableFilterDropdownButton = ({
<EditableFilterChip viewFilter={viewFilter} onRemove={handleRemove} />
}
dropdownComponents={
<MultipleFiltersDropdownContent
<ObjectFilterOperandSelectAndInput
filterDropdownId={viewFilterDropdownId}
/>
}

View File

@ -7,6 +7,7 @@ import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton';
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
import { EditableSortChip } from '@/views/components/EditableSortChip';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
@ -137,11 +138,16 @@ export const ViewBarDetails = ({
const otherViewFilters =
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
(viewFilter) => viewFilter.variant && viewFilter.variant !== 'default',
(viewFilter) =>
viewFilter.variant &&
viewFilter.variant !== 'default' &&
!viewFilter.viewFilterGroupId,
);
const defaultViewFilters =
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
(viewFilter) => !viewFilter.variant || viewFilter.variant === 'default',
(viewFilter) =>
(!viewFilter.variant || viewFilter.variant === 'default') &&
!viewFilter.viewFilterGroupId,
);
return {
@ -166,6 +172,10 @@ export const ViewBarDetails = ({
return null;
}
const showAdvancedFilterDropdownButton =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups &&
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.length > 0;
return (
<StyledBar>
<StyledFilterContainer>
@ -199,6 +209,7 @@ export const ViewBarDetails = ({
<StyledSeperator />
</StyledSeperatorContainer>
)}
{showAdvancedFilterDropdownButton && <AdvancedFilterDropdownButton />}
{mapViewFiltersToFilters(
defaultViewFilters,
availableFilterDefinitions,