Add rating filter/sort + fix isEmpty/isNotEmpty + fix combinedViewFilters (#6310)

## Context
- Adding RATING sort and filter capabilities.
- Fixing isEmpty/isNotEmpty filters
- Fixing combined view filters so it combines filters per field metadata
and not per filter id. This is more a product question but to me it does
not make sense to apply multiples filters on the same field IF the
operations is wrapped in a AND. If at some point we want to put a OR
instead then that would make more sense
This commit is contained in:
Weiko
2024-07-17 17:54:37 +02:00
committed by GitHub
parent 47ddc7be83
commit efd932e99b
12 changed files with 172 additions and 14 deletions

View File

@ -5,6 +5,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
@ -70,6 +71,9 @@ export const MultipleFiltersDropdownContent = ({
{['NUMBER', 'CURRENCY'].includes(
filterDefinitionUsedInDropdown.type,
) && <ObjectFilterDropdownNumberInput />}
{filterDefinitionUsedInDropdown.type === 'RATING' && (
<ObjectFilterDropdownRatingInput />
)}
{filterDefinitionUsedInDropdown.type === 'DATE_TIME' && (
<ObjectFilterDropdownDateInput />
)}

View File

@ -0,0 +1,67 @@
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
import { FieldRatingValue } from '@/object-record/record-field/types/FieldMetadata';
import { RatingInput } from '@/ui/field/input/components/RatingInput';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
const convertFieldRatingValueToNumber = (rating: FieldRatingValue): string => {
return rating.split('_')[1];
};
export const convertGreaterThanRatingToArrayOfRatingValues = (
greaterThanValue: number,
) => {
return RATING_VALUES.filter((_, index) => index + 1 > greaterThanValue);
};
export const convertLessThanRatingToArrayOfRatingValues = (
lessThanValue: number,
) => {
return RATING_VALUES.filter((_, index) => index + 1 <= lessThanValue);
};
export const convertRatingToRatingValue = (rating: number) => {
return `RATING_${rating}`;
};
export const ObjectFilterDropdownRatingInput = () => {
const {
selectedOperandInDropdownState,
filterDefinitionUsedInDropdownState,
selectedFilterState,
selectFilter,
} = useFilterDropdown();
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const selectedFilter = useRecoilValue(selectedFilterState);
return (
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
<DropdownMenuItemsContainer>
<RatingInput
value={selectedFilter?.value as FieldRatingValue}
onChange={(newValue: FieldRatingValue) => {
selectFilter?.({
id: selectedFilter?.id ? selectedFilter.id : v4(),
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: convertFieldRatingValueToNumber(newValue),
operand: selectedOperandInDropdown,
displayValue: convertFieldRatingValueToNumber(newValue),
definition: filterDefinitionUsedInDropdown,
});
}}
/>
</DropdownMenuItemsContainer>
)
);
};

View File

@ -12,4 +12,5 @@ export type FilterType =
| 'RELATION'
| 'ADDRESS'
| 'SELECT'
| 'RATING'
| 'MULTI_SELECT';

View File

@ -34,6 +34,13 @@ export const getOperandsForFilterType = (
ViewFilterOperand.LessThan,
...emptyOperands,
];
case 'RATING':
return [
ViewFilterOperand.Is,
ViewFilterOperand.GreaterThan,
ViewFilterOperand.LessThan,
...emptyOperands,
];
case 'RELATION':
return [...relationOperands, ...emptyOperands];
case 'SELECT':

View File

@ -143,6 +143,7 @@ export const isRecordMatchingFilter = ({
case FieldMetadataType.Email:
case FieldMetadataType.Phone:
case FieldMetadataType.Select:
case FieldMetadataType.Rating:
case FieldMetadataType.MultiSelect:
case FieldMetadataType.Text: {
return isMatchingStringFilter({

View File

@ -18,6 +18,11 @@ import { Field } from '~/generated/graphql';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
import { isDefined } from '~/utils/isDefined';
import {
convertGreaterThanRatingToArrayOfRatingValues,
convertLessThanRatingToArrayOfRatingValues,
convertRatingToRatingValue,
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { Filter } from '../../object-filter-dropdown/types/Filter';
export type ObjectDropdownFilter = Omit<Filter, 'definition'> & {
@ -187,6 +192,11 @@ const applyEmptyFilters = (
[correspondingField.name]: { is: 'NULL' } as FloatFilter,
};
break;
case 'RATING':
emptyRecordFilter = {
[correspondingField.name]: { is: 'NULL' } as StringFilter,
};
break;
case 'DATE_TIME':
emptyRecordFilter = {
[correspondingField.name]: { is: 'NULL' } as DateFilter,
@ -313,6 +323,48 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
);
}
break;
case 'RATING':
switch (rawUIFilter.operand) {
case ViewFilterOperand.Is:
objectRecordFilters.push({
[correspondingField.name]: {
eq: convertRatingToRatingValue(parseFloat(rawUIFilter.value)),
} as StringFilter,
});
break;
case ViewFilterOperand.GreaterThan:
objectRecordFilters.push({
[correspondingField.name]: {
in: convertGreaterThanRatingToArrayOfRatingValues(
parseFloat(rawUIFilter.value),
),
} as StringFilter,
});
break;
case ViewFilterOperand.LessThan:
objectRecordFilters.push({
[correspondingField.name]: {
in: convertLessThanRatingToArrayOfRatingValues(
parseFloat(rawUIFilter.value),
),
} as StringFilter,
});
break;
case ViewFilterOperand.IsEmpty:
case ViewFilterOperand.IsNotEmpty:
applyEmptyFilters(
rawUIFilter.operand,
correspondingField,
objectRecordFilters,
rawUIFilter.definition.type,
);
break;
default:
throw new Error(
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`,
);
}
break;
case 'NUMBER':
switch (rawUIFilter.operand) {
case ViewFilterOperand.GreaterThan: