fix: filter and sort options to match order of table columns (#7392)

### ISSUE

- Closes #5960 

### Demo


https://github.com/user-attachments/assets/279b19cf-6841-4a63-82ed-423bc0eb4395

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Nabhag Motivaras
2024-10-10 20:35:22 +05:30
committed by GitHub
parent 2c927cfd7e
commit 539dc9506d
14 changed files with 573 additions and 277 deletions

View File

@ -1,38 +1,19 @@
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
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 { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect'; import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect'; import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter'; import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
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';
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect'; import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInput';
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect';
const StyledContainer = styled.div` const StyledContainer = styled.div`
position: relative; position: relative;
`; `;
const StyledOperandSelectContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
box-shadow: ${({ theme }) => theme.boxShadow.light};
border-radius: ${({ theme }) => theme.border.radius.md};
left: 10px;
position: absolute;
top: 10px;
width: 100%;
z-index: 1000;
`;
type MultipleFiltersDropdownContentProps = { type MultipleFiltersDropdownContentProps = {
filterDropdownId?: string; filterDropdownId?: string;
}; };
@ -40,99 +21,38 @@ type MultipleFiltersDropdownContentProps = {
export const MultipleFiltersDropdownContent = ({ export const MultipleFiltersDropdownContent = ({
filterDropdownId, filterDropdownId,
}: MultipleFiltersDropdownContentProps) => { }: MultipleFiltersDropdownContentProps) => {
const { const { filterDefinitionUsedInDropdownState } = useFilterDropdown({
filterDefinitionUsedInDropdownState, filterDropdownId,
selectedOperandInDropdownState, });
isObjectFilterDropdownOperandSelectUnfoldedState,
} = useFilterDropdown({ filterDropdownId });
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( const [objectFilterDropdownIsSelectingCompositeField] =
isObjectFilterDropdownOperandSelectUnfoldedState, useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
filterDropdownId,
);
const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
filterDropdownId,
); );
const filterDefinitionUsedInDropdown = useRecoilValue( const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
); );
const selectedOperandInDropdown = useRecoilValue( const shouldShowCompositeSelectionSubMenu =
selectedOperandInDropdownState, objectFilterDropdownIsSelectingCompositeField;
);
const isConfigurable = const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
selectedOperandInDropdown &&
[
ViewFilterOperand.Is,
ViewFilterOperand.IsNotNull,
ViewFilterOperand.IsNot,
ViewFilterOperand.LessThan,
ViewFilterOperand.GreaterThan,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
ViewFilterOperand.Contains,
ViewFilterOperand.DoesNotContain,
ViewFilterOperand.IsRelative,
].includes(selectedOperandInDropdown);
return ( return (
<StyledContainer> <StyledContainer>
{!filterDefinitionUsedInDropdown ? ( {shoudShowFilterInput ? (
<ObjectFilterDropdownFilterSelect /> <ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} />
) : shouldShowCompositeSelectionSubMenu ? (
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
) : ( ) : (
<> <ObjectFilterDropdownFilterSelect />
<ObjectFilterDropdownOperandButton />
{isObjectFilterDropdownOperandSelectUnfolded && (
<StyledOperandSelectContainer>
<ObjectFilterDropdownOperandSelect />
</StyledOperandSelectContainer>
)}
{isConfigurable && selectedOperandInDropdown && (
<>
{[
'TEXT',
'EMAIL',
'EMAILS',
'PHONE',
'FULL_NAME',
'LINK',
'LINKS',
'ADDRESS',
'ACTOR',
'ARRAY',
'PHONES',
].includes(filterDefinitionUsedInDropdown.type) &&
!isActorSourceCompositeFilter(
filterDefinitionUsedInDropdown,
) && <ObjectFilterDropdownTextSearchInput />}
{['NUMBER', 'CURRENCY'].includes(
filterDefinitionUsedInDropdown.type,
) && <ObjectFilterDropdownNumberInput />}
{filterDefinitionUsedInDropdown.type === 'RATING' && (
<ObjectFilterDropdownRatingInput />
)}
{['DATE_TIME', 'DATE'].includes(
filterDefinitionUsedInDropdown.type,
) && <ObjectFilterDropdownDateInput />}
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
<>
<ObjectFilterDropdownSearchInput />
<ObjectFilterDropdownRecordSelect />
</>
)}
{isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
<>
<DropdownMenuSeparator />
<ObjectFilterDropdownSourceSelect />
</>
)}
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
<>
<ObjectFilterDropdownSearchInput />
<ObjectFilterDropdownOptionSelect />
</>
)}
</>
)}
</>
)} )}
<MultipleFiltersDropdownFilterOnFilterChangedEffect <MultipleFiltersDropdownFilterOnFilterChangedEffect
filterDefinitionUsedInDropdownType={ filterDefinitionUsedInDropdownType={

View File

@ -1,6 +1,7 @@
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
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 { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton'; import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton';
@ -29,12 +30,16 @@ export const ObjectFilterDropdownButton = ({
} }
return ( return (
<ObjectFilterDropdownScope filterScopeId={filterDropdownId}> <ObjectFilterDropdownComponentInstanceContext.Provider
{hasOnlyOneEntityFilter ? ( value={{ instanceId: filterDropdownId }}
<SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} /> >
) : ( <ObjectFilterDropdownScope filterScopeId={filterDropdownId}>
<MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} /> {hasOnlyOneEntityFilter ? (
)} <SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} />
</ObjectFilterDropdownScope> ) : (
<MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} />
)}
</ObjectFilterDropdownScope>
</ObjectFilterDropdownComponentInstanceContext.Provider>
); );
}; };

View File

@ -0,0 +1,132 @@
import { useRecoilValue } from 'recoil';
import { ObjectFilterDropdownDateInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput';
import { ObjectFilterDropdownNumberInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput';
import { ObjectFilterDropdownOperandButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect';
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-ui';
const StyledOperandSelectContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
box-shadow: ${({ theme }) => theme.boxShadow.light};
border-radius: ${({ theme }) => theme.border.radius.md};
left: 10px;
position: absolute;
top: 10px;
width: 100%;
z-index: 1000;
`;
type ObjectFilterDropdownFilterInputProps = {
filterDropdownId?: string;
};
export const ObjectFilterDropdownFilterInput = ({
filterDropdownId,
}: ObjectFilterDropdownFilterInputProps) => {
const {
filterDefinitionUsedInDropdownState,
selectedOperandInDropdownState,
isObjectFilterDropdownOperandSelectUnfoldedState,
} = useFilterDropdown({ filterDropdownId });
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState,
);
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const isConfigurable =
selectedOperandInDropdown &&
[
ViewFilterOperand.Is,
ViewFilterOperand.IsNotNull,
ViewFilterOperand.IsNot,
ViewFilterOperand.LessThan,
ViewFilterOperand.GreaterThan,
ViewFilterOperand.IsBefore,
ViewFilterOperand.IsAfter,
ViewFilterOperand.Contains,
ViewFilterOperand.DoesNotContain,
ViewFilterOperand.IsRelative,
].includes(selectedOperandInDropdown);
if (!isDefined(filterDefinitionUsedInDropdown)) {
return null;
}
return (
<>
<ObjectFilterDropdownOperandButton />
{isObjectFilterDropdownOperandSelectUnfolded && (
<StyledOperandSelectContainer>
<ObjectFilterDropdownOperandSelect />
</StyledOperandSelectContainer>
)}
{isConfigurable && selectedOperandInDropdown && (
<>
{[
'TEXT',
'EMAIL',
'EMAILS',
'PHONE',
'FULL_NAME',
'LINK',
'LINKS',
'ADDRESS',
'ACTOR',
'ARRAY',
'PHONES',
].includes(filterDefinitionUsedInDropdown.type) &&
!isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
<ObjectFilterDropdownTextSearchInput />
)}
{['NUMBER', 'CURRENCY'].includes(
filterDefinitionUsedInDropdown.type,
) && <ObjectFilterDropdownNumberInput />}
{filterDefinitionUsedInDropdown.type === 'RATING' && (
<ObjectFilterDropdownRatingInput />
)}
{['DATE_TIME', 'DATE'].includes(
filterDefinitionUsedInDropdown.type,
) && <ObjectFilterDropdownDateInput />}
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
<>
<ObjectFilterDropdownSearchInput />
<ObjectFilterDropdownRecordSelect />
</>
)}
{isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
<>
<DropdownMenuSeparator />
<ObjectFilterDropdownSourceSelect />
</>
)}
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
<>
<ObjectFilterDropdownSearchInput />
<ObjectFilterDropdownOptionSelect />
</>
)}
</>
)}
</>
);
};

View File

@ -1,27 +1,23 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useState } from 'react'; import { useContext } from 'react';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu'; 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 { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
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 { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined, useIcons } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
export const StyledInput = styled.input` export const StyledInput = styled.input`
background: transparent; background: transparent;
@ -50,15 +46,7 @@ export const StyledInput = styled.input`
`; `;
export const ObjectFilterDropdownFilterSelect = () => { export const ObjectFilterDropdownFilterSelect = () => {
const [subMenuFieldType, setSubMenuFieldType] =
useState<CompositeFilterableFieldType | null>(null);
const [firstLevelFilterDefinition, setFirstLevelFilterDefinition] =
useState<FilterDefinition | null>(null);
const { const {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
objectFilterDropdownSearchInputState, objectFilterDropdownSearchInputState,
} = useFilterDropdown(); } = useFilterDropdown();
@ -70,16 +58,41 @@ export const ObjectFilterDropdownFilterSelect = () => {
const availableFilterDefinitions = useRecoilComponentValueV2( const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState, availableFilterDefinitionsComponentState,
); );
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
const { hiddenTableColumnsSelector, visibleTableColumnsSelector } =
useRecordTableStates(recordIndexId);
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions] const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
.sort((a, b) => a.label.localeCompare(b.label)) const visibleColumnsIds = visibleTableColumns.map(
.filter((item) => (column) => column.fieldMetadataId,
);
const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector());
const hiddenColumnIds = hiddenTableColumns.map(
(column) => column.fieldMetadataId,
);
const filteredSearchInputFilterDefinitions =
availableFilterDefinitions.filter((item) =>
item.label item.label
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()), .includes(objectFilterDropdownSearchInput.toLocaleLowerCase()),
); );
const selectableListItemIds = sortedAvailableFilterDefinitions.map( const visibleColumnsFilterDefinitions = filteredSearchInputFilterDefinitions
.sort((a, b) => {
return (
visibleColumnsIds.indexOf(a.fieldMetadataId) -
visibleColumnsIds.indexOf(b.fieldMetadataId)
);
})
.filter((item) => visibleColumnsIds.includes(item.fieldMetadataId));
const hiddenColumnsFilterDefinitions = filteredSearchInputFilterDefinitions
.sort((a, b) => a.label.localeCompare(b.label))
.filter((item) => hiddenColumnIds.includes(item.fieldMetadataId));
const selectableListItemIds = availableFilterDefinitions.map(
(item) => item.fieldMetadataId, (item) => item.fieldMetadataId,
); );
@ -88,7 +101,7 @@ export const ObjectFilterDropdownFilterSelect = () => {
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID); const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
const handleEnter = (itemId: string) => { const handleEnter = (itemId: string) => {
const selectedFilterDefinition = sortedAvailableFilterDefinitions.find( const selectedFilterDefinition = availableFilterDefinitions.find(
(item) => item.fieldMetadataId === itemId, (item) => item.fieldMetadataId === itemId,
); );
@ -101,96 +114,56 @@ export const ObjectFilterDropdownFilterSelect = () => {
selectFilter({ filterDefinition: selectedFilterDefinition }); selectFilter({ filterDefinition: selectedFilterDefinition });
}; };
const setHotkeyScope = useSetHotkeyScope(); const shoudShowSeparator =
const { getIcon } = useIcons(); visibleColumnsFilterDefinitions.length > 0 &&
hiddenColumnsFilterDefinitions.length > 0;
const handleSelectFilter = (availableFilterDefinition: FilterDefinition) => {
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
if (
availableFilterDefinition.type === 'RELATION' ||
availableFilterDefinition.type === 'SELECT'
) {
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterDefinition(availableFilterDefinition)[0],
);
setObjectFilterDropdownSearchInput('');
};
const handleSubMenuBack = () => {
setSubMenuFieldType(null);
setFirstLevelFilterDefinition(null);
};
const shouldShowFirstLevelMenu = !isDefined(subMenuFieldType);
return ( return (
<> <>
{shouldShowFirstLevelMenu ? ( <StyledInput
<> value={objectFilterDropdownSearchInput}
<StyledInput autoFocus
value={objectFilterDropdownSearchInput} placeholder="Search fields"
autoFocus onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
placeholder="Search fields" setObjectFilterDropdownSearchInput(event.target.value)
onChange={(event: React.ChangeEvent<HTMLInputElement>) => }
setObjectFilterDropdownSearchInput(event.target.value) />
} <SelectableList
/> hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
<SelectableList selectableItemIdArray={selectableListItemIds}
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton} selectableListId={OBJECT_FILTER_DROPDOWN_ID}
selectableItemIdArray={selectableListItemIds} onEnter={handleEnter}
selectableListId={OBJECT_FILTER_DROPDOWN_ID} >
onEnter={handleEnter} <DropdownMenuItemsContainer>
> {visibleColumnsFilterDefinitions.map(
<DropdownMenuItemsContainer> (visibleFilterDefinition, index) => (
{[...availableFilterDefinitions] <SelectableItem
.sort((a, b) => a.label.localeCompare(b.label)) itemId={visibleFilterDefinition.fieldMetadataId}
.filter((item) => key={`visible-select-filter-${index}`}
item.label >
.toLocaleLowerCase() <ObjectFilterDropdownFilterSelectMenuItem
.includes( filterDefinition={visibleFilterDefinition}
objectFilterDropdownSearchInput.toLocaleLowerCase(), />
), </SelectableItem>
) ),
.map((availableFilterDefinition, index) => ( )}
<SelectableItem </DropdownMenuItemsContainer>
itemId={availableFilterDefinition.fieldMetadataId} {shoudShowSeparator && <DropdownMenuSeparator />}
> <DropdownMenuItemsContainer>
<MenuItem {hiddenColumnsFilterDefinitions.map(
key={`select-filter-${index}`} (hiddenFilterDefinition, index) => (
testId={`select-filter-${index}`} <SelectableItem
onClick={() => { itemId={hiddenFilterDefinition.fieldMetadataId}
if (isCompositeField(availableFilterDefinition.type)) { key={`hidden-select-filter-${index}`}
setSubMenuFieldType(availableFilterDefinition.type); >
setFirstLevelFilterDefinition( <ObjectFilterDropdownFilterSelectMenuItem
availableFilterDefinition, filterDefinition={hiddenFilterDefinition}
); />
} else { </SelectableItem>
handleSelectFilter(availableFilterDefinition); ),
} )}
}} </DropdownMenuItemsContainer>
LeftIcon={getIcon(availableFilterDefinition.iconName)} </SelectableList>
text={availableFilterDefinition.label}
hasSubMenu={isCompositeField(
availableFilterDefinition.type,
)}
/>
</SelectableItem>
))}
</DropdownMenuItemsContainer>
</SelectableList>
</>
) : (
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu
fieldType={subMenuFieldType}
firstLevelFieldDefinition={firstLevelFilterDefinition}
onBack={handleSubMenuBack}
/>
)}
</> </>
); );
}; };

View File

@ -1,6 +1,8 @@
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect'; 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 { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState';
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';
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
@ -9,24 +11,33 @@ import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/con
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useState } from 'react'; import { useState } from 'react';
import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui'; import { IconApps, IconChevronLeft, isDefined, useIcons } from 'twenty-ui';
type ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps = { export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
fieldType: CompositeFilterableFieldType;
firstLevelFieldDefinition: FilterDefinition | null;
onBack: () => void;
};
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
fieldType,
firstLevelFieldDefinition,
onBack,
}: ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps) => {
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const [
objectFilterDropdownFirstLevelFilterDefinition,
setObjectFilterDropdownFirstLevelFilterDefinition,
] = useRecoilComponentStateV2(
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
);
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
);
const [
objectFilterDropdownSubMenuFieldType,
setObjectFilterDropdownSubMenuFieldType,
] = useRecoilComponentStateV2(
objectFilterDropdownSubMenuFieldTypeComponentState,
);
const { const {
setFilterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
@ -42,11 +53,26 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
); );
setObjectFilterDropdownSearchInput(''); setObjectFilterDropdownSearchInput('');
setObjectFilterDropdownFilterIsSelected(true);
} }
}; };
const handleSubMenuBack = () => {
setFilterDefinitionUsedInDropdown(null);
setObjectFilterDropdownSubMenuFieldType(null);
setObjectFilterDropdownFirstLevelFilterDefinition(null);
};
if (
!isDefined(objectFilterDropdownSubMenuFieldType) ||
!isDefined(objectFilterDropdownFirstLevelFilterDefinition)
) {
return null;
}
const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[ const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
fieldType objectFilterDropdownSubMenuFieldType
].filterableSubFields ].filterableSubFields
.sort((a, b) => a.localeCompare(b)) .sort((a, b) => a.localeCompare(b))
.filter((item) => .filter((item) =>
@ -55,8 +81,11 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
return ( return (
<> <>
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={onBack}> <DropdownMenuHeader
{getFilterableFieldTypeLabel(fieldType)} StartIcon={IconChevronLeft}
onClick={handleSubMenuBack}
>
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
</DropdownMenuHeader> </DropdownMenuHeader>
<StyledInput <StyledInput
value={searchText} value={searchText}
@ -71,25 +100,34 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
key={`select-filter-${-1}`} key={`select-filter-${-1}`}
testId={`select-filter-${-1}`} testId={`select-filter-${-1}`}
onClick={() => { onClick={() => {
handleSelectFilter(firstLevelFieldDefinition); handleSelectFilter(objectFilterDropdownFirstLevelFilterDefinition);
}} }}
LeftIcon={IconApps} LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(fieldType)} field`} text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
/> />
{options.map((subFieldName, index) => ( {options.map((subFieldName, index) => (
<MenuItem <MenuItem
key={`select-filter-${index}`} key={`select-filter-${index}`}
testId={`select-filter-${index}`} testId={`select-filter-${index}`}
onClick={() => onClick={() => {
firstLevelFieldDefinition && if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) {
handleSelectFilter({ handleSelectFilter({
...firstLevelFieldDefinition, ...objectFilterDropdownFirstLevelFilterDefinition,
label: getCompositeSubFieldLabel(fieldType, subFieldName), label: getCompositeSubFieldLabel(
compositeFieldName: subFieldName, objectFilterDropdownSubMenuFieldType,
}) subFieldName,
} ),
text={getCompositeSubFieldLabel(fieldType, subFieldName)} compositeFieldName: subFieldName,
LeftIcon={getIcon(firstLevelFieldDefinition?.iconName)} });
}
}}
text={getCompositeSubFieldLabel(
objectFilterDropdownSubMenuFieldType,
subFieldName,
)}
LeftIcon={getIcon(
objectFilterDropdownFirstLevelFilterDefinition?.iconName,
)}
/> />
))} ))}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>

View File

@ -1,9 +1,20 @@
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 { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
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 { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
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 { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui'; import { useIcons } from 'twenty-ui';
@ -16,6 +27,24 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
}: ObjectFilterDropdownFilterSelectMenuItemProps) => { }: ObjectFilterDropdownFilterSelectMenuItemProps) => {
const { selectFilter } = useSelectFilter(); const { selectFilter } = useSelectFilter();
const [, setObjectFilterDropdownFirstLevelFilterDefinition] =
useRecoilComponentStateV2(
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
);
const [, setObjectFilterDropdownSubMenuFieldType] = useRecoilComponentStateV2(
objectFilterDropdownSubMenuFieldTypeComponentState,
);
const [, setObjectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
);
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList(
OBJECT_FILTER_DROPDOWN_ID, OBJECT_FILTER_DROPDOWN_ID,
); );
@ -24,12 +53,52 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
isSelectedItemIdSelector(filterDefinition.fieldMetadataId), isSelectedItemIdSelector(filterDefinition.fieldMetadataId),
); );
const isACompositeField = isCompositeField(filterDefinition.type);
const {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setObjectFilterDropdownSearchInput,
} = useFilterDropdown();
const setHotkeyScope = useSetHotkeyScope();
const handleSelectFilter = (availableFilterDefinition: FilterDefinition) => {
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
if (
availableFilterDefinition.type === 'RELATION' ||
availableFilterDefinition.type === 'SELECT'
) {
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterDefinition(availableFilterDefinition)[0],
);
setObjectFilterDropdownSearchInput('');
setObjectFilterDropdownFilterIsSelected(true);
};
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const handleClick = () => { const handleClick = () => {
resetSelectedItem(); resetSelectedItem();
selectFilter({ filterDefinition }); selectFilter({ filterDefinition });
if (isACompositeField) {
// TODO: create isCompositeFilterableFieldType type guard
setObjectFilterDropdownSubMenuFieldType(
filterDefinition.type as CompositeFilterableFieldType,
);
setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition);
setObjectFilterDropdownIsSelectingCompositeField(true);
} else {
handleSelectFilter(filterDefinition);
}
}; };
return ( return (
@ -39,6 +108,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
onClick={handleClick} onClick={handleClick}
LeftIcon={getIcon(filterDefinition.iconName)} LeftIcon={getIcon(filterDefinition.iconName)}
text={filterDefinition.label} text={filterDefinition.label}
hasSubMenu={isACompositeField}
/> />
); );
}; };

View File

@ -3,10 +3,15 @@ import { Meta, StoryObj } from '@storybook/react';
import { TaskGroups } from '@/activities/tasks/components/TaskGroups'; import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton'; import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { within } from '@storybook/test'; import { within } from '@storybook/test';
import { useSetRecoilState } from 'recoil';
import { ComponentDecorator } from 'twenty-ui'; import { ComponentDecorator } from 'twenty-ui';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
@ -25,6 +30,43 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
instanceId, instanceId,
); );
const { tableColumnsState } = useRecordTableStates(instanceId);
const setTableColumns = useSetRecoilState(tableColumnsState);
setTableColumns([
{
fieldMetadataId: '1',
iconName: 'IconUser',
label: 'Text',
type: FieldMetadataType.Text,
isVisible: true,
metadata: {
fieldName: 'text',
},
} as ColumnDefinition<any>,
{
fieldMetadataId: '3',
iconName: 'IconNumber',
label: 'Number',
type: FieldMetadataType.Number,
isVisible: true,
metadata: {
fieldName: 'number',
},
} as ColumnDefinition<any>,
{
fieldMetadataId: '4',
iconName: 'IconCalendar',
label: 'Date',
type: FieldMetadataType.DateTime,
isVisible: true,
metadata: {
fieldName: 'date',
},
} as ColumnDefinition<any>,
]);
setAvailableFilterDefinitions([ setAvailableFilterDefinitions([
{ {
fieldMetadataId: '1', fieldMetadataId: '1',
@ -32,12 +74,6 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
label: 'Text', label: 'Text',
type: FieldMetadataType.Text, type: FieldMetadataType.Text,
}, },
{
fieldMetadataId: '2',
iconName: 'Icon123',
label: 'Email',
type: FieldMetadataType.Emails,
},
{ {
fieldMetadataId: '3', fieldMetadataId: '3',
iconName: 'IconNumber', iconName: 'IconNumber',
@ -52,11 +88,19 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
}, },
]); ]);
return ( return (
<ViewComponentInstanceContext.Provider value={{ instanceId }}> <ObjectFilterDropdownComponentInstanceContext.Provider
<ObjectFilterDropdownScope filterScopeId={instanceId}> value={{ instanceId }}
<Story /> >
</ObjectFilterDropdownScope> <RecordTableScopeInternalContext.Provider
</ViewComponentInstanceContext.Provider> value={{ scopeId: instanceId, onColumnsChange: () => {} }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<ObjectFilterDropdownScope filterScopeId={instanceId}>
<Story />
</ObjectFilterDropdownScope>
</ViewComponentInstanceContext.Provider>
</RecordTableScopeInternalContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
); );
}, },
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,

View File

@ -4,6 +4,9 @@ import { useFilterDropdownStates } from '@/object-record/object-filter-dropdown/
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { ObjectFilterDropdownScopeInternalContext } from '../scopes/scope-internal-context/ObjectFilterDropdownScopeInternalContext'; import { ObjectFilterDropdownScopeInternalContext } from '../scopes/scope-internal-context/ObjectFilterDropdownScopeInternalContext';
import { Filter } from '../types/Filter'; import { Filter } from '../types/Filter';
@ -54,6 +57,18 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
], ],
); );
const setObjectFilterDropdownFilterIsSelectedCallbackState =
useRecoilComponentCallbackStateV2(
objectFilterDropdownFilterIsSelectedComponentState,
props?.filterDropdownId,
);
const setObjectFilterDropdownIsSelectingCompositeFieldCallbackState =
useRecoilComponentCallbackStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
props?.filterDropdownId,
);
const resetFilter = useRecoilCallback( const resetFilter = useRecoilCallback(
({ set }) => ({ set }) =>
() => { () => {
@ -62,6 +77,11 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
set(selectedFilterState, undefined); set(selectedFilterState, undefined);
set(filterDefinitionUsedInDropdownState, null); set(filterDefinitionUsedInDropdownState, null);
set(selectedOperandInDropdownState, null); set(selectedOperandInDropdownState, null);
set(setObjectFilterDropdownFilterIsSelectedCallbackState, false);
set(
setObjectFilterDropdownIsSelectingCompositeFieldCallbackState,
false,
);
}, },
[ [
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
@ -69,6 +89,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
objectFilterDropdownSelectedRecordIdsState, objectFilterDropdownSelectedRecordIdsState,
selectedFilterState, selectedFilterState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
setObjectFilterDropdownFilterIsSelectedCallbackState,
setObjectFilterDropdownIsSelectingCompositeFieldCallbackState,
], ],
); );

View File

@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const ObjectFilterDropdownComponentInstanceContext =
createComponentInstanceContext();

View File

@ -0,0 +1,9 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const objectFilterDropdownFilterIsSelectedComponentState =
createComponentStateV2<boolean>({
key: 'objectFilterDropdownFilterIsSelectedComponentState',
defaultValue: false,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});

View File

@ -0,0 +1,10 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const objectFilterDropdownFirstLevelFilterDefinitionComponentState =
createComponentStateV2<FilterDefinition | null>({
key: 'objectFilterDropdownFirstLevelFilterDefinitionComponentState',
defaultValue: null,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});

View File

@ -0,0 +1,9 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const objectFilterDropdownIsSelectingCompositeFieldComponentState =
createComponentStateV2<boolean>({
key: 'objectFilterDropdownIsSelectingCompositeFieldComponentState',
defaultValue: false,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});

View File

@ -0,0 +1,10 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const objectFilterDropdownSubMenuFieldTypeComponentState =
createComponentStateV2<CompositeFilterableFieldType | null>({
key: 'objectFilterDropdownSubMenuFieldTypeComponentState',
defaultValue: null,
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
});

View File

@ -5,14 +5,17 @@ import { IconChevronDown, useIcons } from 'twenty-ui';
import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId'; import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId';
import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown'; import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown';
import { ObjectSortDropdownScope } from '@/object-record/object-sort-dropdown/scopes/ObjectSortDropdownScope'; import { ObjectSortDropdownScope } from '@/object-record/object-sort-dropdown/scopes/ObjectSortDropdownScope';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useContext } from 'react';
import { SORT_DIRECTIONS } from '../types/SortDirection'; import { SORT_DIRECTIONS } from '../types/SortDirection';
export const StyledInput = styled.input` export const StyledInput = styled.input`
@ -94,6 +97,43 @@ export const ObjectSortDropdownButton = ({
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
const { hiddenTableColumnsSelector, visibleTableColumnsSelector } =
useRecordTableStates(recordIndexId);
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
const visibleColumnsIds = visibleTableColumns.map(
(column) => column.fieldMetadataId,
);
const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector());
const hiddenColumnIds = hiddenTableColumns.map(
(column) => column.fieldMetadataId,
);
const filteredSearchInputSortDefinitions = availableSortDefinitions.filter(
(item) =>
item.label
.toLocaleLowerCase()
.includes(objectSortDropdownSearchInput.toLocaleLowerCase()),
);
const visibleColumnsSortDefinitions = filteredSearchInputSortDefinitions
.sort((a, b) => {
return (
visibleColumnsIds.indexOf(a.fieldMetadataId) -
visibleColumnsIds.indexOf(b.fieldMetadataId)
);
})
.filter((item) => visibleColumnsIds.includes(item.fieldMetadataId));
const hiddenColumnsSortDefinitions = filteredSearchInputSortDefinitions
.sort((a, b) => a.label.localeCompare(b.label))
.filter((item) => hiddenColumnIds.includes(item.fieldMetadataId));
const shoudShowSeparator =
visibleColumnsSortDefinitions.length > 0 &&
hiddenColumnsSortDefinitions.length > 0;
return ( return (
<ObjectSortDropdownScope sortScopeId={sortDropdownId}> <ObjectSortDropdownScope sortScopeId={sortDropdownId}>
<Dropdown <Dropdown
@ -142,27 +182,37 @@ export const ObjectSortDropdownButton = ({
} }
/> />
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{[...availableSortDefinitions] {visibleColumnsSortDefinitions.map(
.sort((a, b) => a.label.localeCompare(b.label)) (visibleSortDefinition, index) => (
.filter((item) =>
item.label
.toLocaleLowerCase()
.includes(
objectSortDropdownSearchInput.toLocaleLowerCase(),
),
)
.map((availableSortDefinition, index) => (
<MenuItem <MenuItem
testId={`select-sort-${index}`} testId={`visible-select-sort-${index}`}
key={index} key={index}
onClick={() => { onClick={() => {
setObjectSortDropdownSearchInput(''); setObjectSortDropdownSearchInput('');
handleAddSort(availableSortDefinition); handleAddSort(visibleSortDefinition);
}} }}
LeftIcon={getIcon(availableSortDefinition.iconName)} LeftIcon={getIcon(visibleSortDefinition.iconName)}
text={availableSortDefinition.label} text={visibleSortDefinition.label}
/> />
))} ),
)}
</DropdownMenuItemsContainer>
{shoudShowSeparator && <DropdownMenuSeparator />}
<DropdownMenuItemsContainer>
{hiddenColumnsSortDefinitions.map(
(hiddenSortDefinition, index) => (
<MenuItem
testId={`hidden-select-sort-${index}`}
key={index}
onClick={() => {
setObjectSortDropdownSearchInput('');
handleAddSort(hiddenSortDefinition);
}}
LeftIcon={getIcon(hiddenSortDefinition.iconName)}
text={hiddenSortDefinition.label}
/>
),
)}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</StyledContainer> </StyledContainer>
</> </>