Implemented CRUD for view filter group and removed old states (#10590)

This PR implements CRUD for view filter groups with the new logic as
already done for view filters and view sorts.

It also completely removes the old combined view filter group states and
usage.

This PR is quite big but the impact is limited since it only changes
advanced filters module, which is under feature flag at the moment, and
it is already in a broken state so unusable, even if someone activates
the feature flag.
This commit is contained in:
Lucas Bordeau
2025-03-04 13:16:02 +01:00
committed by GitHub
parent 9d80d2a8ef
commit aba20dae11
54 changed files with 304 additions and 681 deletions

View File

@ -1,7 +1,6 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
@ -12,8 +11,7 @@ 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 { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
@ -21,31 +19,29 @@ import { IconLibraryPlus, IconPlus, LightButton, MenuItem } from 'twenty-ui';
import { v4 } from 'uuid';
type AdvancedFilterAddFilterRuleSelectProps = {
viewFilterGroup: ViewFilterGroup;
recordFilterGroup: RecordFilterGroup;
lastChildPosition?: number;
};
export const AdvancedFilterAddFilterRuleSelect = ({
viewFilterGroup,
recordFilterGroup,
lastChildPosition = 0,
}: AdvancedFilterAddFilterRuleSelectProps) => {
const dropdownId = `advanced-filter-add-filter-rule-${viewFilterGroup.id}`;
const dropdownId = `advanced-filter-add-filter-rule-${recordFilterGroup.id}`;
const { currentViewId } = useGetCurrentView();
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup();
const { upsertRecordFilter } = useUpsertRecordFilter();
const newPositionInViewFilterGroup = lastChildPosition + 1;
const newPositionInRecordFilterGroup = lastChildPosition + 1;
const { closeDropdown } = useDropdown(dropdownId);
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const objectMetadataId =
currentViewWithCombinedFiltersAndSorts?.objectMetadataId;
const objectMetadataId = currentView?.objectMetadataId;
if (!isDefined(objectMetadataId)) {
throw new Error('Object metadata id is missing from current view');
@ -100,8 +96,8 @@ export const AdvancedFilterAddFilterRuleSelect = ({
})[0],
value: '',
displayValue: '',
viewFilterGroupId: viewFilterGroup.id,
positionInViewFilterGroup: newPositionInViewFilterGroup,
recordFilterGroupId: recordFilterGroup.id,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItem.label,
});
};
@ -115,23 +111,13 @@ export const AdvancedFilterAddFilterRuleSelect = ({
const newRecordFilterGroupId = v4();
const newViewFilterGroup: ViewFilterGroup = {
__typename: 'ViewFilterGroup',
id: newRecordFilterGroupId,
viewId: currentViewId,
logicalOperator: ViewFilterGroupLogicalOperator.AND,
parentViewFilterGroupId: viewFilterGroup.id,
positionInViewFilterGroup: newPositionInViewFilterGroup,
};
const newRecordFilterGroup: RecordFilterGroup = {
id: newRecordFilterGroupId,
logicalOperator: RecordFilterGroupLogicalOperator.AND,
parentRecordFilterGroupId: viewFilterGroup.id,
positionInRecordFilterGroup: newPositionInViewFilterGroup,
parentRecordFilterGroupId: recordFilterGroup.id,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
};
upsertCombinedViewFilterGroup(newViewFilterGroup);
upsertRecordFilterGroup(newRecordFilterGroup);
const defaultFieldMetadataItem = getDefaultFieldMetadataItem();
@ -149,14 +135,14 @@ export const AdvancedFilterAddFilterRuleSelect = ({
})[0],
value: '',
displayValue: '',
viewFilterGroupId: newViewFilterGroup.id,
positionInViewFilterGroup: newPositionInViewFilterGroup,
recordFilterGroupId: newRecordFilterGroupId,
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
label: defaultFieldMetadataItem.label,
});
};
const isFilterRuleGroupOptionVisible = !isDefined(
viewFilterGroup.parentViewFilterGroupId,
recordFilterGroup.parentRecordFilterGroupId,
);
if (!isFilterRuleGroupOptionVisible) {

View File

@ -1,5 +1,6 @@
import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import styled from '@emotion/styled';
import { capitalize } from 'twenty-shared';
@ -18,23 +19,23 @@ const StyledContainer = styled.div`
type AdvancedFilterLogicalOperatorCellProps = {
index: number;
viewFilterGroup: ViewFilterGroup;
recordFilterGroup: RecordFilterGroup;
};
export const AdvancedFilterLogicalOperatorCell = ({
index,
viewFilterGroup,
recordFilterGroup,
}: AdvancedFilterLogicalOperatorCellProps) => (
<StyledContainer>
{index === 0 ? (
<StyledText>Where</StyledText>
) : index === 1 ? (
<AdvancedFilterLogicalOperatorDropdown
viewFilterGroup={viewFilterGroup}
recordFilterGroup={recordFilterGroup}
/>
) : (
<StyledText>
{capitalize(viewFilterGroup.logicalOperator.toLowerCase())}
{capitalize(recordFilterGroup.logicalOperator.toLowerCase())}
</StyledText>
)}
</StyledContainer>

View File

@ -1,43 +1,33 @@
import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions';
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { Select } from '@/ui/input/components/Select';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
type AdvancedFilterLogicalOperatorDropdownProps = {
viewFilterGroup: ViewFilterGroup;
recordFilterGroup: RecordFilterGroup;
};
export const AdvancedFilterLogicalOperatorDropdown = ({
viewFilterGroup,
recordFilterGroup,
}: AdvancedFilterLogicalOperatorDropdownProps) => {
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup();
const handleChange = (value: ViewFilterGroupLogicalOperator) => {
upsertCombinedViewFilterGroup({
...viewFilterGroup,
logicalOperator: value,
});
const handleChange = (value: RecordFilterGroupLogicalOperator) => {
upsertRecordFilterGroup({
id: viewFilterGroup.id,
parentRecordFilterGroupId: viewFilterGroup.parentViewFilterGroupId,
positionInRecordFilterGroup: viewFilterGroup.positionInViewFilterGroup,
logicalOperator:
value === ViewFilterGroupLogicalOperator.AND
? RecordFilterGroupLogicalOperator.AND
: RecordFilterGroupLogicalOperator.OR,
id: recordFilterGroup.id,
parentRecordFilterGroupId: recordFilterGroup.parentRecordFilterGroupId,
positionInRecordFilterGroup:
recordFilterGroup.positionInRecordFilterGroup,
logicalOperator: value,
});
};
return (
<Select
fullWidth
dropdownId={`advanced-filter-logical-operator-${viewFilterGroup.id}`}
value={viewFilterGroup.logicalOperator}
dropdownId={`advanced-filter-logical-operator-${recordFilterGroup.id}`}
value={recordFilterGroup.logicalOperator}
onChange={handleChange}
options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS}
/>

View File

@ -39,7 +39,7 @@ export const AdvancedFilterRootLevelViewFilterGroup = ({
childViewFiltersAndViewFilterGroups,
lastChildPosition,
} = useCurrentViewViewFilterGroup({
viewFilterGroupId: rootLevelViewFilterGroupId,
recordFilterGroupId: rootLevelViewFilterGroupId,
});
if (!isDefined(rootLevelViewFilterGroup)) {
@ -53,7 +53,7 @@ export const AdvancedFilterRootLevelViewFilterGroup = ({
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
viewFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelViewFilterGroup}
/>
<AdvancedFilterViewFilterGroup viewFilterGroupId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterGroupId={child.id} />
@ -62,7 +62,7 @@ export const AdvancedFilterRootLevelViewFilterGroup = ({
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
viewFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelViewFilterGroup}
/>
<AdvancedFilterViewFilter viewFilterId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
@ -70,7 +70,7 @@ export const AdvancedFilterRootLevelViewFilterGroup = ({
),
)}
<AdvancedFilterAddFilterRuleSelect
viewFilterGroup={rootLevelViewFilterGroup}
recordFilterGroup={rootLevelViewFilterGroup}
lastChildPosition={lastChildPosition}
/>
</StyledContainer>

View File

@ -1,7 +1,6 @@
import { AdvancedFilterRuleOptionsDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdownButton';
import { useCurrentViewViewFilterGroup } from '@/object-record/advanced-filter/hooks/useCurrentViewViewFilterGroup';
import { useDeleteCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useDeleteCombinedViewFilterGroup';
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';
@ -30,12 +29,11 @@ export const AdvancedFilterRuleOptionsDropdown = ({
const dropdownId = `advanced-filter-rule-options-${viewFilterId ?? viewFilterGroupId}`;
const { removeRecordFilter } = useRemoveRecordFilter();
const { deleteCombinedViewFilterGroup } = useDeleteCombinedViewFilterGroup();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
const { currentViewFilterGroup, childViewFiltersAndViewFilterGroups } =
useCurrentViewViewFilterGroup({
viewFilterGroupId,
recordFilterGroupId: viewFilterGroupId,
});
const currentRecordFilters = useRecoilComponentValueV2(
@ -55,13 +53,11 @@ export const AdvancedFilterRuleOptionsDropdown = ({
if (
isOnlyViewFilterInGroup &&
isDefined(currentRecordFilter?.viewFilterGroupId)
isDefined(currentRecordFilter?.recordFilterGroupId)
) {
deleteCombinedViewFilterGroup(currentRecordFilter.viewFilterGroupId);
removeRecordFilterGroup(currentRecordFilter.viewFilterGroupId);
removeRecordFilterGroup(currentRecordFilter.recordFilterGroupId);
}
} else if (isDefined(currentViewFilterGroup)) {
deleteCombinedViewFilterGroup(currentViewFilterGroup.id);
removeRecordFilterGroup(currentViewFilterGroup.id);
// TODO: This is a temporary fix view filter group will be removed soon.

View File

@ -67,7 +67,7 @@ export const AdvancedFilterViewFilterFieldSelect = ({
}
onOpen={() => {
setAdvancedFilterViewFilterId(recordFilter?.id);
setAdvancedFilterViewFilterGroupId(recordFilter?.viewFilterGroupId);
setAdvancedFilterViewFilterGroupId(recordFilter?.recordFilterGroupId);
}}
dropdownComponents={
shouldShowCompositeSelectionSubMenu ? (

View File

@ -37,7 +37,7 @@ export const AdvancedFilterViewFilterGroup = ({
childViewFiltersAndViewFilterGroups,
lastChildPosition,
} = useCurrentViewViewFilterGroup({
viewFilterGroupId,
recordFilterGroupId: viewFilterGroupId,
});
if (!currentViewFilterGroup) {
@ -46,20 +46,20 @@ export const AdvancedFilterViewFilterGroup = ({
return (
<StyledContainer
isGrayBackground={!!currentViewFilterGroup.parentViewFilterGroupId}
isGrayBackground={!!currentViewFilterGroup.parentRecordFilterGroupId}
>
{childViewFiltersAndViewFilterGroups.map((child, i) => (
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
viewFilterGroup={currentViewFilterGroup}
recordFilterGroup={currentViewFilterGroup}
/>
<AdvancedFilterViewFilter viewFilterId={child.id} />
<AdvancedFilterRuleOptionsDropdown viewFilterId={child.id} />
</StyledRow>
))}
<AdvancedFilterAddFilterRuleSelect
viewFilterGroup={currentViewFilterGroup}
recordFilterGroup={currentViewFilterGroup}
lastChildPosition={lastChildPosition}
/>
</StyledContainer>

View File

@ -1,12 +1,12 @@
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
export const ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS = [
{
value: ViewFilterGroupLogicalOperator.AND,
value: RecordFilterGroupLogicalOperator.AND,
label: 'And',
},
{
value: ViewFilterGroupLogicalOperator.OR,
value: RecordFilterGroupLogicalOperator.OR,
label: 'Or',
},
];

View File

@ -1,27 +1,28 @@
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 { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { isDefined } from 'twenty-shared';
export const useCurrentViewViewFilterGroup = ({
viewFilterGroupId,
recordFilterGroupId,
}: {
viewFilterGroupId?: string;
recordFilterGroupId?: string;
}) => {
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const viewFilterGroup =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.find(
(viewFilterGroup) => viewFilterGroup.id === viewFilterGroupId,
);
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
if (!isDefined(viewFilterGroup)) {
const currentRecordFilterGroup = currentRecordFilterGroups.find(
(recordFilterGroup) => recordFilterGroup.id === recordFilterGroupId,
);
if (!isDefined(currentRecordFilterGroup)) {
return {
currentViewFilterGroup: undefined,
childViewFiltersAndViewFilterGroups: [] as (
@ -33,31 +34,31 @@ export const useCurrentViewViewFilterGroup = ({
const childRecordFilters = currentRecordFilters.filter(
(recordFilterToFilter) =>
recordFilterToFilter.viewFilterGroupId === viewFilterGroup.id,
recordFilterToFilter.recordFilterGroupId === currentRecordFilterGroup.id,
);
const childViewFilterGroups =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.filter(
(viewFilterGroupToFilter) =>
viewFilterGroupToFilter.parentViewFilterGroupId === viewFilterGroup.id,
);
const childViewFilterGroups = currentRecordFilterGroups.filter(
(currentRecordGroupToFilter) =>
currentRecordGroupToFilter.parentRecordFilterGroupId ===
currentRecordFilterGroup.id,
);
const childViewFiltersAndViewFilterGroups = [
...(childViewFilterGroups ?? []),
...(childRecordFilters ?? []),
].sort((a, b) => {
const positionA = a.positionInViewFilterGroup ?? 0;
const positionB = b.positionInViewFilterGroup ?? 0;
const positionA = a.positionInRecordFilterGroup ?? 0;
const positionB = b.positionInRecordFilterGroup ?? 0;
return positionA - positionB;
});
const lastChildPosition =
childViewFiltersAndViewFilterGroups[
childViewFiltersAndViewFilterGroups.length - 1
]?.positionInViewFilterGroup ?? 0;
]?.positionInRecordFilterGroup ?? 0;
return {
currentViewFilterGroup: viewFilterGroup,
currentViewFilterGroup: currentRecordFilterGroup,
childViewFiltersAndViewFilterGroups,
lastChildPosition,
};

View File

@ -1,110 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { isDefined } from 'twenty-shared';
export const useDeleteCombinedViewFilterGroup = (
viewBarComponentId?: string,
) => {
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
viewBarComponentId,
);
const unsavedToDeleteViewFilterGroupIdsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
viewBarComponentId,
);
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
contextStoreCurrentViewIdComponentState,
);
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const deleteCombinedViewFilterGroup = useRecoilCallback(
({ snapshot, set }) =>
async (filterGroupId: string) => {
const currentViewId = getSnapshotValue(
snapshot,
currentViewIdCallbackState,
);
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
snapshot,
unsavedToUpsertViewFilterGroupsCallbackState({
viewId: currentViewId,
}),
);
const unsavedToDeleteViewFilterGroupIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterGroupIdsCallbackState({
viewId: currentViewId,
}),
);
if (!currentViewId) {
return;
}
const currentView = await getViewFromPrefetchState(currentViewId);
if (!currentView) {
return;
}
const matchingFilterGroupInCurrentView =
currentView.viewFilterGroups?.find(
(viewFilterGroup) => viewFilterGroup.id === filterGroupId,
);
const matchingFilterGroupInUnsavedFilterGroups =
unsavedToUpsertViewFilterGroups.find(
(viewFilterGroup) => viewFilterGroup.id === filterGroupId,
);
if (isDefined(matchingFilterGroupInUnsavedFilterGroups)) {
set(
unsavedToUpsertViewFilterGroupsCallbackState({
viewId: currentViewId,
}),
unsavedToUpsertViewFilterGroups.filter(
(viewFilterGroup) => viewFilterGroup.id !== filterGroupId,
),
);
}
if (isDefined(matchingFilterGroupInCurrentView)) {
set(
unsavedToDeleteViewFilterGroupIdsCallbackState({
viewId: currentViewId,
}),
[
...new Set([
...unsavedToDeleteViewFilterGroupIds,
matchingFilterGroupInCurrentView.id,
]),
],
);
}
},
[
currentViewIdCallbackState,
getViewFromPrefetchState,
unsavedToDeleteViewFilterGroupIdsCallbackState,
unsavedToUpsertViewFilterGroupsCallbackState,
],
);
return {
deleteCombinedViewFilterGroup,
};
};

View File

@ -1,54 +0,0 @@
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { useRecoilCallback } from 'recoil';
export const useUpsertCombinedViewFilterGroup = () => {
const instanceId = useAvailableComponentInstanceIdOrThrow(
ViewComponentInstanceContext,
);
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
instanceId,
);
const upsertCombinedViewFilterGroup = useRecoilCallback(
({ snapshot, set }) =>
(newViewFilterGroup: Omit<ViewFilterGroup, '__typename'>) => {
const currentViewUnsavedToUpsertViewFilterGroups =
unsavedToUpsertViewFilterGroupsCallbackState({
viewId: newViewFilterGroup.viewId,
});
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
snapshot,
currentViewUnsavedToUpsertViewFilterGroups,
);
const newViewFilterWithTypename: ViewFilterGroup = {
...newViewFilterGroup,
__typename: 'ViewFilterGroup',
};
set(
unsavedToUpsertViewFilterGroupsCallbackState({
viewId: newViewFilterGroup.viewId,
}),
[
...unsavedToUpsertViewFilterGroups.filter(
(viewFilterGroup) => viewFilterGroup.id !== newViewFilterGroup.id,
),
newViewFilterWithTypename,
],
);
},
[unsavedToUpsertViewFilterGroupsCallbackState],
);
return { upsertCombinedViewFilterGroup };
};

View File

@ -1,16 +1,17 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
@ -56,16 +57,13 @@ export const AdvancedFilterButton = () => {
OBJECT_FILTER_DROPDOWN_ID,
);
const { currentViewId, currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const { upsertCombinedViewFilterGroup } = useUpsertCombinedViewFilterGroup();
const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup();
const { upsertRecordFilter } = useUpsertRecordFilter();
const objectMetadataId =
currentViewWithCombinedFiltersAndSorts?.objectMetadataId;
const objectMetadataId = currentView?.objectMetadataId;
if (!objectMetadataId) {
throw new Error('Object metadata id is missing from current view');
@ -81,24 +79,24 @@ export const AdvancedFilterButton = () => {
}),
);
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
const handleClick = () => {
if (!currentViewId) {
if (!isDefined(currentView)) {
throw new Error('Missing current view id');
}
const alreadyHasAdvancedFilterGroup =
(currentViewWithCombinedFiltersAndSorts?.viewFilterGroups?.length ?? 0) >
0;
const alreadyHasAdvancedFilterGroup = currentRecordFilterGroups.length > 0;
if (!alreadyHasAdvancedFilterGroup) {
const newViewFilterGroup = {
id: v4(),
viewId: currentViewId,
viewId: currentView.id,
logicalOperator: ViewFilterGroupLogicalOperator.AND,
};
upsertCombinedViewFilterGroup(newViewFilterGroup);
upsertRecordFilterGroup({
id: newViewFilterGroup.id,
logicalOperator: RecordFilterGroupLogicalOperator.AND,
@ -129,7 +127,7 @@ export const AdvancedFilterButton = () => {
operand: firstOperand,
value: '',
displayValue: '',
viewFilterGroupId: newViewFilterGroup.id,
recordFilterGroupId: newViewFilterGroup.id,
type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type),
label: defaultFieldMetadataItem.label,
});

View File

@ -78,7 +78,7 @@ export const ObjectFilterDropdownBooleanSelect = () => {
displayValue: value ? 'True' : 'False',
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
value: value.toString(),
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
});

View File

@ -59,7 +59,7 @@ export const ObjectFilterDropdownDateInput = () => {
? newDate.toLocaleString()
: newDate.toLocaleDateString()
: '',
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
});
@ -88,7 +88,7 @@ export const ObjectFilterDropdownDateInput = () => {
value,
operand: selectedOperandInDropdown,
displayValue: getRelativeDateDisplayValue(relativeDate),
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
label: fieldMetadataItemUsedInDropdown.label,
});

View File

@ -18,7 +18,6 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared';
import { FeatureFlagKey } from '~/generated/graphql';
@ -28,6 +27,7 @@ import { advancedFilterViewFilterIdComponentState } from '@/object-record/object
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useLingui } from '@lingui/react/macro';
export const StyledInput = styled.input`
@ -156,16 +156,14 @@ export const ObjectFilterDropdownFilterSelect = ({
visibleColumnsFieldMetadataItems.length > 0 &&
hiddenColumnsFieldMetadataItems.length > 0;
const { currentViewId, currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const isAdvancedFiltersEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAdvancedFiltersEnabled,
);
const shouldShowAdvancedFilterButton =
isDefined(currentViewId) &&
isDefined(currentViewWithCombinedFiltersAndSorts?.objectMetadataId) &&
isDefined(currentView?.objectMetadataId) &&
isAdvancedFilterButtonVisible &&
isAdvancedFiltersEnabled;

View File

@ -109,7 +109,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
displayValue,
type: getFilterTypeFromFieldType(fieldMetadataItem.type),
label: fieldMetadataItem.label,
viewFilterGroupId: advancedFilterViewFilterGroupId,
recordFilterGroupId: advancedFilterViewFilterGroupId,
subFieldName: subFieldName,
});
}

View File

@ -65,7 +65,7 @@ export const ObjectFilterDropdownNumberInput = () => {
fieldMetadataItemUsedInDropdown.type,
),
label: fieldMetadataItemUsedInDropdown.label,
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
});
}}
/>

View File

@ -146,7 +146,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
displayValue: filterDisplayValue,
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
value: newFilterValue,
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
});
}
resetSelectedItem();

View File

@ -65,7 +65,7 @@ export const ObjectFilterDropdownRatingInput = () => {
value: convertFieldRatingValueToNumber(newValue),
operand: selectedOperandInDropdown,
displayValue: convertFieldRatingValueToNumber(newValue),
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
type: getFilterTypeFromFieldType(
fieldMetadataItemUsedInDropdown.type,
),

View File

@ -214,7 +214,7 @@ export const ObjectFilterDropdownRecordSelect = ({
displayValue: filterDisplayValue,
fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id,
value: newFilterValue,
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
});
}
};

View File

@ -136,7 +136,7 @@ export const ObjectFilterDropdownSourceSelect = ({
displayValue: filterDisplayValue,
fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id,
value: newFilterValue,
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
});
}
};

View File

@ -64,7 +64,7 @@ export const ObjectFilterDropdownTextSearchInput = () => {
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
type: getFilterTypeFromFieldType(
fieldMetadataItemUsedInDropdown.type,
),

View File

@ -92,7 +92,7 @@ export const useSelectFilterUsedInDropdown = (componentInstanceId?: string) => {
displayValue,
operand: firstOperand,
value,
viewFilterGroupId: advancedFilterViewFilterGroupId,
recordFilterGroupId: advancedFilterViewFilterGroupId,
type: filterType,
label: fieldMetadataItem.label,
});

View File

@ -18,7 +18,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
@ -36,8 +36,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
} = useOptionsDropdown();
const { getIcon } = useIcons();
const { currentViewWithCombinedFiltersAndSorts: currentView } =
useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const CurrentViewIcon = currentView?.icon ? getIcon(currentView.icon) : null;

View File

@ -25,7 +25,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useLingui } from '@lingui/react/macro';
export const ObjectOptionsDropdownRecordGroupsContent = () => {
@ -38,8 +38,7 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
resetContent,
} = useOptionsDropdown();
const { currentViewWithCombinedFiltersAndSorts: currentView } =
useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordGroupFieldMetadataComponentState,

View File

@ -20,6 +20,7 @@ import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/s
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@ -31,8 +32,8 @@ import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/get
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewType } from '@/views/types/ViewType';
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
@ -104,6 +105,10 @@ export const RecordBoard = () => {
const { resetRecordSelection, setRecordAsSelected } =
useRecordBoardSelection(recordBoardId);
const currentRecordSorts = useRecoilComponentValueV2(
currentRecordSortsComponentState,
);
useListenClickOutside({
excludeClassNames: [
'bottom-bar',
@ -144,9 +149,6 @@ export const RecordBoard = () => {
ActionBarHotkeyScope.ActionBar,
);
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(recordBoardId);
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
);
@ -156,10 +158,7 @@ export const RecordBoard = () => {
(result) => {
if (!result.destination) return;
const viewSorts =
currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
if (viewSorts.length > 0) {
if (currentRecordSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
return;
}
@ -219,7 +218,7 @@ export const RecordBoard = () => {
selectFieldMetadataItem,
updateOneRecord,
setIsRemoveSortingModalOpen,
currentViewWithCombinedFiltersAndSorts,
currentRecordSorts,
],
);

View File

@ -7,10 +7,10 @@ export type RecordFilter = {
value: string;
displayValue: string;
type: FilterableFieldType;
viewFilterGroupId?: string;
recordFilterGroupId?: string;
displayAvatarUrl?: string;
operand: ViewFilterOperand;
positionInViewFilterGroup?: number | null;
positionInRecordFilterGroup?: number | null;
label: string;
subFieldName?: string | null | undefined;
};

View File

@ -824,7 +824,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
}
const groupFilters = filters.filter(
(filter) => filter.viewFilterGroupId === currentViewFilterGroupId,
(filter) => filter.recordFilterGroupId === currentViewFilterGroupId,
);
const groupRecordGqlOperationFilters = groupFilters
@ -886,7 +886,7 @@ export const computeViewRecordGqlOperationFilter = (
viewFilterGroups: ViewFilterGroup[],
): RecordGqlOperationFilter => {
const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = filters
.filter((filter) => !filter.viewFilterGroupId)
.filter((filter) => !filter.recordFilterGroupId)
.map((regularFilter) =>
computeFilterRecordGqlOperationFilter({
filterValueDependencies,

View File

@ -5,12 +5,13 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared';
export const RecordTableBodyDragDropContextProvider = ({
@ -18,7 +19,7 @@ export const RecordTableBodyDragDropContextProvider = ({
}: {
children: ReactNode;
}) => {
const { objectNameSingular, recordTableId } = useRecordTableContextOrThrow();
const { objectNameSingular } = useRecordTableContextOrThrow();
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
objectNameSingular,
@ -28,10 +29,9 @@ export const RecordTableBodyDragDropContextProvider = ({
recordIndexAllRecordIdsComponentSelector,
);
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(recordTableId);
const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
const currentRecordSorts = useRecoilComponentValueV2(
currentRecordSortsComponentState,
);
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
@ -40,7 +40,7 @@ export const RecordTableBodyDragDropContextProvider = ({
const handleDragEnd = useRecoilCallback(
({ snapshot }) =>
(result: DropResult) => {
if (viewSorts.length > 0) {
if (currentRecordSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
return;
}
@ -103,7 +103,7 @@ export const RecordTableBodyDragDropContextProvider = ({
recordIndexAllRecordIdsSelector,
setIsRemoveSortingModalOpen,
updateOneRow,
viewSorts.length,
currentRecordSorts,
],
);

View File

@ -3,28 +3,28 @@ 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 { useRemoveRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup';
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 { 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 { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from 'twenty-shared';
export const AdvancedFilterDropdownButton = () => {
const { deleteCombinedViewFilterGroup } = useDeleteCombinedViewFilterGroup();
const { removeRecordFilterGroup } = useRemoveRecordFilterGroup();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const advancedRecordFilterIds = currentRecordFilters
.filter((recordFilter) => isDefined(recordFilter.viewFilterGroupId))
.filter((recordFilter) => isDefined(recordFilter.recordFilterGroupId))
.map((recordFilter) => recordFilter.id);
const { removeRecordFilter } = useRemoveRecordFilter();
@ -39,12 +39,11 @@ export const AdvancedFilterDropdownButton = () => {
}
const viewFilterGroupIds =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups?.map(
(viewFilter) => viewFilter.id,
currentRecordFilterGroups?.map(
(recordFilterGroup) => recordFilterGroup.id,
) ?? [];
for (const viewFilterGroupId of viewFilterGroupIds) {
await deleteCombinedViewFilterGroup(viewFilterGroupId);
removeRecordFilterGroup(viewFilterGroupId);
}
@ -55,16 +54,14 @@ export const AdvancedFilterDropdownButton = () => {
advancedRecordFilterIds,
removeRecordFilterGroup,
removeRecordFilter,
deleteCombinedViewFilterGroup,
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups,
currentRecordFilterGroups,
]);
const outermostViewFilterGroupId =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.find(
(viewFilterGroup) => !viewFilterGroup.parentViewFilterGroupId,
)?.id;
const outermostRecordFilterGroupId = currentRecordFilterGroups.find(
(recordFilterGroup) => !recordFilterGroup.parentRecordFilterGroupId,
)?.id;
if (!outermostViewFilterGroupId) {
if (!outermostRecordFilterGroupId) {
return null;
}
@ -79,7 +76,7 @@ export const AdvancedFilterDropdownButton = () => {
}
dropdownComponents={
<AdvancedFilterRootLevelViewFilterGroup
rootLevelViewFilterGroupId={outermostViewFilterGroupId}
rootLevelViewFilterGroupId={outermostRecordFilterGroupId}
/>
}
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}

View File

@ -2,14 +2,11 @@ import { useEffect } from 'react';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
export const QueryParamsFiltersEffect = () => {
const { hasFiltersQueryParams, getFiltersFromQueryParams } =
useViewFromQueryParams();
const { resetUnsavedViewStates } = useResetUnsavedViewStates();
const { applyViewFiltersToCurrentRecordFilters } =
useApplyViewFiltersToCurrentRecordFilters();
@ -27,7 +24,6 @@ export const QueryParamsFiltersEffect = () => {
applyViewFiltersToCurrentRecordFilters,
getFiltersFromQueryParams,
hasFiltersQueryParams,
resetUnsavedViewStates,
]);
return <></>;

View File

@ -19,7 +19,7 @@ import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryP
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
@ -56,7 +56,7 @@ export const UpdateViewButtonGroup = ({
const { openDropdown: openViewPickerDropdown } = useDropdown(
VIEW_PICKER_DROPDOWN_ID,
);
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
viewPickerReferenceViewIdComponentState,
@ -109,7 +109,7 @@ export const UpdateViewButtonGroup = ({
return (
<StyledContainer>
{currentViewWithCombinedFiltersAndSorts?.key !== 'INDEX' ? (
{currentView?.key !== 'INDEX' ? (
<ButtonGroup size="small" accent="blue">
<Button title="Update view" onClick={handleUpdateViewClick} />
<Dropdown

View File

@ -53,7 +53,7 @@ export const ViewBar = ({
<ViewBarRecordSortEffect />
<ViewBarFilterEffect filterDropdownId={filterDropdownId} />
<QueryParamsFiltersEffect />
<ViewBarPageTitle viewBarId={viewBarId} />
<ViewBarPageTitle />
<TopBar
className={className}
leftComponent={

View File

@ -21,8 +21,6 @@ import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/
import { useApplyCurrentViewSortsToCurrentRecordSorts } from '@/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts';
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
@ -115,10 +113,6 @@ export const ViewBarDetails = ({
viewBarId,
objectNamePlural,
}: ViewBarDetailsProps) => {
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const viewId = currentViewWithCombinedFiltersAndSorts?.id;
const isViewBarExpanded = useRecoilComponentValueV2(
isViewBarExpandedComponentState,
);
@ -144,7 +138,6 @@ export const ViewBarDetails = ({
objectNameSingular: objectNameSingular,
viewBarId: viewBarId,
});
const { resetUnsavedViewStates } = useResetUnsavedViewStates();
const { viewFilterGroupsAreDifferentFromRecordFilterGroups } =
useAreViewFilterGroupsDifferentFromRecordFilterGroups();
@ -170,7 +163,7 @@ export const ViewBarDetails = ({
const recordFilters = useMemo(() => {
return currentRecordFilters.filter(
(recordFilter) =>
!recordFilter.viewFilterGroupId &&
!recordFilter.recordFilterGroupId &&
!checkIsSoftDeleteFilter(recordFilter),
);
}, [currentRecordFilters, checkIsSoftDeleteFilter]);
@ -185,13 +178,10 @@ export const ViewBarDetails = ({
useApplyCurrentViewSortsToCurrentRecordSorts();
const handleCancelClick = () => {
if (isDefined(viewId)) {
resetUnsavedViewStates(viewId);
applyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
applyCurrentViewFiltersToCurrentRecordFilters();
applyCurrentViewSortsToCurrentRecordSorts();
toggleSoftDeleteFilterState(false);
}
applyCurrentViewFilterGroupsToCurrentRecordFilterGroups();
applyCurrentViewFiltersToCurrentRecordFilters();
applyCurrentViewSortsToCurrentRecordSorts();
toggleSoftDeleteFilterState(false);
};
const shouldExpandViewBar =
@ -207,9 +197,8 @@ export const ViewBarDetails = ({
return null;
}
const showAdvancedFilterDropdownButton =
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups &&
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.length > 0;
const shouldShowAdvancedFilterDropdownButton =
currentRecordFilterGroups.length > 0;
return (
<StyledBar>
@ -239,7 +228,9 @@ export const ViewBarDetails = ({
<StyledSeperator />
</StyledSeperatorContainer>
)}
{showAdvancedFilterDropdownButton && <AdvancedFilterDropdownButton />}
{shouldShowAdvancedFilterDropdownButton && (
<AdvancedFilterDropdownButton />
)}
{recordFilters.map((recordFilter) => (
<ObjectFilterDropdownComponentInstanceContext.Provider
key={recordFilter.id}

View File

@ -1,18 +1,13 @@
import { useParams } from 'react-router-dom';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { capitalize } from 'twenty-shared';
export type ViewBarPageTitleProps = {
viewBarId: string;
};
export const ViewBarPageTitle = ({ viewBarId }: ViewBarPageTitleProps) => {
export const ViewBarPageTitle = () => {
const { objectNamePlural } = useParams();
const { currentViewWithCombinedFiltersAndSorts: currentView } =
useGetCurrentView(viewBarId);
const { currentView } = useGetCurrentViewOnly();
if (!objectNamePlural) {
return;

View File

@ -104,8 +104,8 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
value: mockViewFilter.value,
displayValue: mockViewFilter.displayValue,
operand: mockViewFilter.operand,
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
recordFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInRecordFilterGroup: mockViewFilter.positionInViewFilterGroup,
label: mockFieldMetadataItem.label,
type: getFilterTypeFromFieldType(mockFieldMetadataItem.type),
} satisfies RecordFilter,

View File

@ -70,8 +70,8 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => {
value: mockViewFilter.value,
displayValue: mockViewFilter.displayValue,
operand: mockViewFilter.operand,
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
recordFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInRecordFilterGroup: mockViewFilter.positionInViewFilterGroup,
label: mockFieldMetadataItem.label,
type: getFilterTypeFromFieldType(mockFieldMetadataItem.type),
} satisfies RecordFilter,

View File

@ -1,15 +1,10 @@
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { useSetViewInUrl } from '@/views/hooks/useSetViewInUrl';
export const useChangeView = (viewBarComponentId?: string) => {
const { resetUnsavedViewStates } =
useResetUnsavedViewStates(viewBarComponentId);
export const useChangeView = () => {
const { setViewInUrl } = useSetViewInUrl();
const changeView = async (viewId: string) => {
setViewInUrl(viewId);
resetUnsavedViewStates(viewId);
};
return { changeView };

View File

@ -2,6 +2,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
@ -14,12 +15,12 @@ import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePers
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
import { useGetViewFilterGroupsCombined } from '@/views/hooks/useGetCombinedViewFilterGroups';
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { GraphQLView } from '@/views/types/GraphQLView';
import { View } from '@/views/types/View';
import { ViewGroup } from '@/views/types/ViewGroup';
import { ViewType } from '@/views/types/ViewType';
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
import { useRecoilCallback } from 'recoil';
@ -39,9 +40,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { createViewFieldRecords } = usePersistViewFieldRecords();
const { getViewFilterGroupsCombined } =
useGetViewFilterGroupsCombined(viewBarComponentId);
const { createViewSortRecords } = usePersistViewSortRecords();
const { createViewGroupRecords } = usePersistViewGroupRecords();
@ -57,6 +55,10 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
fetchPolicy: 'network-only',
});
const currentRecordFilterGroups = useRecoilComponentValueV2(
currentRecordFilterGroupsComponentState,
);
const currentRecordSorts = useRecoilComponentValueV2(
currentRecordSortsComponentState,
);
@ -165,8 +167,12 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
}
if (shouldCopyFiltersAndSortsAndAggregate === true) {
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
sourceView.id,
const viewFilterGroupsToCreate = currentRecordFilterGroups.map(
(recordFilterGroup) =>
mapRecordFilterGroupToViewFilterGroup({
recordFilterGroup,
view: newView,
}),
);
const viewSortsToCreate = currentRecordSorts.map(
@ -178,10 +184,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
await createViewSortRecords(viewSortsToCreate, newView);
await createViewFilterRecords(viewFiltersToCreate, newView);
await createViewFilterGroupRecords(
sourceViewCombinedFilterGroups,
newView,
);
await createViewFilterGroupRecords(viewFilterGroupsToCreate, newView);
}
await findManyRecords();
@ -193,13 +196,13 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
createViewFieldRecords,
findManyRecords,
objectMetadataItem.fields,
getViewFilterGroupsCombined,
createViewGroupRecords,
createViewSortRecords,
createViewFilterRecords,
createViewFilterGroupRecords,
currentRecordFilters,
currentRecordSorts,
currentRecordFilterGroups,
],
);

View File

@ -1,68 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
import { isDefined } from 'twenty-shared';
export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => {
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
viewBarComponentId,
);
const unsavedToDeleteViewFilterGroupIdsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
viewBarComponentId,
);
const getViewFilterGroupsCombined = useRecoilCallback(
({ snapshot }) =>
(viewId: string) => {
const view = snapshot
.getLoadable(
prefetchViewFromViewIdFamilySelector({
viewId,
}),
)
.getValue();
if (!isDefined(view)) {
throw new Error(
`Cannot get view with id ${viewId}, because it cannot be found in client cache data.`,
);
}
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
snapshot,
unsavedToUpsertViewFilterGroupsCallbackState({ viewId: view.id }),
);
const unsavedToDeleteViewFilterGroupIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterGroupIdsCallbackState({ viewId: view.id }),
);
const combinedViewFilterGroups = getCombinedViewFilterGroups(
view.viewFilterGroups ?? [],
unsavedToUpsertViewFilterGroups,
unsavedToDeleteViewFilterGroupIds,
);
return combinedViewFilterGroups;
},
[
unsavedToDeleteViewFilterGroupIdsCallbackState,
unsavedToUpsertViewFilterGroupsCallbackState,
],
);
return {
getViewFilterGroupsCombined,
};
};

View File

@ -6,14 +6,10 @@ import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefe
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { prefetchViewsFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchViewsFromObjectMetadataItemFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { isCurrentViewKeyIndexComponentState } from '@/views/states/isCurrentViewIndexComponentState';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
@ -52,7 +48,6 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
instanceId,
);
const viewId = currentViewId ?? indexView?.id;
const currentView = currentViewFromViewId ?? indexView;
useEffect(() => {
@ -65,18 +60,6 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
}),
);
const unsavedToUpsertViewFilterGroups = useRecoilComponentFamilyValueV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
{ viewId },
instanceId,
);
const unsavedToDeleteViewFilterGroupIds = useRecoilComponentFamilyValueV2(
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
{ viewId },
instanceId,
);
if (!isDefined(currentView)) {
return {
instanceId,
@ -86,18 +69,8 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
};
}
const currentViewWithCombinedFiltersAndSorts = {
...currentView,
viewFilterGroups: getCombinedViewFilterGroups(
currentView.viewFilterGroups ?? [],
unsavedToUpsertViewFilterGroups,
unsavedToDeleteViewFilterGroupIds,
),
};
return {
instanceId,
currentViewWithCombinedFiltersAndSorts,
viewsOnCurrentObject: viewsOnCurrentObject ?? [],
currentViewId,
};

View File

@ -1,34 +0,0 @@
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { useRecoilCallback } from 'recoil';
export const useResetUnsavedViewStates = (viewBarInstanceId?: string) => {
const unsavedToDeleteViewFilterGroupIdsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
viewBarInstanceId,
);
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
viewBarInstanceId,
);
const resetUnsavedViewStates = useRecoilCallback(
({ set }) =>
(viewId: string) => {
set(unsavedToDeleteViewFilterGroupIdsCallbackState({ viewId }), []);
set(unsavedToUpsertViewFilterGroupsCallbackState({ viewId }), []);
},
[
unsavedToUpsertViewFilterGroupsCallbackState,
unsavedToDeleteViewFilterGroupIdsCallbackState,
],
);
return {
resetUnsavedViewStates,
};
};

View File

@ -1,131 +1,21 @@
import { useRecoilCallback } from 'recoil';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { useSaveRecordFilterGroupsToViewFilterGroups } from '@/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups';
import { useSaveRecordFiltersToViewFilters } from '@/views/hooks/useSaveRecordFiltersToViewFilters';
import { useSaveRecordSortsToViewSorts } from '@/views/hooks/useSaveRecordSortsToViewSorts';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { isDefined } from 'twenty-shared';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useSaveCurrentViewFiltersAndSorts = (
viewBarComponentId?: string,
) => {
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
contextStoreCurrentViewIdComponentState,
viewBarComponentId,
);
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
viewBarComponentId,
);
const unsavedToDeleteViewFilterGroupIdsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
viewBarComponentId,
);
const {
createViewFilterGroupRecords,
deleteViewFilterGroupRecords,
updateViewFilterGroupRecords,
} = usePersistViewFilterGroupRecords();
const { resetUnsavedViewStates } =
useResetUnsavedViewStates(viewBarComponentId);
const saveViewFilterGroups = useRecoilCallback(
({ snapshot }) =>
async (viewId: string) => {
const unsavedToDeleteViewFilterGroupIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterGroupIdsCallbackState({ viewId }),
);
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
snapshot,
unsavedToUpsertViewFilterGroupsCallbackState({ viewId }),
);
const view = await getViewFromPrefetchState(viewId);
if (isUndefinedOrNull(view)) {
return;
}
const viewFilterGroupsToCreate = unsavedToUpsertViewFilterGroups.filter(
(viewFilterGroup) =>
!view.viewFilterGroups?.some(
(viewFilterGroupToFilter) =>
viewFilterGroupToFilter.id === viewFilterGroup.id,
),
);
const viewFilterGroupsToUpdate = unsavedToUpsertViewFilterGroups.filter(
(viewFilterGroup) =>
view.viewFilterGroups?.some(
(viewFilterGroupToFilter) =>
viewFilterGroupToFilter.id === viewFilterGroup.id,
),
);
await createViewFilterGroupRecords(viewFilterGroupsToCreate, view);
await updateViewFilterGroupRecords(viewFilterGroupsToUpdate);
await deleteViewFilterGroupRecords(unsavedToDeleteViewFilterGroupIds);
},
[
getViewFromPrefetchState,
createViewFilterGroupRecords,
deleteViewFilterGroupRecords,
unsavedToDeleteViewFilterGroupIdsCallbackState,
unsavedToUpsertViewFilterGroupsCallbackState,
updateViewFilterGroupRecords,
],
);
export const useSaveCurrentViewFiltersAndSorts = () => {
const { saveRecordFilterGroupsToViewFilterGroups } =
useSaveRecordFilterGroupsToViewFilterGroups();
const { saveRecordFiltersToViewFilters } =
useSaveRecordFiltersToViewFilters();
const { saveRecordSortsToViewSorts } = useSaveRecordSortsToViewSorts();
const saveCurrentViewFilterAndSorts = useRecoilCallback(
({ snapshot }) =>
async (viewIdFromProps?: string) => {
const currentViewId = snapshot
.getLoadable(currentViewIdCallbackState)
.getValue();
if (!isDefined(currentViewId)) {
return;
}
const viewId = viewIdFromProps ?? currentViewId;
await saveViewFilterGroups(viewId);
await saveRecordSortsToViewSorts();
await saveRecordFiltersToViewFilters();
resetUnsavedViewStates(viewId);
},
[
currentViewIdCallbackState,
resetUnsavedViewStates,
saveViewFilterGroups,
saveRecordFiltersToViewFilters,
saveRecordSortsToViewSorts,
],
);
const saveCurrentViewFilterAndSorts = async () => {
await saveRecordSortsToViewSorts();
await saveRecordFiltersToViewFilters();
await saveRecordFilterGroupsToViewFilterGroups();
};
return {
saveCurrentViewFilterAndSorts,

View File

@ -0,0 +1,85 @@
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate';
import { getViewFilterGroupsToDelete } from '@/views/utils/getViewFilterGroupsToDelete';
import { getViewFilterGroupsToUpdate } from '@/views/utils/getViewFilterGroupsToUpdate';
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared';
export const useSaveRecordFilterGroupsToViewFilterGroups = () => {
const {
createViewFilterGroupRecords,
updateViewFilterGroupRecords,
deleteViewFilterGroupRecords,
} = usePersistViewFilterGroupRecords();
const { currentView } = useGetCurrentViewOnly();
const currentRecordFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(currentRecordFilterGroupsComponentState);
const saveRecordFilterGroupsToViewFilterGroups = useRecoilCallback(
({ snapshot }) =>
async () => {
if (!isDefined(currentView)) {
return;
}
const currentViewFilterGroups = currentView?.viewFilterGroups ?? [];
const currentRecordFilterGroups = getSnapshotValue(
snapshot,
currentRecordFilterGroupsCallbackState,
);
const newViewFilterGroups = currentRecordFilterGroups.map(
(recordFilterGroup) =>
mapRecordFilterGroupToViewFilterGroup({
recordFilterGroup,
view: currentView,
}),
);
const viewFilterGroupsToCreate = getViewFilterGroupsToCreate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToDelete = getViewFilterGroupsToDelete(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterGroupsToUpdate = getViewFilterGroupsToUpdate(
currentViewFilterGroups,
newViewFilterGroups,
);
const viewFilterIdsToDelete = viewFilterGroupsToDelete.map(
(viewFilter) => viewFilter.id,
);
await createViewFilterGroupRecords(
viewFilterGroupsToCreate,
currentView,
);
await updateViewFilterGroupRecords(viewFilterGroupsToUpdate);
await deleteViewFilterGroupRecords(viewFilterIdsToDelete);
},
[
createViewFilterGroupRecords,
deleteViewFilterGroupRecords,
updateViewFilterGroupRecords,
currentRecordFilterGroupsCallbackState,
currentView,
],
);
return {
saveRecordFilterGroupsToViewFilterGroups,
};
};

View File

@ -1,9 +0,0 @@
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const unsavedToDeleteViewFilterGroupIdsComponentFamilyState =
createComponentFamilyStateV2<string[], { viewId?: string }>({
key: 'unsavedToDeleteViewFilterGroupIdsComponentFamilyState',
defaultValue: [],
componentInstanceContext: ViewComponentInstanceContext,
});

View File

@ -1,10 +0,0 @@
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
export const unsavedToUpsertViewFilterGroupsComponentFamilyState =
createComponentFamilyStateV2<ViewFilterGroup[], { viewId?: string }>({
key: 'unsavedToUpsertViewFilterGroupsComponentFamilyState',
defaultValue: [],
componentInstanceContext: ViewComponentInstanceContext,
});

View File

@ -55,6 +55,7 @@ describe('mapViewFiltersToFilters', () => {
operand: ViewFilterOperand.Is,
},
];
const expectedFilters: RecordFilter[] = [
{
id: 'id',
@ -64,8 +65,8 @@ describe('mapViewFiltersToFilters', () => {
operand: ViewFilterOperand.Is,
label: baseFieldMetadataItem.label,
type: FieldMetadataType.FULL_NAME,
positionInViewFilterGroup: undefined,
viewFilterGroupId: undefined,
positionInRecordFilterGroup: undefined,
recordFilterGroupId: undefined,
},
];
expect(

View File

@ -6,6 +6,12 @@ export const mapRecordFilterToViewFilter = (
): ViewFilter => {
return {
__typename: 'ViewFilter',
...recordFilter,
} satisfies ViewFilter;
displayValue: recordFilter.displayValue,
fieldMetadataId: recordFilter.fieldMetadataId,
id: recordFilter.id,
operand: recordFilter.operand,
value: recordFilter.value,
positionInViewFilterGroup: recordFilter.positionInRecordFilterGroup,
viewFilterGroupId: recordFilter.recordFilterGroupId,
};
};

View File

@ -32,11 +32,11 @@ export const mapViewFiltersToFilters = (
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
viewFilterGroupId: viewFilter.viewFilterGroupId,
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
recordFilterGroupId: viewFilter.viewFilterGroupId,
positionInRecordFilterGroup: viewFilter.positionInViewFilterGroup,
label: availableFieldMetadataItem.label,
type: filterType,
};
} satisfies RecordFilter;
})
.filter(isDefined);
};

View File

@ -1,18 +1,27 @@
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { isDefined } from 'twenty-shared';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
export const shouldReplaceFilter = (
oldFilter: Pick<RecordFilter, 'id' | 'fieldMetadataId' | 'viewFilterGroupId'>,
newFilter: Pick<RecordFilter, 'id' | 'fieldMetadataId' | 'viewFilterGroupId'>,
oldFilter: Pick<
RecordFilter,
'id' | 'fieldMetadataId' | 'recordFilterGroupId'
>,
newFilter: Pick<
RecordFilter,
'id' | 'fieldMetadataId' | 'recordFilterGroupId'
>,
) => {
const isNewFilterAdvancedFilter = isDefined(newFilter.viewFilterGroupId);
const isNewFilterAdvancedFilter = isDefined(newFilter.recordFilterGroupId);
if (isNewFilterAdvancedFilter) {
return newFilter.id === oldFilter.id;
} else {
return (
newFilter.fieldMetadataId === oldFilter.fieldMetadataId &&
!oldFilter.viewFilterGroupId
compareStrictlyExceptForNullAndUndefined(
newFilter.fieldMetadataId,
oldFilter.fieldMetadataId,
) && !isDefined(oldFilter.recordFilterGroupId)
);
}
};

View File

@ -12,7 +12,7 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
import { ViewPickerContentCreateMode } from '@/views/view-picker/components/ViewPickerContentCreateMode';
import { ViewPickerContentEditMode } from '@/views/view-picker/components/ViewPickerContentEditMode';
@ -51,7 +51,7 @@ const StyledViewName = styled.span`
export const ViewPickerDropdown = () => {
const theme = useTheme();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
@ -66,7 +66,7 @@ export const ViewPickerDropdown = () => {
const { viewPickerMode, setViewPickerMode } = useViewPickerMode();
const { getIcon } = useIcons();
const CurrentViewIcon = getIcon(currentViewWithCombinedFiltersAndSorts?.icon);
const CurrentViewIcon = getIcon(currentView?.icon);
const handleClickOutside = async () => {
if (isViewsListDropdownOpen && viewPickerMode === 'edit') {
@ -85,14 +85,12 @@ export const ViewPickerDropdown = () => {
onClickOutside={handleClickOutside}
clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isViewsListDropdownOpen}>
{currentViewWithCombinedFiltersAndSorts && CurrentViewIcon ? (
{currentView && CurrentViewIcon ? (
<CurrentViewIcon size={theme.icon.size.md} />
) : (
<IconList size={theme.icon.size.md} />
)}
<StyledViewName>
{currentViewWithCombinedFiltersAndSorts?.name ?? 'All'}
</StyledViewName>
<StyledViewName>{currentView?.name ?? 'All'}</StyledViewName>
<StyledDropdownLabelAdornments>
{isDefined(entityCount) && <>· {entityCount} </>}
<IconChevronDown size={theme.icon.size.sm} />

View File

@ -10,13 +10,14 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useChangeView } from '@/views/hooks/useChangeView';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useUpdateView } from '@/views/hooks/useUpdateView';
import { ViewPickerOptionDropdown } from '@/views/view-picker/components/ViewPickerOptionDropdown';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { useLingui } from '@lingui/react/macro';
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
font-weight: ${({ theme }) => theme.font.weight.regular};
@ -24,8 +25,9 @@ const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
export const ViewPickerListContent = () => {
const { t } = useLingui();
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
useGetCurrentView();
const { viewsOnCurrentObject } = useGetCurrentView();
const { currentView } = useGetCurrentViewOnly();
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
viewPickerReferenceViewIdComponentState,
@ -41,8 +43,8 @@ export const ViewPickerListContent = () => {
};
const handleAddViewButtonClick = () => {
if (isDefined(currentViewWithCombinedFiltersAndSorts?.id)) {
setViewPickerReferenceViewId(currentViewWithCombinedFiltersAndSorts.id);
if (isDefined(currentView?.id)) {
setViewPickerReferenceViewId(currentView.id);
setViewPickerMode('create-empty');
}
};

View File

@ -32,7 +32,7 @@ export const useDeleteViewFromCurrentState = (viewBarInstanceId?: string) => {
viewBarInstanceId,
);
const { changeView } = useChangeView(viewBarInstanceId);
const { changeView } = useChangeView();
const { deleteView } = useDeleteView();

View File

@ -10,7 +10,7 @@ import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/sta
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { useRecoilCallback } from 'recoil';
export const useUpdateViewFromCurrentState = (viewBarInstanceId?: string) => {
export const useUpdateViewFromCurrentState = () => {
const { closeAndResetViewPicker } = useCloseAndResetViewPicker();
const viewPickerInputNameCallbackState = useRecoilComponentCallbackStateV2(
@ -33,7 +33,7 @@ export const useUpdateViewFromCurrentState = (viewBarInstanceId?: string) => {
useRecoilComponentCallbackStateV2(viewPickerReferenceViewIdComponentState);
const { updateView } = useUpdateView();
const { changeView } = useChangeView(viewBarInstanceId);
const { changeView } = useChangeView();
const updateViewFromCurrentState = useRecoilCallback(
({ set, snapshot }) =>