Fixed bugs in ViewBar filtering (#7608)

- Fixed CSS for SortOrFilter chips
- Fixed bug when refreshing with an actor source filter set

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-10-11 20:25:15 +02:00
committed by GitHub
parent 7b96be6f8c
commit fef3d32237
5 changed files with 108 additions and 98 deletions

View File

@ -36,8 +36,7 @@ export const triggerDestroyRecordsOptimisticEffect = ({
const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse; const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse;
const recordIdsToDelete = recordsToDestroy.map(({ id }) => id); const recordIdsToDestroy = recordsToDestroy.map(({ id }) => id);
const cachedEdges = readField<RecordGqlRefEdge[]>( const cachedEdges = readField<RecordGqlRefEdge[]>(
'edges', 'edges',
rootQueryCachedObjectRecordConnection, rootQueryCachedObjectRecordConnection,
@ -52,7 +51,7 @@ export const triggerDestroyRecordsOptimisticEffect = ({
cachedEdges?.filter((cachedEdge) => { cachedEdges?.filter((cachedEdge) => {
const nodeId = readField<string>('id', cachedEdge.node); const nodeId = readField<string>('id', cachedEdge.node);
return nodeId && !recordIdsToDelete.includes(nodeId); return nodeId && !recordIdsToDestroy.includes(nodeId);
}) || []; }) || [];
if (nextCachedEdges.length === cachedEdges?.length) if (nextCachedEdges.length === cachedEdges?.length)
@ -62,7 +61,7 @@ export const triggerDestroyRecordsOptimisticEffect = ({
...rootQueryCachedObjectRecordConnection, ...rootQueryCachedObjectRecordConnection,
edges: nextCachedEdges, edges: nextCachedEdges,
totalCount: isDefined(totalCount) totalCount: isDefined(totalCount)
? totalCount - recordIdsToDelete.length ? totalCount - recordIdsToDestroy.length
: undefined, : undefined,
}; };
}, },

View File

@ -1,7 +1,7 @@
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
@ -16,7 +16,7 @@ import { useState } from 'react';
import { IconApps, IconChevronLeft, isDefined, useIcons } from 'twenty-ui'; import { IconApps, IconChevronLeft, isDefined, useIcons } from 'twenty-ui';
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
const [searchText, setSearchText] = useState(''); const [searchText] = useState('');
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -31,6 +31,11 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
objectFilterDropdownFilterIsSelectedComponentState, objectFilterDropdownFilterIsSelectedComponentState,
); );
const [, setObjectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const [ const [
objectFilterDropdownSubMenuFieldType, objectFilterDropdownSubMenuFieldType,
setObjectFilterDropdownSubMenuFieldType, setObjectFilterDropdownSubMenuFieldType,
@ -62,6 +67,8 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
setFilterDefinitionUsedInDropdown(null); setFilterDefinitionUsedInDropdown(null);
setObjectFilterDropdownSubMenuFieldType(null); setObjectFilterDropdownSubMenuFieldType(null);
setObjectFilterDropdownFirstLevelFilterDefinition(null); setObjectFilterDropdownFirstLevelFilterDefinition(null);
setObjectFilterDropdownIsSelectingCompositeField(false);
setObjectFilterDropdownFilterIsSelected(false);
}; };
if ( if (
@ -87,14 +94,14 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
> >
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} {getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
</DropdownMenuHeader> </DropdownMenuHeader>
<StyledInput {/* <StyledInput
value={searchText} value={searchText}
autoFocus autoFocus
placeholder="Search fields" placeholder="Search fields"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value) setSearchText(event.target.value)
} }
/> /> */}
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<MenuItem <MenuItem
key={`select-filter-${-1}`} key={`select-filter-${-1}`}
@ -105,31 +112,33 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
LeftIcon={IconApps} LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`} text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
/> />
{options.map((subFieldName, index) => ( {/* TODO: fix this with a backend field on ViewFilter for composite field filter */}
<MenuItem {objectFilterDropdownFirstLevelFilterDefinition.type === 'ACTOR' &&
key={`select-filter-${index}`} options.map((subFieldName, index) => (
testId={`select-filter-${index}`} <MenuItem
onClick={() => { key={`select-filter-${index}`}
if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) { testId={`select-filter-${index}`}
handleSelectFilter({ onClick={() => {
...objectFilterDropdownFirstLevelFilterDefinition, if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) {
label: getCompositeSubFieldLabel( handleSelectFilter({
objectFilterDropdownSubMenuFieldType, ...objectFilterDropdownFirstLevelFilterDefinition,
subFieldName, label: getCompositeSubFieldLabel(
), objectFilterDropdownSubMenuFieldType,
compositeFieldName: subFieldName, subFieldName,
}); ),
} compositeFieldName: subFieldName,
}} });
text={getCompositeSubFieldLabel( }
objectFilterDropdownSubMenuFieldType, }}
subFieldName, text={getCompositeSubFieldLabel(
)} objectFilterDropdownSubMenuFieldType,
LeftIcon={getIcon( subFieldName,
objectFilterDropdownFirstLevelFilterDefinition?.iconName, )}
)} LeftIcon={getIcon(
/> objectFilterDropdownFirstLevelFilterDefinition?.iconName,
))} )}
/>
))}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</> </>
); );

View File

@ -24,7 +24,6 @@ import {
convertRatingToRatingValue, convertRatingToRatingValue,
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
import { applyEmptyFilters } from '@/object-record/record-filter/utils/applyEmptyFilters'; import { applyEmptyFilters } from '@/object-record/record-filter/utils/applyEmptyFilters';
import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue';
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
@ -677,81 +676,83 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
} }
break; break;
} }
case 'ACTOR': // TODO: fix this with a new composite field in ViewFilter entity
if (isActorSourceCompositeFilter(rawUIFilter.definition)) { case 'ACTOR': {
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[]; switch (rawUIFilter.operand) {
case ViewFilterOperand.Is: {
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
switch (rawUIFilter.operand) { objectRecordFilters.push({
case ViewFilterOperand.Is: [correspondingField.name]: {
source: {
in: parsedRecordIds,
} as RelationFilter,
},
});
break;
}
case ViewFilterOperand.IsNot: {
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
if (parsedRecordIds.length > 0) {
objectRecordFilters.push({ objectRecordFilters.push({
[correspondingField.name]: { not: {
source: { [correspondingField.name]: {
in: parsedRecordIds, source: {
} as RelationFilter, in: parsedRecordIds,
} as RelationFilter,
},
}, },
}); });
}
break; break;
case ViewFilterOperand.IsNot:
if (parsedRecordIds.length > 0) {
objectRecordFilters.push({
not: {
[correspondingField.name]: {
source: {
in: parsedRecordIds,
} as RelationFilter,
},
},
});
}
break;
} }
} else { case ViewFilterOperand.Contains:
switch (rawUIFilter.operand) { objectRecordFilters.push({
case ViewFilterOperand.Contains: or: [
objectRecordFilters.push({ {
or: [ [correspondingField.name]: {
{ name: {
ilike: `%${rawUIFilter.value}%`,
},
} as ActorFilter,
},
],
});
break;
case ViewFilterOperand.DoesNotContain:
objectRecordFilters.push({
and: [
{
not: {
[correspondingField.name]: { [correspondingField.name]: {
name: { name: {
ilike: `%${rawUIFilter.value}%`, ilike: `%${rawUIFilter.value}%`,
}, },
} as ActorFilter, } as ActorFilter,
}, },
], },
}); ],
break; });
case ViewFilterOperand.DoesNotContain: break;
objectRecordFilters.push({ case ViewFilterOperand.IsEmpty:
and: [ case ViewFilterOperand.IsNotEmpty:
{ applyEmptyFilters(
not: { rawUIFilter.operand,
[correspondingField.name]: { correspondingField,
name: { objectRecordFilters,
ilike: `%${rawUIFilter.value}%`, rawUIFilter.definition,
}, );
} as ActorFilter, break;
},
}, default:
], throw new Error(
}); `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.label} filter`,
break; );
case ViewFilterOperand.IsEmpty:
case ViewFilterOperand.IsNotEmpty:
applyEmptyFilters(
rawUIFilter.operand,
correspondingField,
objectRecordFilters,
rawUIFilter.definition,
);
break;
default:
throw new Error(
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.label} filter`,
);
}
} }
break; break;
}
case 'EMAILS': case 'EMAILS':
switch (rawUIFilter.operand) { switch (rawUIFilter.operand) {
case ViewFilterOperand.Contains: case ViewFilterOperand.Contains:

View File

@ -39,9 +39,11 @@ const StyledChip = styled.div<{ variant: SortOrFitlerChipVariant }>`
flex-shrink: 0; flex-shrink: 0;
font-size: ${({ theme }) => theme.font.size.sm}; font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
padding: ${({ theme }) => theme.spacing(1) + ' ' + theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(0.5) + ' ' + theme.spacing(2)};
user-select: none; user-select: none;
white-space: nowrap; white-space: nowrap;
max-height: ${({ theme }) => theme.spacing(4.5)};
`; `;
const StyledIcon = styled.div` const StyledIcon = styled.div`

View File

@ -50,7 +50,6 @@ const StyledChipcontainer = styled.div`
overflow: scroll; overflow: scroll;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
padding-top: ${({ theme }) => theme.spacing(1)}; padding-top: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${({ theme }) => theme.spacing(0.5)};
z-index: 1; z-index: 1;
`; `;