Feat: Advanced filter (#7700)
Design:  Not ready to be merged yet! --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -0,0 +1,27 @@
|
||||
import { IconFilterCog } from 'twenty-ui';
|
||||
|
||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||
import { plural } from 'pluralize';
|
||||
|
||||
type AdvancedFilterChipProps = {
|
||||
onRemove: () => void;
|
||||
advancedFilterCount?: number;
|
||||
};
|
||||
|
||||
export const AdvancedFilterChip = ({
|
||||
onRemove,
|
||||
advancedFilterCount,
|
||||
}: AdvancedFilterChipProps) => {
|
||||
const labelText = 'advanced rule';
|
||||
const chipLabel = `${advancedFilterCount ?? 0} ${advancedFilterCount === 1 ? labelText : plural(labelText)}`;
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
testId={ADVANCED_FILTER_DROPDOWN_ID}
|
||||
labelKey={chipLabel}
|
||||
labelValue=""
|
||||
Icon={IconFilterCog}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,83 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
|
||||
import { AdvancedFilterRootLevelViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup';
|
||||
import { useDeleteCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useDeleteCombinedViewFilterGroup';
|
||||
import { AdvancedFilterChip } from '@/views/components/AdvancedFilterChip';
|
||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const AdvancedFilterDropdownButton = () => {
|
||||
const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters();
|
||||
const { deleteCombinedViewFilterGroup } = useDeleteCombinedViewFilterGroup();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const advancedViewFilterIds =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters
|
||||
.filter((viewFilter) => isDefined(viewFilter.viewFilterGroupId))
|
||||
.map((viewFilter) => viewFilter.id);
|
||||
|
||||
const handleDropdownClickOutside = useCallback(() => {}, []);
|
||||
|
||||
const handleDropdownClose = () => {};
|
||||
|
||||
const removeAdvancedFilter = useCallback(async () => {
|
||||
if (!advancedViewFilterIds) {
|
||||
throw new Error('No advanced view filters to remove');
|
||||
}
|
||||
|
||||
const viewFilterGroupIds =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups?.map(
|
||||
(viewFilter) => viewFilter.id,
|
||||
) ?? [];
|
||||
|
||||
for (const viewFilterGroupId of viewFilterGroupIds) {
|
||||
await deleteCombinedViewFilterGroup(viewFilterGroupId);
|
||||
}
|
||||
|
||||
for (const viewFilterId of advancedViewFilterIds) {
|
||||
await deleteCombinedViewFilter(viewFilterId);
|
||||
}
|
||||
}, [
|
||||
advancedViewFilterIds,
|
||||
deleteCombinedViewFilter,
|
||||
deleteCombinedViewFilterGroup,
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups,
|
||||
]);
|
||||
|
||||
const outermostViewFilterGroupId =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.find(
|
||||
(viewFilterGroup) => !viewFilterGroup.parentViewFilterGroupId,
|
||||
)?.id;
|
||||
|
||||
if (!outermostViewFilterGroupId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={ADVANCED_FILTER_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<AdvancedFilterChip
|
||||
onRemove={removeAdvancedFilter}
|
||||
advancedFilterCount={advancedViewFilterIds?.length}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<AdvancedFilterRootLevelViewFilterGroup
|
||||
rootLevelViewFilterGroupId={outermostViewFilterGroupId}
|
||||
/>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
|
||||
dropdownOffset={{ y: 8, x: 0 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownMenuWidth={800}
|
||||
onClickOutside={handleDropdownClickOutside}
|
||||
onClose={handleDropdownClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { MultipleFiltersDropdownContent } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { FilterOperand } from '@/object-record/object-filter-dropdown/types/FilterOperand';
|
||||
@ -11,6 +10,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { EditableFilterChip } from '@/views/components/EditableFilterChip';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -98,7 +98,7 @@ export const EditableFilterDropdownButton = ({
|
||||
<EditableFilterChip viewFilter={viewFilter} onRemove={handleRemove} />
|
||||
}
|
||||
dropdownComponents={
|
||||
<MultipleFiltersDropdownContent
|
||||
<ObjectFilterOperandSelectAndInput
|
||||
filterDropdownId={viewFilterDropdownId}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton';
|
||||
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
|
||||
import { EditableSortChip } from '@/views/components/EditableSortChip';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
@ -137,11 +138,16 @@ export const ViewBarDetails = ({
|
||||
|
||||
const otherViewFilters =
|
||||
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
|
||||
(viewFilter) => viewFilter.variant && viewFilter.variant !== 'default',
|
||||
(viewFilter) =>
|
||||
viewFilter.variant &&
|
||||
viewFilter.variant !== 'default' &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
const defaultViewFilters =
|
||||
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
|
||||
(viewFilter) => !viewFilter.variant || viewFilter.variant === 'default',
|
||||
(viewFilter) =>
|
||||
(!viewFilter.variant || viewFilter.variant === 'default') &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
|
||||
return {
|
||||
@ -166,6 +172,10 @@ export const ViewBarDetails = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const showAdvancedFilterDropdownButton =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups &&
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilterGroups.length > 0;
|
||||
|
||||
return (
|
||||
<StyledBar>
|
||||
<StyledFilterContainer>
|
||||
@ -199,6 +209,7 @@ export const ViewBarDetails = ({
|
||||
<StyledSeperator />
|
||||
</StyledSeperatorContainer>
|
||||
)}
|
||||
{showAdvancedFilterDropdownButton && <AdvancedFilterDropdownButton />}
|
||||
{mapViewFiltersToFilters(
|
||||
defaultViewFilters,
|
||||
availableFilterDefinitions,
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const ADVANCED_FILTER_DROPDOWN_ID = 'advanced-filter';
|
||||
@ -0,0 +1,214 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const usePersistViewFilterGroupRecords = () => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
|
||||
});
|
||||
|
||||
const getRecordFromCache = useGetRecordFromCache({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
|
||||
});
|
||||
|
||||
const { destroyOneRecordMutation } = useDestroyOneRecordMutation({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
|
||||
});
|
||||
|
||||
const { createOneRecordMutation } = useCreateOneRecordMutation({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
|
||||
});
|
||||
|
||||
const { updateOneRecordMutation } = useUpdateOneRecordMutation({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewFilterGroupRecord = useCallback(
|
||||
async (viewFilterGroup: ViewFilterGroup, view: GraphQLView) => {
|
||||
const result = await apolloClient.mutate<{
|
||||
createViewFilterGroup: ViewFilterGroup;
|
||||
}>({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: viewFilterGroup.id,
|
||||
viewId: view.id,
|
||||
parentViewFilterGroupId: viewFilterGroup.parentViewFilterGroupId,
|
||||
logicalOperator: viewFilterGroup.logicalOperator,
|
||||
positionInViewFilterGroup:
|
||||
viewFilterGroup.positionInViewFilterGroup,
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.createViewFilterGroup;
|
||||
if (!record) return;
|
||||
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error('Failed to create view filter group');
|
||||
}
|
||||
|
||||
return { newRecordId: result.data.createViewFilterGroup.id };
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
],
|
||||
);
|
||||
|
||||
const createViewFilterGroupRecords = useCallback(
|
||||
async (viewFilterGroupsToCreate: ViewFilterGroup[], view: GraphQLView) => {
|
||||
if (!viewFilterGroupsToCreate.length) return [];
|
||||
|
||||
const oldToNewId = new Map<string, string>();
|
||||
|
||||
for (const viewFilterGroupToCreate of viewFilterGroupsToCreate) {
|
||||
const newParentViewFilterGroupId = isDefined(
|
||||
viewFilterGroupToCreate.parentViewFilterGroupId,
|
||||
)
|
||||
? (oldToNewId.get(viewFilterGroupToCreate.parentViewFilterGroupId) ??
|
||||
viewFilterGroupToCreate.parentViewFilterGroupId)
|
||||
: undefined;
|
||||
|
||||
const { newRecordId } = await createViewFilterGroupRecord(
|
||||
{
|
||||
...viewFilterGroupToCreate,
|
||||
parentViewFilterGroupId: newParentViewFilterGroupId,
|
||||
},
|
||||
view,
|
||||
);
|
||||
|
||||
oldToNewId.set(viewFilterGroupToCreate.id, newRecordId);
|
||||
}
|
||||
|
||||
const newRecordIds = viewFilterGroupsToCreate.map((viewFilterGroup) => {
|
||||
const newId = oldToNewId.get(viewFilterGroup.id);
|
||||
if (!newId) {
|
||||
throw new Error('Failed to create view filter group');
|
||||
}
|
||||
return newId;
|
||||
});
|
||||
|
||||
return newRecordIds;
|
||||
},
|
||||
[createViewFilterGroupRecord],
|
||||
);
|
||||
|
||||
const updateViewFilterGroupRecords = useCallback(
|
||||
(viewFilterGroupsToUpdate: ViewFilterGroup[]) => {
|
||||
if (!viewFilterGroupsToUpdate.length) return;
|
||||
return Promise.all(
|
||||
viewFilterGroupsToUpdate.map((viewFilterGroup) =>
|
||||
apolloClient.mutate<{ updateViewFilterGroup: ViewFilterGroup }>({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: viewFilterGroup.id,
|
||||
input: {
|
||||
parentViewFilterGroupId:
|
||||
viewFilterGroup.parentViewFilterGroupId,
|
||||
logicalOperator: viewFilterGroup.logicalOperator,
|
||||
positionInViewFilterGroup:
|
||||
viewFilterGroup.positionInViewFilterGroup,
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.updateViewFilterGroup;
|
||||
if (!record) return;
|
||||
const cachedRecord = getRecordFromCache<ObjectRecord>(record.id);
|
||||
|
||||
if (!cachedRecord) return;
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: cachedRecord,
|
||||
updatedRecord: record,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
getRecordFromCache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
updateOneRecordMutation,
|
||||
],
|
||||
);
|
||||
|
||||
const deleteViewFilterGroupRecords = useCallback(
|
||||
(viewFilterGroupIdsToDelete: string[]) => {
|
||||
if (!viewFilterGroupIdsToDelete.length) return;
|
||||
return Promise.all(
|
||||
viewFilterGroupIdsToDelete.map((viewFilterGroupId) =>
|
||||
apolloClient.mutate<{ destroyViewFilterGroup: ViewFilterGroup }>({
|
||||
mutation: destroyOneRecordMutation,
|
||||
variables: {
|
||||
idToDestroy: viewFilterGroupId,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.destroyViewFilterGroup;
|
||||
|
||||
if (!record) return;
|
||||
|
||||
const cachedRecord = getRecordFromCache(record.id, cache);
|
||||
|
||||
if (!cachedRecord) return;
|
||||
|
||||
triggerDestroyRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToDestroy: [cachedRecord],
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
destroyOneRecordMutation,
|
||||
getRecordFromCache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFilterGroupRecords,
|
||||
updateViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords,
|
||||
};
|
||||
};
|
||||
@ -50,11 +50,13 @@ export const usePersistViewFilterRecords = () => {
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: viewFilter.id,
|
||||
fieldMetadataId: viewFilter.fieldMetadataId,
|
||||
viewId: view.id,
|
||||
value: viewFilter.value,
|
||||
displayValue: viewFilter.displayValue,
|
||||
operand: viewFilter.operand,
|
||||
viewFilterGroupId: viewFilter.viewFilterGroupId,
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
|
||||
@ -4,9 +4,11 @@ import { RecordIndexRootPropsContext } from '@/object-record/record-index/contex
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
|
||||
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 { useGetViewFiltersCombined } from '@/views/hooks/useGetCombinedViewFilters';
|
||||
import { useGetViewSortsCombined } from '@/views/hooks/useGetCombinedViewSorts';
|
||||
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
|
||||
@ -45,6 +47,8 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
const { getViewSortsCombined } = useGetViewSortsCombined(viewBarComponentId);
|
||||
const { getViewFiltersCombined } =
|
||||
useGetViewFiltersCombined(viewBarComponentId);
|
||||
const { getViewFilterGroupsCombined } =
|
||||
useGetViewFilterGroupsCombined(viewBarComponentId);
|
||||
|
||||
const { createViewSortRecords } = usePersistViewSortRecords();
|
||||
|
||||
@ -52,6 +56,8 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
|
||||
const { createViewFilterRecords } = usePersistViewFilterRecords();
|
||||
|
||||
const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords();
|
||||
|
||||
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext);
|
||||
|
||||
const createViewFromCurrentView = useRecoilCallback(
|
||||
@ -143,11 +149,18 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
}
|
||||
|
||||
if (shouldCopyFiltersAndSorts === true) {
|
||||
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
|
||||
view.id,
|
||||
);
|
||||
const sourceViewCombinedFilters = getViewFiltersCombined(view.id);
|
||||
const sourceViewCombinedSorts = getViewSortsCombined(view.id);
|
||||
|
||||
await createViewSortRecords(sourceViewCombinedSorts, view);
|
||||
await createViewFilterRecords(sourceViewCombinedFilters, view);
|
||||
await createViewFilterGroupRecords(
|
||||
sourceViewCombinedFilterGroups,
|
||||
view,
|
||||
);
|
||||
}
|
||||
|
||||
set(isPersistingViewFieldsCallbackState, false);
|
||||
@ -160,10 +173,12 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
createViewFieldRecords,
|
||||
getViewSortsCombined,
|
||||
getViewFiltersCombined,
|
||||
getViewFilterGroupsCombined,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromCache,
|
||||
isPersistingViewFieldsCallbackState,
|
||||
createViewGroupRecords,
|
||||
createViewFilterGroupRecords,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
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 { View } from '@/views/types/View';
|
||||
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useGetViewFilterGroupsCombined = (viewBarComponentId?: string) => {
|
||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||
|
||||
const unsavedToUpsertViewFilterGroupsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||
viewBarComponentId,
|
||||
);
|
||||
|
||||
const unsavedToDeleteViewFilterGroupIdsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
|
||||
viewBarComponentId,
|
||||
);
|
||||
|
||||
const getViewFilterGroupsCombined = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(viewId: string) => {
|
||||
const view = views.find((view) => view.id === viewId);
|
||||
|
||||
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;
|
||||
},
|
||||
[
|
||||
views,
|
||||
unsavedToDeleteViewFilterGroupIdsCallbackState,
|
||||
unsavedToUpsertViewFilterGroupsCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
getViewFilterGroupsCombined,
|
||||
};
|
||||
};
|
||||
@ -9,12 +9,15 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { isCurrentViewKeyIndexComponentState } from '@/views/states/isCurrentViewIndexComponentState';
|
||||
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
|
||||
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
|
||||
import { View } from '@/views/types/View';
|
||||
import { getCombinedViewFilterGroups } from '@/views/utils/getCombinedViewFilterGroups';
|
||||
import { getCombinedViewFilters } from '@/views/utils/getCombinedViewFilters';
|
||||
import { getCombinedViewSorts } from '@/views/utils/getCombinedViewSorts';
|
||||
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
||||
@ -70,6 +73,12 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewFilterGroups = useRecoilComponentFamilyValueV2(
|
||||
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||
{ viewId },
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewSorts = useRecoilComponentFamilyValueV2(
|
||||
unsavedToUpsertViewSortsComponentFamilyState,
|
||||
{ viewId },
|
||||
@ -82,6 +91,12 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const unsavedToDeleteViewFilterGroupIds = useRecoilComponentFamilyValueV2(
|
||||
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
|
||||
{ viewId },
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const unsavedToDeleteViewSortIds = useRecoilComponentFamilyValueV2(
|
||||
unsavedToDeleteViewSortIdsComponentFamilyState,
|
||||
{ viewId },
|
||||
@ -104,6 +119,11 @@ export const useGetCurrentView = (viewBarInstanceId?: string) => {
|
||||
unsavedToUpsertViewFilters,
|
||||
unsavedToDeleteViewFilterIds,
|
||||
),
|
||||
viewFilterGroups: getCombinedViewFilterGroups(
|
||||
currentView.viewFilterGroups ?? [],
|
||||
unsavedToUpsertViewFilterGroups,
|
||||
unsavedToDeleteViewFilterGroupIds,
|
||||
),
|
||||
viewSorts: getCombinedViewSorts(
|
||||
currentView.viewSorts,
|
||||
unsavedToUpsertViewSorts,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@ -18,6 +20,12 @@ export const useResetUnsavedViewStates = (viewBarInstanceId?: string) => {
|
||||
viewBarInstanceId,
|
||||
);
|
||||
|
||||
const unsavedToDeleteViewFilterGroupIdsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
|
||||
viewBarInstanceId,
|
||||
);
|
||||
|
||||
const setUnsavedToUpsertViewFiltersCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToUpsertViewFiltersComponentFamilyState,
|
||||
@ -30,19 +38,29 @@ export const useResetUnsavedViewStates = (viewBarInstanceId?: string) => {
|
||||
viewBarInstanceId,
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewFilterGroupsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||
viewBarInstanceId,
|
||||
);
|
||||
|
||||
const resetUnsavedViewStates = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(viewId: string) => {
|
||||
set(unsavedToDeleteViewFilterGroupIdsCallbackState({ viewId }), []);
|
||||
set(setUnsavedToDeleteViewFilterIdsCallbackState({ viewId }), []);
|
||||
set(setUnsavedToDeleteViewSortIdsCallbackState({ viewId }), []);
|
||||
set(unsavedToUpsertViewFilterGroupsCallbackState({ viewId }), []);
|
||||
set(setUnsavedToUpsertViewFiltersCallbackState({ viewId }), []);
|
||||
set(unsavedToUpsertViewSortsCallbackState({ viewId }), []);
|
||||
},
|
||||
[
|
||||
unsavedToUpsertViewSortsCallbackState,
|
||||
setUnsavedToUpsertViewFiltersCallbackState,
|
||||
unsavedToUpsertViewFilterGroupsCallbackState,
|
||||
setUnsavedToDeleteViewSortIdsCallbackState,
|
||||
setUnsavedToDeleteViewFilterIdsCallbackState,
|
||||
unsavedToDeleteViewFilterGroupIdsCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -2,13 +2,16 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
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 { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
|
||||
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
|
||||
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -48,6 +51,18 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
viewBarComponentId,
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewFilterGroupsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToUpsertViewFilterGroupsComponentFamilyState,
|
||||
viewBarComponentId,
|
||||
);
|
||||
|
||||
const unsavedToDeleteViewFilterGroupIdsCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
unsavedToDeleteViewFilterGroupIdsComponentFamilyState,
|
||||
viewBarComponentId,
|
||||
);
|
||||
|
||||
const {
|
||||
createViewSortRecords,
|
||||
updateViewSortRecords,
|
||||
@ -60,6 +75,12 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
deleteViewFilterRecords,
|
||||
} = usePersistViewFilterRecords();
|
||||
|
||||
const {
|
||||
createViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords,
|
||||
updateViewFilterGroupRecords,
|
||||
} = usePersistViewFilterGroupRecords();
|
||||
|
||||
const { resetUnsavedViewStates } =
|
||||
useResetUnsavedViewStates(viewBarComponentId);
|
||||
|
||||
@ -131,14 +152,14 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
const viewFiltersToCreate = unsavedToUpsertViewFilters.filter(
|
||||
(viewFilter) =>
|
||||
!view.viewFilters.some(
|
||||
(vf) => vf.fieldMetadataId === viewFilter.fieldMetadataId,
|
||||
(viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id,
|
||||
),
|
||||
);
|
||||
|
||||
const viewFiltersToUpdate = unsavedToUpsertViewFilters.filter(
|
||||
(viewFilter) =>
|
||||
view.viewFilters.some(
|
||||
(vf) => vf.fieldMetadataId === viewFilter.fieldMetadataId,
|
||||
(viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id,
|
||||
),
|
||||
);
|
||||
|
||||
@ -156,6 +177,55 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
],
|
||||
);
|
||||
|
||||
const saveViewFilterGroups = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (viewId: string) => {
|
||||
const unsavedToDeleteViewFilterGroupIds = getSnapshotValue(
|
||||
snapshot,
|
||||
unsavedToDeleteViewFilterGroupIdsCallbackState({ viewId }),
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewFilterGroups = getSnapshotValue(
|
||||
snapshot,
|
||||
unsavedToUpsertViewFilterGroupsCallbackState({ viewId }),
|
||||
);
|
||||
|
||||
const view = await getViewFromCache(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);
|
||||
},
|
||||
[
|
||||
getViewFromCache,
|
||||
createViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords,
|
||||
unsavedToDeleteViewFilterGroupIdsCallbackState,
|
||||
unsavedToUpsertViewFilterGroupsCallbackState,
|
||||
updateViewFilterGroupRecords,
|
||||
],
|
||||
);
|
||||
|
||||
const saveCurrentViewFilterAndSorts = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (viewIdFromProps?: string) => {
|
||||
@ -169,6 +239,7 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
|
||||
const viewId = viewIdFromProps ?? currentViewId;
|
||||
|
||||
await saveViewFilterGroups(viewId);
|
||||
await saveViewFilters(viewId);
|
||||
await saveViewSorts(viewId);
|
||||
|
||||
@ -179,6 +250,7 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
resetUnsavedViewStates,
|
||||
saveViewFilters,
|
||||
saveViewSorts,
|
||||
saveViewFilterGroups,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { currentViewIdComponentState } from '@/views/states/currentViewIdCompone
|
||||
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { shouldReplaceFilter } from '@/views/utils/shouldReplaceFilter';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useUpsertCombinedViewFilters = (viewBarComponentId?: string) => {
|
||||
@ -59,19 +60,16 @@ export const useUpsertCombinedViewFilters = (viewBarComponentId?: string) => {
|
||||
}
|
||||
|
||||
const matchingFilterInCurrentView = currentView.viewFilters.find(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
|
||||
(viewFilter) => shouldReplaceFilter(viewFilter, upsertedFilter),
|
||||
);
|
||||
|
||||
const matchingFilterInUnsavedFilters = unsavedToUpsertViewFilters.find(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
|
||||
(viewFilter) => shouldReplaceFilter(viewFilter, upsertedFilter),
|
||||
);
|
||||
|
||||
if (isDefined(matchingFilterInUnsavedFilters)) {
|
||||
const updatedFilters = unsavedToUpsertViewFilters.map((viewFilter) =>
|
||||
viewFilter.fieldMetadataId ===
|
||||
matchingFilterInUnsavedFilters.fieldMetadataId
|
||||
shouldReplaceFilter(viewFilter, matchingFilterInUnsavedFilters)
|
||||
? { ...viewFilter, ...upsertedFilter, id: viewFilter.id }
|
||||
: viewFilter,
|
||||
);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
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,
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
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,
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewFilter } from '../types/ViewFilter';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
|
||||
export const unsavedToUpsertViewFiltersComponentFamilyState =
|
||||
createComponentFamilyStateV2<ViewFilter[], { viewId?: string }>({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
import { ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
@ -15,6 +16,7 @@ export type GraphQLView = {
|
||||
isCompact: boolean;
|
||||
viewFields: ViewField[];
|
||||
viewFilters: ViewFilter[];
|
||||
viewFilterGroups?: ViewFilterGroup[];
|
||||
viewSorts: ViewSort[];
|
||||
viewGroups: ViewGroup[];
|
||||
position: number;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
import { ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
@ -15,6 +16,7 @@ export type View = {
|
||||
viewFields: ViewField[];
|
||||
viewGroups: ViewGroup[];
|
||||
viewFilters: ViewFilter[];
|
||||
viewFilterGroups?: ViewFilterGroup[];
|
||||
viewSorts: ViewSort[];
|
||||
kanbanFieldMetadataId: string;
|
||||
position: number;
|
||||
|
||||
@ -12,5 +12,7 @@ export type ViewFilter = {
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
viewId?: string;
|
||||
viewFilterGroupId?: string;
|
||||
positionInViewFilterGroup?: number | null;
|
||||
definition?: FilterDefinition;
|
||||
};
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||
|
||||
export type ViewFilterGroup = {
|
||||
__typename: 'ViewFilterGroup';
|
||||
id: string;
|
||||
viewId: string;
|
||||
parentViewFilterGroupId?: string | null;
|
||||
logicalOperator: ViewFilterGroupLogicalOperator;
|
||||
positionInViewFilterGroup?: number | null;
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
export enum ViewFilterGroupLogicalOperator {
|
||||
AND = 'AND',
|
||||
OR = 'OR',
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
|
||||
export const getCombinedViewFilterGroups = (
|
||||
viewFilterGroups: ViewFilterGroup[],
|
||||
unsavedToUpsertViewFilterGroups: ViewFilterGroup[],
|
||||
unsavedToDeleteViewFilterGroupIds: string[],
|
||||
): ViewFilterGroup[] => {
|
||||
const toCreateViewFilterGroups = unsavedToUpsertViewFilterGroups.filter(
|
||||
(toUpsertViewFilterGroup) =>
|
||||
!viewFilterGroups.some(
|
||||
(viewFilterGroup) => viewFilterGroup.id === toUpsertViewFilterGroup.id,
|
||||
),
|
||||
);
|
||||
|
||||
const toUpdateViewFilterGroups = unsavedToUpsertViewFilterGroups.filter(
|
||||
(toUpsertViewFilterGroup) =>
|
||||
viewFilterGroups.some(
|
||||
(viewFilterGroup) => viewFilterGroup.id === toUpsertViewFilterGroup.id,
|
||||
),
|
||||
);
|
||||
|
||||
const combinedViewFilterGroups = viewFilterGroups
|
||||
.filter(
|
||||
(viewFilterGroup) =>
|
||||
!unsavedToDeleteViewFilterGroupIds.includes(viewFilterGroup.id),
|
||||
)
|
||||
.map((viewFilterGroup) => {
|
||||
const toUpdateViewFilterGroup = toUpdateViewFilterGroups.find(
|
||||
(toUpdateViewFilterGroup) =>
|
||||
toUpdateViewFilterGroup.id === viewFilterGroup.id,
|
||||
);
|
||||
|
||||
return toUpdateViewFilterGroup ?? viewFilterGroup;
|
||||
})
|
||||
.concat(toCreateViewFilterGroups);
|
||||
|
||||
return combinedViewFilterGroups;
|
||||
};
|
||||
@ -8,24 +8,19 @@ export const getCombinedViewFilters = (
|
||||
const toCreateViewFilters = toUpsertViewFilters.filter(
|
||||
(toUpsertViewFilter) =>
|
||||
!viewFilters.some(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId === toUpsertViewFilter.fieldMetadataId,
|
||||
(viewFilter) => viewFilter.id === toUpsertViewFilter.id,
|
||||
),
|
||||
);
|
||||
|
||||
const toUpdateViewFilters = toUpsertViewFilters.filter((toUpsertViewFilter) =>
|
||||
viewFilters.some(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId === toUpsertViewFilter.fieldMetadataId,
|
||||
),
|
||||
viewFilters.some((viewFilter) => viewFilter.id === toUpsertViewFilter.id),
|
||||
);
|
||||
|
||||
const combinedViewFilters = viewFilters
|
||||
.filter((viewFilter) => !toDeleteViewFilterIds.includes(viewFilter.id))
|
||||
.map((viewFilter) => {
|
||||
const toUpdateViewFilter = toUpdateViewFilters.find(
|
||||
(toUpdateViewFilter) =>
|
||||
toUpdateViewFilter.fieldMetadataId === viewFilter.fieldMetadataId,
|
||||
(toUpdateViewFilter) => toUpdateViewFilter.id === viewFilter.id,
|
||||
);
|
||||
|
||||
return toUpdateViewFilter ?? viewFilter;
|
||||
|
||||
@ -3,7 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { View } from '@/views/types/View';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
@ -27,7 +27,7 @@ export const getQueryVariablesFromView = ({
|
||||
};
|
||||
}
|
||||
|
||||
const { viewFilters, viewSorts } = view;
|
||||
const { viewFilterGroups, viewFilters, viewSorts } = view;
|
||||
|
||||
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
|
||||
fields: fieldMetadataItems,
|
||||
@ -38,9 +38,10 @@ export const getQueryVariablesFromView = ({
|
||||
fields: fieldMetadataItems,
|
||||
});
|
||||
|
||||
const filter = turnFiltersIntoQueryFilter(
|
||||
const filter = computeViewRecordGqlOperationFilter(
|
||||
mapViewFiltersToFilters(viewFilters, filterDefinitions),
|
||||
objectMetadataItem?.fields ?? [],
|
||||
viewFilterGroups ?? [],
|
||||
);
|
||||
|
||||
const orderBy = turnSortsIntoOrderBy(
|
||||
|
||||
@ -23,6 +23,8 @@ export const mapViewFiltersToFilters = (
|
||||
value: viewFilter.value,
|
||||
displayValue: viewFilter.displayValue,
|
||||
operand: viewFilter.operand,
|
||||
viewFilterGroupId: viewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
|
||||
definition: viewFilter.definition ?? availableFilterDefinition,
|
||||
};
|
||||
})
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const shouldReplaceFilter = (
|
||||
oldFilter: Pick<Filter, 'id' | 'fieldMetadataId' | 'viewFilterGroupId'>,
|
||||
newFilter: Pick<Filter, 'id' | 'fieldMetadataId' | 'viewFilterGroupId'>,
|
||||
) => {
|
||||
const isNewFilterAdvancedFilter = isDefined(newFilter.viewFilterGroupId);
|
||||
|
||||
if (isNewFilterAdvancedFilter) {
|
||||
return newFilter.id === oldFilter.id;
|
||||
} else {
|
||||
return (
|
||||
newFilter.fieldMetadataId === oldFilter.fieldMetadataId &&
|
||||
!oldFilter.viewFilterGroupId
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
|
||||
export const sortViewFilterGroupsOutermostFirst = (
|
||||
viewFilterGroups: ViewFilterGroup[],
|
||||
parentViewFilterGroupId?: string,
|
||||
): ViewFilterGroup[] => {
|
||||
const childGroups = viewFilterGroups.filter(
|
||||
(group) => group.parentViewFilterGroupId === parentViewFilterGroupId,
|
||||
);
|
||||
|
||||
return childGroups.flatMap((group) => [
|
||||
group,
|
||||
...sortViewFilterGroupsOutermostFirst(viewFilterGroups, group.id),
|
||||
]);
|
||||
};
|
||||
Reference in New Issue
Block a user