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:
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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 };
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -72,7 +72,7 @@ export const AdvancedFilterDropdownButton = () => {
|
||||
}
|
||||
dropdownComponents={
|
||||
<AdvancedFilterRootLevelViewFilterGroup
|
||||
rootLevelViewFilterGroupId={outermostRecordFilterGroupId}
|
||||
rootLevelRecordFilterGroupId={outermostRecordFilterGroupId}
|
||||
/>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||
|
||||
Reference in New Issue
Block a user