Fix advanced filter creation of group rule (#10690)

This PR improves advanced filter code and fixes the bug that prevented
the creation of a filter group.

On the debugging side : 
- Adding an advanced filter rule to create a group now works

On the refactoring side : 
- We now use AdvancedFilterRecordFilterGroupChildOptionsDropdown to
clarify the code that show the option dropdown of a group.
- Refacatored useCurrentViewViewFilterGroup to
useChildRecordFiltersAndRecordFilterGroups. It is now using only
RecordFilter and RecordFilterGroup type instead of view types. It also
exports recordFilters and recordFilterGroups alone, when they are
children of a group, so we don't have to extract them from the merged
array that is typed RecordFilter | RecordFilterGroup, which is necessary
for displaying a group.
- Two typeguards have been introduced to help discern RecordFilter from
RecordFilterGroup : isRecordFilterGroupChildARecordFilterGroup and
isRecordFilterGroupChildARecordFilter, this allows to remove any typing
on child processing.
- Renaming from view to record (but there are still some left)
This commit is contained in:
Lucas Bordeau
2025-03-06 17:57:23 +01:00
committed by GitHub
parent cb5f4820d7
commit 777c12dd06
13 changed files with 293 additions and 223 deletions

View File

@ -1,6 +1,5 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter';
import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
@ -12,8 +11,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import { IconLibraryPlus, IconPlus, LightButton, MenuItem } from 'twenty-ui';
import { v4 } from 'uuid';
@ -41,55 +38,23 @@ export const AdvancedFilterAddFilterRuleSelect = ({
const { closeDropdown } = useDropdown(dropdownId);
const objectMetadataId = currentView?.objectMetadataId;
if (!isDefined(objectMetadataId)) {
throw new Error('Object metadata id is missing from current view');
}
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: objectMetadataId,
});
const availableFieldMetadataItemsForFilter = useRecoilValue(
availableFieldMetadataItemsForFilterFamilySelector({
objectMetadataItemId: objectMetadataId,
}),
);
const getDefaultFieldMetadataItem = useCallback(() => {
const defaultFieldMetadataItem =
availableFieldMetadataItemsForFilter.find(
(fieldMetadataItem) =>
fieldMetadataItem.id ===
objectMetadataItem?.labelIdentifierFieldMetadataId,
) ?? availableFieldMetadataItemsForFilter[0];
if (!isDefined(defaultFieldMetadataItem)) {
throw new Error(
`Could not find default field metadata item for object ${objectMetadataId}`,
);
}
return defaultFieldMetadataItem;
}, [
availableFieldMetadataItemsForFilter,
objectMetadataItem,
objectMetadataId,
]);
const { defaultFieldMetadataItemForFilter } =
useDefaultFieldMetadataItemForFilter();
const handleAddFilter = () => {
if (!isDefined(defaultFieldMetadataItemForFilter)) {
throw new Error('Missing default field metadata item for filter');
}
closeDropdown();
const defaultFieldMetadataItem = getDefaultFieldMetadataItem();
const filterType = getFilterTypeFromFieldType(
defaultFieldMetadataItem.type,
defaultFieldMetadataItemForFilter.type,
);
upsertRecordFilter({
id: v4(),
fieldMetadataId: defaultFieldMetadataItem.id,
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
type: filterType,
operand: getRecordFilterOperands({
filterType,
@ -98,13 +63,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({
displayValue: '',
recordFilterGroupId: recordFilterGroup.id,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItem.label,
label: defaultFieldMetadataItemForFilter.label,
});
};
const handleAddFilterGroup = () => {
closeDropdown();
if (!isDefined(defaultFieldMetadataItemForFilter)) {
throw new Error('Missing default field metadata item for filter');
}
if (!isDefined(currentView)) {
throw new Error('Missing view');
}
@ -120,15 +89,13 @@ export const AdvancedFilterAddFilterRuleSelect = ({
upsertRecordFilterGroup(newRecordFilterGroup);
const defaultFieldMetadataItem = getDefaultFieldMetadataItem();
const filterType = getFilterTypeFromFieldType(
defaultFieldMetadataItem.type,
defaultFieldMetadataItemForFilter.type,
);
upsertRecordFilter({
id: v4(),
fieldMetadataId: defaultFieldMetadataItem.id,
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
type: filterType,
operand: getRecordFilterOperands({
filterType,
@ -136,8 +103,8 @@ export const AdvancedFilterAddFilterRuleSelect = ({
value: '',
displayValue: '',
recordFilterGroupId: newRecordFilterGroupId,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItem.label,
positionInRecordFilterGroup: 1,
label: defaultFieldMetadataItemForFilter.label,
});
};

View File

@ -1,9 +1,11 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown';
import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown';
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledRow = styled.div`
display: flex;
@ -25,41 +27,45 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
overflow: hidden;
`;
type AdvancedFilterViewFilterGroupProps = {
viewFilterGroupId: string;
type AdvancedFilterRecordFilterGroupProps = {
recordFilterGroupId: string;
};
export const AdvancedFilterViewFilterGroup = ({
viewFilterGroupId,
}: AdvancedFilterViewFilterGroupProps) => {
export const AdvancedFilterRecordFilterGroup = ({
recordFilterGroupId,
}: AdvancedFilterRecordFilterGroupProps) => {
const {
currentViewFilterGroup,
childViewFiltersAndViewFilterGroups,
currentRecordFilterGroup,
childRecordFiltersAndRecordFilterGroups,
lastChildPosition,
} = useCurrentViewViewFilterGroup({
recordFilterGroupId: viewFilterGroupId,
} = useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId,
});
if (!currentViewFilterGroup) {
if (!currentRecordFilterGroup) {
return null;
}
const hasParentRecordFilterGroup = isDefined(
currentRecordFilterGroup.parentRecordFilterGroupId,
);
return (
<StyledContainer
isGrayBackground={!!currentViewFilterGroup.parentRecordFilterGroupId}
>
{childViewFiltersAndViewFilterGroups.map((child, i) => (
<StyledContainer isGrayBackground={hasParentRecordFilterGroup}>
{childRecordFiltersAndRecordFilterGroups.map((child, i) => (
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
recordFilterGroup={currentViewFilterGroup}
recordFilterGroup={currentRecordFilterGroup}
/>
<AdvancedFilterViewFilter viewFilterId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={child}
/>
</StyledRow>
))}
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={currentViewFilterGroup}
recordFilterGroup={currentRecordFilterGroup}
lastChildPosition={lastChildPosition}
/>
</StyledContainer>

View File

@ -0,0 +1,27 @@
import { AdvancedFilterRecordFilterGroupOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown';
import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown';
import { isRecordFilterGroupChildARecordFilter } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
type AdvancedFilterRecordFilterGroupChildOptionsDropdownProps = {
recordFilterGroupChild: RecordFilter | RecordFilterGroup;
};
export const AdvancedFilterRecordFilterGroupChildOptionsDropdown = ({
recordFilterGroupChild,
}: AdvancedFilterRecordFilterGroupChildOptionsDropdownProps) => {
const isRecordFilter = isRecordFilterGroupChildARecordFilter(
recordFilterGroupChild,
);
return isRecordFilter ? (
<AdvancedFilterRecordFilterOptionsDropdown
recordFilterId={recordFilterGroupChild.id}
/>
) : (
<AdvancedFilterRecordFilterGroupOptionsDropdown
recordFilterGroupId={recordFilterGroupChild.id}
/>
);
};

View File

@ -0,0 +1,53 @@
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui';
type AdvancedFilterRecordFilterGroupOptionsDropdownProps = {
recordFilterGroupId: string;
};
export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
recordFilterGroupId,
}: AdvancedFilterRecordFilterGroupOptionsDropdownProps) => {
const dropdownId = `advanced-filter-record-filter-group-options-${recordFilterGroupId}`;
const { removeRecordFilter } = useRemoveRecordFilter();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
const { childRecordFilters } = useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId,
});
const handleRemove = () => {
for (const childRecordFilter of childRecordFilters ?? []) {
removeRecordFilter({ recordFilterId: childRecordFilter.id });
}
removeRecordFilterGroup(recordFilterGroupId);
};
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<IconButton
aria-label="Filter group rule options"
variant="tertiary"
Icon={IconDotsVertical}
/>
}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem text="Remove rule group" onClick={handleRemove} />
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>
);
};

View File

@ -0,0 +1,71 @@
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared';
import { IconButton, IconDotsVertical, MenuItem } from 'twenty-ui';
type AdvancedFilterRecordFilterOptionsDropdownProps = {
recordFilterId: string;
};
export const AdvancedFilterRecordFilterOptionsDropdown = ({
recordFilterId,
}: AdvancedFilterRecordFilterOptionsDropdownProps) => {
const dropdownId = `advanced-filter-record-filter-options-${recordFilterId}`;
const { removeRecordFilter } = useRemoveRecordFilter();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const currentRecordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const { childRecordFiltersAndRecordFilterGroups } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: currentRecordFilter?.recordFilterGroupId,
});
const handleRemove = async () => {
removeRecordFilter({ recordFilterId: recordFilterId });
if (isDefined(currentRecordFilter?.recordFilterGroupId)) {
const isOnlyViewFilterInGroup =
childRecordFiltersAndRecordFilterGroups?.length === 1;
if (isOnlyViewFilterInGroup) {
removeRecordFilterGroup(currentRecordFilter.recordFilterGroupId);
}
}
};
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<IconButton
aria-label="Record filter rule options"
variant="tertiary"
Icon={IconDotsVertical}
/>
}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem text="Remove rule" onClick={handleRemove} />
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>
);
};

View File

@ -1,9 +1,11 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRuleOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown';
import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown';
import { AdvancedFilterRecordFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup';
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
import { AdvancedFilterViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterGroup';
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
@ -28,49 +30,53 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
`;
type AdvancedFilterRootLevelViewFilterGroupProps = {
rootLevelViewFilterGroupId: string;
rootLevelRecordFilterGroupId: string;
};
export const AdvancedFilterRootLevelViewFilterGroup = ({
rootLevelViewFilterGroupId,
rootLevelRecordFilterGroupId,
}: AdvancedFilterRootLevelViewFilterGroupProps) => {
const {
currentViewFilterGroup: rootLevelViewFilterGroup,
childViewFiltersAndViewFilterGroups,
currentRecordFilterGroup: rootLevelRecordFilterGroup,
childRecordFiltersAndRecordFilterGroups,
lastChildPosition,
} = useCurrentViewViewFilterGroup({
recordFilterGroupId: rootLevelViewFilterGroupId,
} = useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: rootLevelRecordFilterGroupId,
});
if (!isDefined(rootLevelViewFilterGroup)) {
if (!isDefined(rootLevelRecordFilterGroup)) {
return null;
}
return (
<StyledContainer>
{childViewFiltersAndViewFilterGroups.map((child, i) =>
(child as any).__typename === 'ViewFilterGroup' ? (
{childRecordFiltersAndRecordFilterGroups.map((child, i) =>
isRecordFilterGroupChildARecordFilterGroup(child) ? (
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
recordFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelRecordFilterGroup}
/>
<AdvancedFilterRecordFilterGroup recordFilterGroupId={child.id} />
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={child}
/>
<AdvancedFilterViewFilterGroup viewFilterGroupId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterGroupId={child.id} />
</StyledRow>
) : (
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
recordFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelRecordFilterGroup}
/>
<AdvancedFilterViewFilter viewFilterId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={child}
/>
</StyledRow>
),
)}
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelRecordFilterGroup}
lastChildPosition={lastChildPosition}
/>
</StyledContainer>

View File

@ -1,93 +0,0 @@
import { AdvancedFilterRuleOptionsDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton';
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
import { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared';
import { MenuItem } from 'twenty-ui';
type AdvancedFilterRuleOptionsDropdownProps =
| {
viewFilterId: string;
viewFilterGroupId?: never;
}
| {
viewFilterId?: never;
viewFilterGroupId: string;
};
export const AdvancedFilterRuleOptionsDropdown = ({
viewFilterId,
viewFilterGroupId,
}: AdvancedFilterRuleOptionsDropdownProps) => {
const dropdownId = `advanced-filter-rule-options-${viewFilterId ?? viewFilterGroupId}`;
const { removeRecordFilter } = useRemoveRecordFilter();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
const { currentViewFilterGroup, childViewFiltersAndViewFilterGroups } =
useCurrentViewViewFilterGroup({
recordFilterGroupId: viewFilterGroupId,
});
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const currentRecordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === viewFilterId,
);
const handleRemove = async () => {
if (isDefined(viewFilterId)) {
removeRecordFilter({ recordFilterId: viewFilterId });
const isOnlyViewFilterInGroup =
childViewFiltersAndViewFilterGroups.length === 1;
if (
isOnlyViewFilterInGroup &&
isDefined(currentRecordFilter?.recordFilterGroupId)
) {
removeRecordFilterGroup(currentRecordFilter.recordFilterGroupId);
}
} else if (isDefined(currentViewFilterGroup)) {
removeRecordFilterGroup(currentViewFilterGroup.id);
// TODO: This is a temporary fix view filter group will be removed soon.
const childViewFilters = childViewFiltersAndViewFilterGroups.filter(
(child) => (child as any).__typename === 'ViewFilter',
);
for (const childViewFilter of childViewFilters) {
removeRecordFilter({ recordFilterId: childViewFilter.id });
}
} else {
throw new Error('No view filter or view filter group to remove');
}
};
const removeButtonLabel = viewFilterId ? 'Remove rule' : 'Remove rule group';
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<AdvancedFilterRuleOptionsDropdownButton dropdownId={dropdownId} />
}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem text={removeButtonLabel} onClick={handleRemove} />
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>
);
};

View File

@ -1,25 +0,0 @@
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { IconButton, IconDotsVertical } from 'twenty-ui';
type AdvancedFilterRuleOptionsDropdownButtonProps = {
dropdownId: string;
};
export const AdvancedFilterRuleOptionsDropdownButton = ({
dropdownId,
}: AdvancedFilterRuleOptionsDropdownButtonProps) => {
const { toggleDropdown } = useDropdown(dropdownId);
const handleClick = () => {
toggleDropdown();
};
return (
<IconButton
aria-label="Filter rule options"
variant="tertiary"
Icon={IconDotsVertical}
onClick={handleClick}
/>
);
};

View File

@ -1,11 +1,9 @@
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { isDefined } from 'twenty-shared';
export const useCurrentViewViewFilterGroup = ({
export const useChildRecordFiltersAndRecordFilterGroups = ({
recordFilterGroupId,
}: {
recordFilterGroupId?: string;
@ -24,11 +22,11 @@ export const useCurrentViewViewFilterGroup = ({
if (!isDefined(currentRecordFilterGroup)) {
return {
currentViewFilterGroup: undefined,
childViewFiltersAndViewFilterGroups: [] as (
| ViewFilter
| ViewFilterGroup
)[],
currentRecordFilterGroup: undefined,
childRecordFiltersAndRecordFilterGroups: [],
childRecordFilters: [],
childRecordFilterGroups: [],
lastChildPosition: 0,
};
}
@ -37,15 +35,15 @@ export const useCurrentViewViewFilterGroup = ({
recordFilterToFilter.recordFilterGroupId === currentRecordFilterGroup.id,
);
const childViewFilterGroups = currentRecordFilterGroups.filter(
const childRecordFilterGroups = currentRecordFilterGroups.filter(
(currentRecordGroupToFilter) =>
currentRecordGroupToFilter.parentRecordFilterGroupId ===
currentRecordFilterGroup.id,
);
const childViewFiltersAndViewFilterGroups = [
...(childViewFilterGroups ?? []),
...(childRecordFilters ?? []),
const childRecordFiltersAndRecordFilterGroups = [
...childRecordFilterGroups,
...childRecordFilters,
].sort((a, b) => {
const positionA = a.positionInRecordFilterGroup ?? 0;
const positionB = b.positionInRecordFilterGroup ?? 0;
@ -53,13 +51,15 @@ export const useCurrentViewViewFilterGroup = ({
});
const lastChildPosition =
childViewFiltersAndViewFilterGroups[
childViewFiltersAndViewFilterGroups.length - 1
childRecordFiltersAndRecordFilterGroups[
childRecordFiltersAndRecordFilterGroups.length - 1
]?.positionInRecordFilterGroup ?? 0;
return {
currentViewFilterGroup: currentRecordFilterGroup,
childViewFiltersAndViewFilterGroups,
currentRecordFilterGroup,
childRecordFiltersAndRecordFilterGroups,
childRecordFilters,
childRecordFilterGroups,
lastChildPosition,
};
};

View File

@ -0,0 +1,42 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
export const useDefaultFieldMetadataItemForFilter = () => {
const { currentView } = useGetCurrentViewOnly();
const objectMetadataId = currentView?.objectMetadataId;
if (!isDefined(objectMetadataId)) {
throw new Error('Object metadata id is missing from current view');
}
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: objectMetadataId,
});
const availableFieldMetadataItemsForFilter = useRecoilValue(
availableFieldMetadataItemsForFilterFamilySelector({
objectMetadataItemId: objectMetadataId,
}),
);
const fieldMetadataItemForLabelIdentifier =
availableFieldMetadataItemsForFilter.find(
(fieldMetadataItem) =>
fieldMetadataItem.id ===
objectMetadataItem?.labelIdentifierFieldMetadataId,
);
const firstFieldMetadataItem = availableFieldMetadataItemsForFilter?.[0] as
| FieldMetadataItem
| undefined;
const defaultFieldMetadataItemForFilter =
fieldMetadataItemForLabelIdentifier ?? firstFieldMetadataItem;
return { defaultFieldMetadataItemForFilter };
};

View File

@ -0,0 +1,8 @@
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
export const isRecordFilterGroupChildARecordFilter = (
child: RecordFilter | RecordFilterGroup,
): child is RecordFilter => {
return ('fieldMetadataId' satisfies keyof RecordFilter) in child;
};

View File

@ -0,0 +1,8 @@
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
export const isRecordFilterGroupChildARecordFilterGroup = (
child: RecordFilter | RecordFilterGroup,
): child is RecordFilterGroup => {
return ('logicalOperator' satisfies keyof RecordFilterGroup) in child;
};

View File

@ -72,7 +72,7 @@ export const AdvancedFilterDropdownButton = () => {
}
dropdownComponents={
<AdvancedFilterRootLevelViewFilterGroup
rootLevelViewFilterGroupId={outermostRecordFilterGroupId}
rootLevelRecordFilterGroupId={outermostRecordFilterGroupId}
/>
}
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}