add multiple filters of same FieldMetadataType (#5892)

fixes: #5378
This commit is contained in:
Aditya Pimpalkar 2024-06-18 09:49:33 +01:00 committed by GitHub
parent de2b0527a3
commit 14abd99bb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 91 additions and 180 deletions

View File

@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
@ -8,6 +9,7 @@ export const ObjectFilterDropdownDateInput = () => {
const {
filterDefinitionUsedInDropdownState,
selectedOperandInDropdownState,
selectedFilterState,
setIsObjectFilterDropdownUnfolded,
selectFilter,
} = useFilterDropdown();
@ -19,10 +21,13 @@ export const ObjectFilterDropdownDateInput = () => {
selectedOperandInDropdownState,
);
const selectedFilter = useRecoilValue(selectedFilterState);
const handleChange = (date: Date | null) => {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: isDefined(date) ? date.toISOString() : '',
operand: selectedOperandInDropdown,

View File

@ -1,127 +0,0 @@
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
export const ObjectFilterDropdownEntitySearchSelect = ({
entitiesForSelect,
}: {
entitiesForSelect: EntitiesForMultipleEntitySelect<EntityForSelect>;
}) => {
const {
setObjectFilterDropdownSelectedEntityId,
filterDefinitionUsedInDropdownState,
selectedOperandInDropdownState,
objectFilterDropdownSearchInputState,
selectedFilterState,
selectFilter,
} = useFilterDropdown();
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const objectFilterDropdownSearchInput = useRecoilValue(
objectFilterDropdownSearchInputState,
);
const selectedFilter = useRecoilValue(selectedFilterState);
const { closeDropdown } = useDropdown(OBJECT_FILTER_DROPDOWN_ID);
const [isAllEntitySelected, setIsAllEntitySelected] = useState(false);
const handleRecordSelected = (
selectedEntity: EntityForSelect | null | undefined,
) => {
if (
!filterDefinitionUsedInDropdown ||
!selectedOperandInDropdown ||
!selectedEntity
) {
return;
}
if (isAllEntitySelected) {
setIsAllEntitySelected(false);
}
setObjectFilterDropdownSelectedEntityId(selectedEntity.id);
selectFilter?.({
displayValue: selectedEntity.name,
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
operand: selectedOperandInDropdown,
value: selectedEntity.id,
displayAvatarUrl: selectedEntity.avatarUrl,
definition: filterDefinitionUsedInDropdown,
});
closeDropdown();
};
const isAllEntitySelectShown =
!!filterDefinitionUsedInDropdown?.selectAllLabel &&
!!filterDefinitionUsedInDropdown?.SelectAllIcon &&
(isAllEntitySelected ||
filterDefinitionUsedInDropdown?.selectAllLabel
.toLocaleLowerCase()
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()));
const handleAllEntitySelectClick = () => {
if (
!filterDefinitionUsedInDropdown ||
!selectedOperandInDropdown ||
!filterDefinitionUsedInDropdown.selectAllLabel
) {
return;
}
setIsAllEntitySelected(true);
setObjectFilterDropdownSelectedEntityId(null);
closeDropdown();
selectFilter?.({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
operand: ViewFilterOperand.IsNotNull,
value: '',
definition: filterDefinitionUsedInDropdown,
});
};
useEffect(() => {
if (!selectedFilter) {
setObjectFilterDropdownSelectedEntityId(null);
} else {
setObjectFilterDropdownSelectedEntityId(selectedFilter.value);
setIsAllEntitySelected(
selectedFilter.operand === ViewFilterOperand.IsNotNull,
);
}
}, [
selectedFilter,
setObjectFilterDropdownSelectedEntityId,
entitiesForSelect.selectedEntities,
]);
return (
<SingleEntitySelectMenuItems
entitiesToSelect={entitiesForSelect.entitiesToSelect}
selectedEntity={entitiesForSelect.selectedEntities[0]}
loading={entitiesForSelect.loading}
onEntitySelected={handleRecordSelected}
SelectAllIcon={filterDefinitionUsedInDropdown?.SelectAllIcon}
selectAllLabel={filterDefinitionUsedInDropdown?.selectAllLabel}
isAllEntitySelected={isAllEntitySelected}
isAllEntitySelectShown={isAllEntitySelectShown}
onAllEntitySelected={handleAllEntitySelectClick}
/>
);
};

View File

@ -1,5 +1,6 @@
import { ChangeEvent } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
@ -8,6 +9,7 @@ export const ObjectFilterDropdownNumberInput = () => {
const {
selectedOperandInDropdownState,
filterDefinitionUsedInDropdownState,
selectedFilterState,
selectFilter,
} = useFilterDropdown();
@ -18,6 +20,8 @@ export const ObjectFilterDropdownNumberInput = () => {
selectedOperandInDropdownState,
);
const selectedFilter = useRecoilValue(selectedFilterState);
return (
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
@ -27,6 +31,7 @@ export const ObjectFilterDropdownNumberInput = () => {
placeholder={filterDefinitionUsedInDropdown.label}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: event.target.value,
operand: selectedOperandInDropdown,

View File

@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -42,6 +43,7 @@ export const ObjectFilterDropdownOperandSelect = () => {
isDefined(selectedFilter)
) {
selectFilter?.({
id: selectedFilter.id ? selectedFilter.id : v4(),
fieldMetadataId: selectedFilter.fieldMetadataId,
displayValue: selectedFilter.displayValue,
operand: newOperand,

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
@ -22,6 +23,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
objectFilterDropdownSearchInputState,
selectedOperandInDropdownState,
objectFilterDropdownSelectedOptionValuesState,
selectedFilterState,
selectFilter,
} = useFilterDropdown();
@ -38,6 +40,8 @@ export const ObjectFilterDropdownOptionSelect = () => {
objectFilterDropdownSelectedOptionValuesState,
);
const selectedFilter = useRecoilValue(selectedFilterState);
const fieldMetaDataId = filterDefinitionUsedInDropdown?.fieldMetadataId ?? '';
const { selectOptions } = useOptionsForSelect(fieldMetaDataId);
@ -96,6 +100,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
: EMPTY_FILTER_VALUE;
selectFilter({
id: selectedFilter?.id ? selectedFilter.id : v4(),
definition: filterDefinitionUsedInDropdown,
operand: selectedOperandInDropdown,
displayValue: filterDisplayValue,

View File

@ -1,9 +1,13 @@
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from '~/utils/isDefined';
export const EMPTY_FILTER_VALUE = '[]';
@ -14,12 +18,16 @@ export const ObjectFilterDropdownRecordSelect = () => {
filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState,
selectedOperandInDropdownState,
selectedFilterState,
setObjectFilterDropdownSelectedRecordIds,
objectFilterDropdownSelectedRecordIdsState,
selectFilter,
emptyFilterButKeepDefinition,
} = useFilterDropdown();
const { removeCombinedViewFilter } = useCombinedViewFilters();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
@ -32,6 +40,9 @@ export const ObjectFilterDropdownRecordSelect = () => {
const objectFilterDropdownSelectedRecordIds = useRecoilValue(
objectFilterDropdownSelectedRecordIdsState,
);
const [fieldId] = useState(v4());
const selectedFilter = useRecoilValue(selectedFilterState);
const objectNameSingular =
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? '';
@ -60,6 +71,7 @@ export const ObjectFilterDropdownRecordSelect = () => {
if (newSelectedRecordIds.length === 0) {
emptyFilterButKeepDefinition();
removeCombinedViewFilter(fieldId);
return;
}
@ -91,7 +103,17 @@ export const ObjectFilterDropdownRecordSelect = () => {
? JSON.stringify(newSelectedRecordIds)
: EMPTY_FILTER_VALUE;
const viewFilter =
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
(viewFilter) =>
viewFilter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
);
const filterId = viewFilter?.id ?? fieldId;
selectFilter({
id: selectedFilter?.id ? selectedFilter.id : filterId,
definition: filterDefinitionUsedInDropdown,
operand: selectedOperandInDropdown,
displayValue: filterDisplayValue,

View File

@ -1,5 +1,6 @@
import { ChangeEvent } from 'react';
import { ChangeEvent, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
@ -14,6 +15,8 @@ export const ObjectFilterDropdownTextSearchInput = () => {
selectFilter,
} = useFilterDropdown();
const [filterId] = useState(v4());
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
@ -37,6 +40,7 @@ export const ObjectFilterDropdownTextSearchInput = () => {
setObjectFilterDropdownSearchInput(event.target.value);
selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : filterId,
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: event.target.value,
operand: selectedOperandInDropdown,

View File

@ -23,6 +23,7 @@ const filterDefinitions: FilterDefinition[] = [
];
const mockFilter: Filter = {
id: 'id',
definition: filterDefinitions[0],
displayValue: '',
fieldMetadataId: '',

View File

@ -3,6 +3,7 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { FilterDefinition } from './FilterDefinition';
export type Filter = {
id: string;
fieldMetadataId: string;
value: string;
displayValue: string;

View File

@ -1,4 +1,5 @@
import { useCallback } from 'react';
import { v4 } from 'uuid';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@ -46,6 +47,7 @@ export const useHandleToggleColumnFilter = ({
const defaultOperand = availableOperandsForFilter[0];
const newFilter: Filter = {
id: v4(),
fieldMetadataId,
operand: defaultOperand,
displayValue: '',

View File

@ -16,8 +16,8 @@ export const EditableFilterChip = ({
const { getIcon } = useIcons();
return (
<SortOrFilterChip
key={viewFilter.fieldMetadataId}
testId={viewFilter.fieldMetadataId}
key={viewFilter.id}
testId={viewFilter.id}
labelKey={viewFilter.definition.label}
labelValue={`${getOperandLabelShort(viewFilter.operand)} ${
viewFilter.displayValue

View File

@ -62,13 +62,13 @@ export const EditableFilterDropdownButton = ({
const handleRemove = () => {
closeDropdown();
removeCombinedViewFilter(viewFilter.fieldMetadataId);
removeCombinedViewFilter(viewFilter.id);
};
const handleDropdownClickOutside = useCallback(() => {
const { value, fieldMetadataId } = viewFilter;
const { id: fieldId, value } = viewFilter;
if (!value) {
removeCombinedViewFilter(fieldMetadataId);
removeCombinedViewFilter(fieldId);
}
}, [viewFilter, removeCombinedViewFilter]);

View File

@ -153,19 +153,17 @@ export const ViewBarDetails = ({
availableFilterDefinitions,
).map((viewFilter) => (
<ObjectFilterDropdownScope
key={viewFilter.fieldMetadataId}
filterScopeId={viewFilter.fieldMetadataId}
key={viewFilter.id}
filterScopeId={viewFilter.id}
>
<DropdownScope dropdownScopeId={viewFilter.fieldMetadataId}>
<ViewBarFilterEffect
filterDropdownId={viewFilter.fieldMetadataId}
/>
<DropdownScope dropdownScopeId={viewFilter.id}>
<ViewBarFilterEffect filterDropdownId={viewFilter.id} />
<EditableFilterDropdownButton
viewFilter={viewFilter}
hotkeyScope={{
scope: viewFilter.fieldMetadataId,
scope: viewFilter.id,
}}
viewFilterDropdownId={viewFilter.fieldMetadataId}
viewFilterDropdownId={viewFilter.id}
/>
</DropdownScope>
</ObjectFilterDropdownScope>

View File

@ -6,6 +6,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { useViewStates } from '@/views/hooks/internal/useViewStates';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from '~/utils/isDefined';
type ViewBarFilterEffectProps = {
@ -15,11 +16,12 @@ type ViewBarFilterEffectProps = {
export const ViewBarFilterEffect = ({
filterDropdownId,
}: ViewBarFilterEffectProps) => {
const { availableFilterDefinitionsState, unsavedToUpsertViewFiltersState } =
useViewStates();
const { availableFilterDefinitionsState } = useViewStates();
const { upsertCombinedViewFilter } = useCombinedViewFilters();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
);
@ -51,47 +53,41 @@ export const ViewBarFilterEffect = ({
upsertCombinedViewFilter,
]);
const unsavedToUpsertViewFilters = useRecoilValue(
unsavedToUpsertViewFiltersState,
);
useEffect(() => {
if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
const viewFilterUsedInDropdown = unsavedToUpsertViewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
);
const viewFilterUsedInDropdown =
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown?.fieldMetadataId,
);
const viewFilterSelectedRecordIds = isNonEmptyString(
const viewFilterSelectedRecords = isNonEmptyString(
viewFilterUsedInDropdown?.value,
)
? JSON.parse(viewFilterUsedInDropdown.value)
: [];
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecordIds);
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecords);
} else if (filterDefinitionUsedInDropdown?.type === 'SELECT') {
const viewFilterUsedInDropdown = unsavedToUpsertViewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
);
const viewFilterUsedInDropdown =
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown?.fieldMetadataId,
);
const viewFilterSelectedOptionValues = isNonEmptyString(
const viewFilterSelectedRecords = isNonEmptyString(
viewFilterUsedInDropdown?.value,
)
? JSON.parse(viewFilterUsedInDropdown.value)
: [];
setObjectFilterDropdownSelectedOptionValues(
viewFilterSelectedOptionValues,
);
setObjectFilterDropdownSelectedOptionValues(viewFilterSelectedRecords);
}
}, [
filterDefinitionUsedInDropdown,
setObjectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedOptionValues,
unsavedToUpsertViewFilters,
currentViewWithCombinedFiltersAndSorts,
]);
return <></>;

View File

@ -1,5 +1,4 @@
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
@ -43,13 +42,11 @@ export const useCombinedViewFilters = (viewBarComponentId?: string) => {
}
const matchingFilterInCurrentView = currentView.viewFilters.find(
(viewFilter) =>
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
(viewFilter) => viewFilter.id === upsertedFilter.id,
);
const matchingFilterInUnsavedFilters = unsavedToUpsertViewFilters.find(
(viewFilter) =>
viewFilter.fieldMetadataId === upsertedFilter.fieldMetadataId,
(viewFilter) => viewFilter.id === upsertedFilter.id,
);
if (isDefined(matchingFilterInUnsavedFilters)) {
@ -81,7 +78,6 @@ export const useCombinedViewFilters = (viewBarComponentId?: string) => {
...unsavedToUpsertViewFilters,
{
...upsertedFilter,
id: v4(),
__typename: 'ViewFilter',
} satisfies ViewFilter,
]);
@ -95,7 +91,7 @@ export const useCombinedViewFilters = (viewBarComponentId?: string) => {
);
const removeCombinedViewFilter = useRecoilCallback(
({ snapshot, set }) =>
async (fieldMetadataId: string) => {
async (fieldId: string) => {
const unsavedToUpsertViewFilters = getSnapshotValue(
snapshot,
unsavedToUpsertViewFiltersState,
@ -119,18 +115,18 @@ export const useCombinedViewFilters = (viewBarComponentId?: string) => {
}
const matchingFilterInCurrentView = currentView.viewFilters.find(
(viewFilter) => viewFilter.fieldMetadataId === fieldMetadataId,
(viewFilter) => viewFilter.id === fieldId,
);
const matchingFilterInUnsavedFilters = unsavedToUpsertViewFilters.find(
(viewFilter) => viewFilter.fieldMetadataId === fieldMetadataId,
(viewFilter) => viewFilter.id === fieldId,
);
if (isDefined(matchingFilterInUnsavedFilters)) {
set(
unsavedToUpsertViewFiltersState,
unsavedToUpsertViewFilters.filter(
(viewFilter) => viewFilter.fieldMetadataId !== fieldMetadataId,
(viewFilter) => viewFilter.id !== fieldId,
),
);
}

View File

@ -55,6 +55,7 @@ describe('mapViewFiltersToFilters', () => {
];
const expectedFilters: Filter[] = [
{
id: 'id',
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
value: 'testValue',
displayValue: 'Test Display Value',

View File

@ -28,9 +28,6 @@ export const combinedViewFilters = (
.concat(toCreateViewFilters);
return Object.values(
combinedViewFilters.reduce(
(acc, obj) => ({ ...acc, [obj.fieldMetadataId]: obj }),
{},
),
combinedViewFilters.reduce((acc, obj) => ({ ...acc, [obj.id]: obj }), {}),
);
};

View File

@ -18,6 +18,7 @@ export const mapViewFiltersToFilters = (
if (!availableFilterDefinition) return null;
return {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,

View File

@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
@ -29,6 +30,7 @@ export const TasksEffect = ({ filterDropdownId }: TasksEffectProps) => {
useEffect(() => {
if (isDefined(currentWorkspaceMember)) {
setSelectedFilter({
id: v4(),
fieldMetadataId: 'assigneeId',
value: JSON.stringify(currentWorkspaceMember.id),
operand: ViewFilterOperand.Is,