TWNTY-6808 - Ability to Filter by Creation Source (#7078)

### Description

- Ability to Filter by Creation Source

### Demo

LOOM:
<https://www.loom.com/share/dba9c3d37a4242fe90f977b1babffbde?sid=59b07c51-d245-43cc-bb38-7d898ef72878>

### Refs

#6808

Fixes #6808

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
gitstart-app[bot]
2024-10-02 17:56:09 +02:00
committed by GitHub
parent 2cd3219636
commit 35788af351
27 changed files with 686 additions and 155 deletions

View File

@ -92,6 +92,7 @@ export type LinksFilter = {
export type ActorFilter = { export type ActorFilter = {
name?: StringFilter; name?: StringFilter;
source?: IsFilter;
}; };
export type EmailsFilter = { export type EmailsFilter = {

View File

@ -2,6 +2,11 @@ import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-d
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput'; import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect'; import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
@ -11,8 +16,6 @@ import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInp
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton'; import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect'; import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect'; import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
const StyledContainer = styled.div` const StyledContainer = styled.div`
position: relative; position: relative;
@ -113,6 +116,12 @@ export const MultipleFiltersDropdownContent = ({
<ObjectFilterDropdownRecordSelect /> <ObjectFilterDropdownRecordSelect />
</> </>
)} )}
{filterDefinitionUsedInDropdown.type === 'SOURCE' && (
<>
<DropdownMenuSeparator />
<ObjectFilterDropdownSourceSelect />
</>
)}
{filterDefinitionUsedInDropdown.type === 'SELECT' && ( {filterDefinitionUsedInDropdown.type === 'SELECT' && (
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />

View File

@ -1,17 +1,15 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { ObjectFilterSelectMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterSelectMenu';
import { ObjectFilterSelectSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterSelectSubMenu';
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { currentSubMenuState } from '@/object-record/object-filter-dropdown/states/subMenuStates';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
export const StyledInput = styled.input` export const StyledInput = styled.input`
@ -47,6 +45,9 @@ export const ObjectFilterDropdownFilterSelect = () => {
availableFilterDefinitionsComponentState, availableFilterDefinitionsComponentState,
); );
const [currentSubMenu, setCurrentSubMenu] =
useRecoilState(currentSubMenuState);
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions] const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]
.sort((a, b) => a.label.localeCompare(b.label)) .sort((a, b) => a.label.localeCompare(b.label))
.filter((item) => .filter((item) =>
@ -75,37 +76,21 @@ export const ObjectFilterDropdownFilterSelect = () => {
selectFilter({ filterDefinition: selectedFilterDefinition }); selectFilter({ filterDefinition: selectedFilterDefinition });
}; };
return ( useEffect(() => {
<> return () => {
<StyledInput setCurrentSubMenu(null);
value={searchText} };
autoFocus }, [setCurrentSubMenu]);
placeholder="Search fields"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => return !currentSubMenu ? (
setSearchText(event.target.value) <ObjectFilterSelectMenu
} searchText={searchText}
/> setSearchText={setSearchText}
<SelectableList sortedAvailableFilterDefinitions={sortedAvailableFilterDefinitions}
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton} selectableListItemIds={selectableListItemIds}
selectableItemIdArray={selectableListItemIds} handleEnter={handleEnter}
selectableListId={OBJECT_FILTER_DROPDOWN_ID} />
onEnter={handleEnter} ) : (
> <ObjectFilterSelectSubMenu />
<DropdownMenuItemsContainer>
{sortedAvailableFilterDefinitions.map(
(availableFilterDefinition, index) => (
<SelectableItem
itemId={availableFilterDefinition.fieldMetadataId}
key={`select-filter-${index}`}
>
<ObjectFilterDropdownFilterSelectMenuItem
filterDefinition={availableFilterDefinition}
/>
</SelectableItem>
),
)}
</DropdownMenuItemsContainer>
</SelectableList>
</>
); );
}; };

View File

@ -1,9 +1,14 @@
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
import {
currentParentFilterDefinitionState,
currentSubMenuState,
} from '@/object-record/object-filter-dropdown/states/subMenuStates';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { hasSubMenuFilter } from '@/object-record/object-filter-dropdown/utils/hasSubMenuFilter';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
import { useRecoilValue } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useIcons } from 'twenty-ui'; import { useIcons } from 'twenty-ui';
export type ObjectFilterDropdownFilterSelectMenuItemProps = { export type ObjectFilterDropdownFilterSelectMenuItemProps = {
@ -23,12 +28,24 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
isSelectedItemIdSelector(filterDefinition.fieldMetadataId), isSelectedItemIdSelector(filterDefinition.fieldMetadataId),
); );
const hasSubMenu = hasSubMenuFilter(filterDefinition.type);
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const setCurrentSubMenu = useSetRecoilState(currentSubMenuState);
const setCurrentParentFilterDefinition = useSetRecoilState(
currentParentFilterDefinitionState,
);
const handleClick = () => { const handleClick = () => {
resetSelectedItem(); resetSelectedItem();
selectFilter({ filterDefinition }); if (hasSubMenu) {
setCurrentSubMenu(filterDefinition.type);
setCurrentParentFilterDefinition(filterDefinition);
} else {
selectFilter({ filterDefinition });
}
}; };
return ( return (
@ -38,6 +55,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
onClick={handleClick} onClick={handleClick}
LeftIcon={getIcon(filterDefinition.iconName)} LeftIcon={getIcon(filterDefinition.iconName)}
text={filterDefinition.label} text={filterDefinition.label}
hasSubMenu={hasSubMenu}
/> />
); );
}; };

View File

@ -4,9 +4,9 @@ import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown'; import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -66,7 +66,7 @@ export const ObjectFilterDropdownRecordSelect = ({
}); });
const handleMultipleRecordSelectChange = ( const handleMultipleRecordSelectChange = (
recordToSelect: SelectableRecord, recordToSelect: SelectableItem,
newSelectedValue: boolean, newSelectedValue: boolean,
) => { ) => {
if (loading) { if (loading) {
@ -134,15 +134,15 @@ export const ObjectFilterDropdownRecordSelect = ({
}; };
return ( return (
<MultipleRecordSelectDropdown <MultipleSelectDropdown
selectableListId="object-filter-record-select-id" selectableListId="object-filter-record-select-id"
hotkeyScope={RelationPickerHotkeyScope.RelationPicker} hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
recordsToSelect={recordsToSelect} itemsToSelect={recordsToSelect}
filteredSelectedRecords={filteredSelectedRecords} filteredSelectedItems={filteredSelectedRecords}
selectedRecords={selectedRecords} selectedItems={selectedRecords}
onChange={handleMultipleRecordSelectChange} onChange={handleMultipleRecordSelectChange}
searchFilter={objectFilterDropdownSearchInput} searchFilter={objectFilterDropdownSearchInput}
loadingRecords={loading} loadingItems={loading}
/> />
); );
}; };

View File

@ -0,0 +1,137 @@
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { getSourceEnumOptions } from '@/object-record/object-filter-dropdown/utils/getSourceEnumOptions';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { isDefined } from '~/utils/isDefined';
export const EMPTY_FILTER_VALUE = '[]';
export const MAX_ITEMS_TO_DISPLAY = 3;
type ObjectFilterDropdownSourceSelectProps = {
viewComponentId?: string;
};
export const ObjectFilterDropdownSourceSelect = ({
viewComponentId,
}: ObjectFilterDropdownSourceSelectProps) => {
const {
filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState,
selectedOperandInDropdownState,
selectedFilterState,
setObjectFilterDropdownSelectedRecordIds,
objectFilterDropdownSelectedRecordIdsState,
selectFilter,
emptyFilterButKeepDefinition,
} = useFilterDropdown();
const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(viewComponentId);
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId);
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
const objectFilterDropdownSearchInput = useRecoilValue(
objectFilterDropdownSearchInputState,
);
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const objectFilterDropdownSelectedRecordIds = useRecoilValue(
objectFilterDropdownSelectedRecordIdsState,
);
const [fieldId] = useState(v4());
const selectedFilter = useRecoilValue(selectedFilterState);
const sourceTypes = getSourceEnumOptions(
objectFilterDropdownSelectedRecordIds,
);
const filteredSelectedItems = sourceTypes.filter((option) =>
objectFilterDropdownSelectedRecordIds.includes(option.id),
);
const handleMultipleItemSelectChange = (
itemToSelect: SelectableItem,
newSelectedValue: boolean,
) => {
const newSelectedItemIds = newSelectedValue
? [...objectFilterDropdownSelectedRecordIds, itemToSelect.id]
: objectFilterDropdownSelectedRecordIds.filter(
(id) => id !== itemToSelect.id,
);
if (newSelectedItemIds.length === 0) {
emptyFilterButKeepDefinition();
deleteCombinedViewFilter(fieldId);
return;
}
setObjectFilterDropdownSelectedRecordIds(newSelectedItemIds);
const selectedItemNames = sourceTypes
.filter((option) => newSelectedItemIds.includes(option.id))
.map((option) => option.name);
const filterDisplayValue =
selectedItemNames.length > MAX_ITEMS_TO_DISPLAY
? `${selectedItemNames.length} source types`
: selectedItemNames.join(', ');
if (
isDefined(filterDefinitionUsedInDropdown) &&
isDefined(selectedOperandInDropdown)
) {
const newFilterValue =
newSelectedItemIds.length > 0
? JSON.stringify(newSelectedItemIds)
: 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 || ViewFilterOperand.Is,
displayValue: filterDisplayValue,
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: newFilterValue,
});
}
};
return (
<MultipleSelectDropdown
selectableListId="object-filter-source-select-id"
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
itemsToSelect={sourceTypes.filter(
(item) =>
!filteredSelectedItems.some((selected) => selected.id === item.id),
)}
filteredSelectedItems={filteredSelectedItems}
selectedItems={filteredSelectedItems}
onChange={handleMultipleItemSelectChange}
searchFilter={objectFilterDropdownSearchInput}
loadingItems={false}
/>
);
};

View File

@ -0,0 +1,87 @@
import styled from '@emotion/styled';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
export const StyledInput = styled.input`
background: transparent;
border: none;
border-top: none;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: 0;
border-top-left-radius: ${({ theme }) => theme.border.radius.md};
border-top-right-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
margin: 0;
outline: none;
padding: ${({ theme }) => theme.spacing(2)};
height: 19px;
font-family: inherit;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: inherit;
max-width: 100%;
overflow: hidden;
text-decoration: none;
&::placeholder {
color: ${({ theme }) => theme.font.color.light};
}
`;
type ObjectFilterSelectMenuProps = {
searchText: string;
setSearchText: (searchText: string) => void;
sortedAvailableFilterDefinitions: FilterDefinition[];
selectableListItemIds: string[];
handleEnter: (itemId: string) => void;
};
export const ObjectFilterSelectMenu = ({
searchText,
setSearchText,
sortedAvailableFilterDefinitions,
selectableListItemIds,
handleEnter,
}: ObjectFilterSelectMenuProps) => {
return (
<>
<StyledInput
value={searchText}
autoFocus
placeholder="Search fields"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value)
}
/>
<SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={selectableListItemIds}
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
onEnter={handleEnter}
>
<DropdownMenuItemsContainer>
{sortedAvailableFilterDefinitions.map(
(availableFilterDefinition: FilterDefinition, index: number) => (
<SelectableItem
key={`selectable-item-${availableFilterDefinition.fieldMetadataId}`}
itemId={availableFilterDefinition.fieldMetadataId}
>
<ObjectFilterDropdownFilterSelectMenuItem
key={`select-filter-${index}`}
filterDefinition={availableFilterDefinition}
/>
</SelectableItem>
),
)}
</DropdownMenuItemsContainer>
</SelectableList>
</>
);
};

View File

@ -0,0 +1,102 @@
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import {
currentParentFilterDefinitionState,
currentSubMenuState,
} from '@/object-record/object-filter-dropdown/states/subMenuStates';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
import { getHeaderTitle } from '@/object-record/object-filter-dropdown/utils/getHeaderTitle';
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
import { getSubMenuOptions } from '@/object-record/object-filter-dropdown/utils/getSubMenuOptions';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { IconChevronLeft, useIcons } from 'twenty-ui';
export const ObjectFilterSelectSubMenu = () => {
const [searchText, setSearchText] = useState('');
const { getIcon } = useIcons();
const [currentSubMenu, setCurrentSubMenu] =
useRecoilState(currentSubMenuState);
const currentParentFilterDefinition = useRecoilValue(
currentParentFilterDefinitionState,
);
const {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setObjectFilterDropdownSearchInput,
} = useFilterDropdown();
const setHotkeyScope = useSetHotkeyScope();
const handleSelectFilter = (definition: FilterDefinition | null) => {
if (definition !== null) {
setFilterDefinitionUsedInDropdown(definition);
if (definition.type === 'SOURCE') {
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterType(definition.type)?.[0],
);
setObjectFilterDropdownSearchInput('');
}
};
return (
<>
<DropdownMenuHeader
StartIcon={IconChevronLeft}
onClick={() => {
setCurrentSubMenu(null);
}}
>
{getHeaderTitle(currentSubMenu)}
</DropdownMenuHeader>
<StyledInput
value={searchText}
autoFocus
placeholder="Search fields"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value)
}
/>
<DropdownMenuItemsContainer>
{getSubMenuOptions(currentSubMenu)
.sort((a, b) => a.name.localeCompare(b.name))
.filter((item) =>
item.name
.toLocaleLowerCase()
.includes(searchText.toLocaleLowerCase()),
)
.map((menuOption, index) => (
<MenuItem
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
currentParentFilterDefinition &&
handleSelectFilter({
...currentParentFilterDefinition,
label: menuOption.name,
type: menuOption.type as FilterType,
});
}}
text={menuOption.name}
LeftIcon={getIcon(
menuOption.icon || currentParentFilterDefinition?.iconName,
)}
/>
))}
</DropdownMenuItemsContainer>
</>
);
};

View File

@ -0,0 +1,15 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
import { atom } from 'recoil';
export const currentSubMenuState = atom<FilterType | null>({
key: 'currentSubMenuState',
default: null,
});
export const currentParentFilterDefinitionState = atom<FilterDefinition | null>(
{
key: 'currentParentFilterDefinitionState',
default: null,
},
);

View File

@ -11,4 +11,5 @@ export type FilterDefinition = {
relationObjectMetadataNameSingular?: string; relationObjectMetadataNameSingular?: string;
selectAllLabel?: string; selectAllLabel?: string;
SelectAllIcon?: IconComponent; SelectAllIcon?: IconComponent;
subFieldType?: FilterType;
}; };

View File

@ -17,4 +17,5 @@ export type FilterType =
| 'RATING' | 'RATING'
| 'MULTI_SELECT' | 'MULTI_SELECT'
| 'ACTOR' | 'ACTOR'
| 'ARRAY'; | 'ARRAY'
| 'SOURCE';

View File

@ -0,0 +1,14 @@
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
export const getHeaderTitle = (
subMenu: FilterType | null,
): string | undefined => {
switch (subMenu) {
case 'ACTOR':
return 'Actor';
case 'SOURCE':
return 'Creation Source';
default:
return undefined;
}
};

View File

@ -57,6 +57,8 @@ export const getOperandsForFilterType = (
]; ];
case 'RELATION': case 'RELATION':
return [...relationOperands, ...emptyOperands]; return [...relationOperands, ...emptyOperands];
case 'SOURCE':
return [...relationOperands];
case 'SELECT': case 'SELECT':
return [...relationOperands]; return [...relationOperands];
default: default:

View File

@ -0,0 +1,56 @@
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import {
IconApi,
IconCsv,
IconGmail,
IconGoogleCalendar,
IconSettingsAutomation,
IconUserCircle,
} from 'twenty-ui';
export const getSourceEnumOptions = (
selectedItemIds: string[],
): SelectableItem[] => {
return [
{
id: 'MANUAL',
name: 'User',
isSelected: selectedItemIds.includes('MANUAL'),
AvatarIcon: IconUserCircle,
isIconInverted: true,
},
{
id: 'IMPORT',
name: 'Import',
isSelected: selectedItemIds.includes('IMPORT'),
AvatarIcon: IconCsv,
isIconInverted: true,
},
{
id: 'API',
name: 'Api',
isSelected: selectedItemIds.includes('API'),
AvatarIcon: IconApi,
isIconInverted: true,
},
{
id: 'EMAIL',
name: 'Email',
isSelected: selectedItemIds.includes('EMAIL'),
AvatarIcon: IconGmail,
},
{
id: 'CALENDAR',
name: 'Calendar',
isSelected: selectedItemIds.includes('CALENDAR'),
AvatarIcon: IconGoogleCalendar,
},
{
id: 'WORKFLOW',
name: 'Workflow',
isSelected: selectedItemIds.includes('WORKFLOW'),
AvatarIcon: IconSettingsAutomation,
isIconInverted: true,
},
];
};

View File

@ -0,0 +1,21 @@
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
export const getSubMenuOptions = (subMenu: FilterType | null) => {
switch (subMenu) {
case 'ACTOR':
return [
{
name: 'Creation Source',
icon: 'IconPlug',
type: 'SOURCE',
},
{
name: 'Creator Name',
icon: 'IconId',
type: 'ACTOR',
},
];
default:
return [];
}
};

View File

@ -0,0 +1,3 @@
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
export const hasSubMenuFilter = (type: FilterType) => ['ACTOR'].includes(type);

View File

@ -30,12 +30,6 @@ import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
import { z } from 'zod'; import { z } from 'zod';
import { Filter } from '../../object-filter-dropdown/types/Filter'; import { Filter } from '../../object-filter-dropdown/types/Filter';
export type ObjectDropdownFilter = Omit<Filter, 'definition'> & {
definition: {
type: Filter['definition']['type'];
};
};
const applyEmptyFilters = ( const applyEmptyFilters = (
operand: ViewFilterOperand, operand: ViewFilterOperand,
correspondingField: Pick<Field, 'id' | 'name'>, correspondingField: Pick<Field, 'id' | 'name'>,
@ -282,7 +276,7 @@ const applyEmptyFilters = (
}; };
export const turnObjectDropdownFilterIntoQueryFilter = ( export const turnObjectDropdownFilterIntoQueryFilter = (
rawUIFilters: ObjectDropdownFilter[], rawUIFilters: Filter[],
fields: Pick<Field, 'id' | 'name'>[], fields: Pick<Field, 'id' | 'name'>[],
): RecordGqlOperationFilter | undefined => { ): RecordGqlOperationFilter | undefined => {
const objectRecordFilters: RecordGqlOperationFilter[] = []; const objectRecordFilters: RecordGqlOperationFilter[] = [];
@ -894,48 +888,87 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
break; break;
} }
case 'ACTOR': case 'ACTOR':
switch (rawUIFilter.operand) { if (rawUIFilter.definition.subFieldType !== undefined) {
case ViewFilterOperand.Contains: const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
objectRecordFilters.push({ switch (rawUIFilter.definition.subFieldType) {
or: [ case 'SOURCE':
{ switch (rawUIFilter.operand) {
[correspondingField.name]: { case ViewFilterOperand.Is:
name: { objectRecordFilters.push({
ilike: `%${rawUIFilter.value}%`, [correspondingField.name]: {
source: {
in: parsedRecordIds,
} as RelationFilter,
}, },
} as ActorFilter, });
},
], break;
}); case ViewFilterOperand.IsNot:
break; if (parsedRecordIds.length > 0) {
case ViewFilterOperand.DoesNotContain: objectRecordFilters.push({
objectRecordFilters.push({ not: {
and: [ [correspondingField.name]: {
{ [rawUIFilter.definition.subFieldType.toLowerCase()]: {
not: { in: parsedRecordIds,
} as RelationFilter,
},
},
});
}
break;
default:
throw new Error(
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.subFieldType} filter`,
);
}
}
} else {
switch (rawUIFilter.operand) {
case ViewFilterOperand.Contains:
objectRecordFilters.push({
or: [
{
[correspondingField.name]: { [correspondingField.name]: {
name: { name: {
ilike: `%${rawUIFilter.value}%`, ilike: `%${rawUIFilter.value}%`,
}, },
} as ActorFilter, } as ActorFilter,
}, },
}, ],
], });
}); break;
break; case ViewFilterOperand.DoesNotContain:
case ViewFilterOperand.IsEmpty: objectRecordFilters.push({
case ViewFilterOperand.IsNotEmpty: and: [
applyEmptyFilters( {
rawUIFilter.operand, not: {
correspondingField, [correspondingField.name]: {
objectRecordFilters, name: {
rawUIFilter.definition.type, ilike: `%${rawUIFilter.value}%`,
); },
break; } as ActorFilter,
default: },
throw new Error( },
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, ],
); });
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;
} }
break; break;
case 'EMAILS': case 'EMAILS':

View File

@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { Avatar } from 'twenty-ui'; import { AvatarChip } from 'twenty-ui';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -14,26 +15,36 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar'; import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
export const MultipleRecordSelectDropdown = ({ const StyledAvatarChip = styled(AvatarChip)`
&.avatar-icon-container {
color: ${({ theme }) => theme.font.color.secondary};
gap: ${({ theme }) => theme.spacing(2)};
padding-left: 0px;
padding-right: 0px;
font-size: ${({ theme }) => theme.font.size.md};
}
`;
export const MultipleSelectDropdown = ({
selectableListId, selectableListId,
hotkeyScope, hotkeyScope,
recordsToSelect, itemsToSelect,
loadingRecords, loadingItems,
filteredSelectedRecords, filteredSelectedItems,
onChange, onChange,
searchFilter, searchFilter,
}: { }: {
selectableListId: string; selectableListId: string;
hotkeyScope: string; hotkeyScope: string;
recordsToSelect: SelectableRecord[]; itemsToSelect: SelectableItem[];
filteredSelectedRecords: SelectableRecord[]; filteredSelectedItems: SelectableItem[];
selectedRecords: SelectableRecord[]; selectedItems: SelectableItem[];
searchFilter: string; searchFilter: string;
onChange: ( onChange: (
changedRecordToSelect: SelectableRecord, changedItemToSelect: SelectableItem,
newSelectedValue: boolean, newSelectedValue: boolean,
) => void; ) => void;
loadingRecords: boolean; loadingItems: boolean;
}) => { }) => {
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown();
const { selectedItemIdState } = useSelectableListStates({ const { selectedItemIdState } = useSelectableListStates({
@ -44,32 +55,32 @@ export const MultipleRecordSelectDropdown = ({
const selectedItemId = useRecoilValue(selectedItemIdState); const selectedItemId = useRecoilValue(selectedItemIdState);
const handleRecordSelectChange = ( const handleItemSelectChange = (
recordToSelect: SelectableRecord, itemToSelect: SelectableItem,
newSelectedValue: boolean, newSelectedValue: boolean,
) => { ) => {
onChange( onChange(
{ {
...recordToSelect, ...itemToSelect,
isSelected: newSelectedValue, isSelected: newSelectedValue,
}, },
newSelectedValue, newSelectedValue,
); );
}; };
const [recordsInDropdown, setRecordInDropdown] = useState([ const [itemsInDropdown, setItemInDropdown] = useState([
...(filteredSelectedRecords ?? []), ...(filteredSelectedItems ?? []),
...(recordsToSelect ?? []), ...(itemsToSelect ?? []),
]); ]);
useEffect(() => { useEffect(() => {
if (!loadingRecords) { if (!loadingItems) {
setRecordInDropdown([ setItemInDropdown([
...(filteredSelectedRecords ?? []), ...(filteredSelectedItems ?? []),
...(recordsToSelect ?? []), ...(itemsToSelect ?? []),
]); ]);
} }
}, [recordsToSelect, filteredSelectedRecords, loadingRecords]); }, [itemsToSelect, filteredSelectedItems, loadingItems]);
useScopedHotkeys( useScopedHotkeys(
[Key.Escape], [Key.Escape],
@ -82,12 +93,12 @@ export const MultipleRecordSelectDropdown = ({
); );
const showNoResult = const showNoResult =
recordsToSelect?.length === 0 && itemsToSelect?.length === 0 &&
searchFilter !== '' && searchFilter !== '' &&
filteredSelectedRecords?.length === 0 && filteredSelectedItems?.length === 0 &&
!loadingRecords; !loadingItems;
const selectableItemIds = recordsInDropdown.map((record) => record.id); const selectableItemIds = itemsInDropdown.map((item) => item.id);
return ( return (
<SelectableList <SelectableList
@ -95,45 +106,46 @@ export const MultipleRecordSelectDropdown = ({
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {
const record = recordsInDropdown.findIndex( const item = itemsInDropdown.findIndex(
(entity) => entity.id === itemId, (entity) => entity.id === itemId,
); );
const recordIsSelectedInDropwdown = filteredSelectedRecords.find( const itemIsSelectedInDropwdown = filteredSelectedItems.find(
(entity) => entity.id === itemId, (entity) => entity.id === itemId,
); );
handleRecordSelectChange( handleItemSelectChange(
recordsInDropdown[record], itemsInDropdown[item],
!recordIsSelectedInDropwdown, !itemIsSelectedInDropwdown,
); );
resetSelectedItem(); resetSelectedItem();
}} }}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{recordsInDropdown?.map((record) => { {itemsInDropdown?.map((item) => {
return ( return (
<MenuItemMultiSelectAvatar <MenuItemMultiSelectAvatar
key={record.id} key={item.id}
selected={record.isSelected} selected={item.isSelected}
isKeySelected={record.id === selectedItemId} isKeySelected={item.id === selectedItemId}
onSelectChange={(newCheckedValue) => { onSelectChange={(newCheckedValue) => {
resetSelectedItem(); resetSelectedItem();
handleRecordSelectChange(record, newCheckedValue); handleItemSelectChange(item, newCheckedValue);
}} }}
avatar={ avatar={
<Avatar <StyledAvatarChip
avatarUrl={record.avatarUrl} className="avatar-icon-container"
placeholderColorSeed={record.id} name={item.name}
placeholder={record.name} avatarUrl={item.avatarUrl}
size="md" LeftIcon={item.AvatarIcon}
type={record.avatarType ?? 'rounded'} avatarType={item.avatarType}
isIconInverted={item.isIconInverted}
placeholderColorSeed={item.id}
/> />
} }
text={record.name}
/> />
); );
})} })}
{showNoResult && <MenuItem text="No result" />} {showNoResult && <MenuItem text="No result" />}
{loadingRecords && <DropdownMenuSkeletonItem />} {loadingItems && <DropdownMenuSkeletonItem />}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</SelectableList> </SelectableList>
); );

View File

@ -5,7 +5,7 @@ import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapTo
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields'; import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields';
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
@ -109,19 +109,19 @@ export const useRecordsForSelect = ({
.map((record) => ({ .map((record) => ({
...record, ...record,
isSelected: true, isSelected: true,
})) as SelectableRecord[], })) as SelectableItem[],
filteredSelectedRecords: filteredSelectedRecordsData filteredSelectedRecords: filteredSelectedRecordsData
.map(mapToObjectRecordIdentifier) .map(mapToObjectRecordIdentifier)
.map((record) => ({ .map((record) => ({
...record, ...record,
isSelected: true, isSelected: true,
})) as SelectableRecord[], })) as SelectableItem[],
recordsToSelect: recordsToSelectData recordsToSelect: recordsToSelectData
.map(mapToObjectRecordIdentifier) .map(mapToObjectRecordIdentifier)
.map((record) => ({ .map((record) => ({
...record, ...record,
isSelected: false, isSelected: false,
})) as SelectableRecord[], })) as SelectableItem[],
loading: loading:
recordsToSelectLoading || recordsToSelectLoading ||
filteredSelectedRecordsLoading || filteredSelectedRecordsLoading ||

View File

@ -0,0 +1,11 @@
import { AvatarType, IconComponent } from 'twenty-ui';
export type SelectableItem<T = object> = T & {
id: string;
name: string;
avatarUrl?: string;
avatarType?: AvatarType;
AvatarIcon?: IconComponent;
isSelected: boolean;
isIconInverted?: boolean;
};

View File

@ -1,10 +0,0 @@
import { AvatarType } from 'twenty-ui';
export type SelectableRecord = {
id: string;
name: string;
avatarUrl?: string;
avatarType?: AvatarType;
record: any;
isSelected: boolean;
};

View File

@ -22,7 +22,7 @@ type MenuItemMultiSelectAvatarProps = {
avatar?: ReactNode; avatar?: ReactNode;
selected: boolean; selected: boolean;
isKeySelected?: boolean; isKeySelected?: boolean;
text: string; text?: string;
className?: string; className?: string;
onSelectChange?: (selected: boolean) => void; onSelectChange?: (selected: boolean) => void;
}; };

View File

@ -1,6 +1,6 @@
import { css, useTheme } from '@emotion/react'; import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconCheck, IconComponent } from 'twenty-ui'; import { IconCheck, IconChevronRight, IconComponent } from 'twenty-ui';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
@ -45,6 +45,7 @@ type MenuItemSelectProps = {
onClick?: () => void; onClick?: () => void;
disabled?: boolean; disabled?: boolean;
hovered?: boolean; hovered?: boolean;
hasSubMenu?: boolean;
}; };
export const MenuItemSelect = ({ export const MenuItemSelect = ({
@ -55,6 +56,7 @@ export const MenuItemSelect = ({
onClick, onClick,
disabled, disabled,
hovered, hovered,
hasSubMenu = false,
}: MenuItemSelectProps) => { }: MenuItemSelectProps) => {
const theme = useTheme(); const theme = useTheme();
@ -68,6 +70,12 @@ export const MenuItemSelect = ({
> >
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} /> <MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
{selected && <IconCheck size={theme.icon.size.md} />} {selected && <IconCheck size={theme.icon.size.md} />}
{hasSubMenu && (
<IconChevronRight
size={theme.icon.size.sm}
color={theme.font.color.tertiary}
/>
)}
</StyledMenuItemSelect> </StyledMenuItemSelect>
); );
}; };

View File

@ -1,3 +1,4 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { ViewFilterOperand } from './ViewFilterOperand'; import { ViewFilterOperand } from './ViewFilterOperand';
export type ViewFilter = { export type ViewFilter = {
@ -11,4 +12,5 @@ export type ViewFilter = {
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
viewId?: string; viewId?: string;
definition?: FilterDefinition;
}; };

View File

@ -0,0 +1,17 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { hasSubMenuFilter } from '@/object-record/object-filter-dropdown/utils/hasSubMenuFilter';
import { ViewFilter } from '../types/ViewFilter';
export const getFilterDefinitionForViewFilter = (
viewFilter: ViewFilter,
availableFilterDefinition: FilterDefinition,
): FilterDefinition => {
return {
...availableFilterDefinition,
subFieldType:
hasSubMenuFilter(availableFilterDefinition.type) &&
viewFilter.definition?.type !== availableFilterDefinition.type
? viewFilter.definition?.type
: undefined,
};
};

View File

@ -2,6 +2,7 @@ import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { getFilterDefinitionForViewFilter } from '@/views/utils/getFilterDefinitionForViewFilter';
import { ViewFilter } from '../types/ViewFilter'; import { ViewFilter } from '../types/ViewFilter';
export const mapViewFiltersToFilters = ( export const mapViewFiltersToFilters = (
@ -23,7 +24,10 @@ export const mapViewFiltersToFilters = (
value: viewFilter.value, value: viewFilter.value,
displayValue: viewFilter.displayValue, displayValue: viewFilter.displayValue,
operand: viewFilter.operand, operand: viewFilter.operand,
definition: availableFilterDefinition, definition: getFilterDefinitionForViewFilter(
viewFilter,
availableFilterDefinition,
),
}; };
}) })
.filter(isDefined); .filter(isDefined);

View File

@ -127,6 +127,7 @@ export const Chip = ({
rightComponent, rightComponent,
accent = ChipAccent.TextPrimary, accent = ChipAccent.TextPrimary,
onClick, onClick,
className,
}: ChipProps) => { }: ChipProps) => {
return ( return (
<StyledContainer <StyledContainer
@ -137,6 +138,7 @@ export const Chip = ({
size={size} size={size}
variant={variant} variant={variant}
onClick={onClick} onClick={onClick}
className={className}
> >
{leftComponent} {leftComponent}
<OverflowingTextWithTooltip <OverflowingTextWithTooltip