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:
@ -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 { 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 { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
|
||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||
import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInput';
|
||||
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
|
||||
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
|
||||
import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
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 = {
|
||||
filterDropdownId?: string;
|
||||
};
|
||||
@ -40,99 +21,38 @@ type MultipleFiltersDropdownContentProps = {
|
||||
export const MultipleFiltersDropdownContent = ({
|
||||
filterDropdownId,
|
||||
}: MultipleFiltersDropdownContentProps) => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdownState,
|
||||
selectedOperandInDropdownState,
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
} = useFilterDropdown({ filterDropdownId });
|
||||
const { filterDefinitionUsedInDropdownState } = useFilterDropdown({
|
||||
filterDropdownId,
|
||||
});
|
||||
|
||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
const [objectFilterDropdownIsSelectingCompositeField] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||
filterDefinitionUsedInDropdownState,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilValue(
|
||||
selectedOperandInDropdownState,
|
||||
);
|
||||
const shouldShowCompositeSelectionSubMenu =
|
||||
objectFilterDropdownIsSelectingCompositeField;
|
||||
|
||||
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);
|
||||
const shoudShowFilterInput = objectFilterDropdownFilterIsSelected;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{!filterDefinitionUsedInDropdown ? (
|
||||
<ObjectFilterDropdownFilterSelect />
|
||||
{shoudShowFilterInput ? (
|
||||
<ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} />
|
||||
) : shouldShowCompositeSelectionSubMenu ? (
|
||||
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
|
||||
) : (
|
||||
<>
|
||||
<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 />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
<ObjectFilterDropdownFilterSelect />
|
||||
)}
|
||||
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
||||
filterDefinitionUsedInDropdownType={
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
|
||||
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 { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton';
|
||||
@ -29,12 +30,16 @@ export const ObjectFilterDropdownButton = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<ObjectFilterDropdownScope filterScopeId={filterDropdownId}>
|
||||
{hasOnlyOneEntityFilter ? (
|
||||
<SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} />
|
||||
) : (
|
||||
<MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} />
|
||||
)}
|
||||
</ObjectFilterDropdownScope>
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: filterDropdownId }}
|
||||
>
|
||||
<ObjectFilterDropdownScope filterScopeId={filterDropdownId}>
|
||||
{hasOnlyOneEntityFilter ? (
|
||||
<SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} />
|
||||
) : (
|
||||
<MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} />
|
||||
)}
|
||||
</ObjectFilterDropdownScope>
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,27 +1,23 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { useContext } from 'react';
|
||||
|
||||
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 { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
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 { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||
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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
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 { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined, useIcons } from 'twenty-ui';
|
||||
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
@ -50,15 +46,7 @@ export const StyledInput = styled.input`
|
||||
`;
|
||||
|
||||
export const ObjectFilterDropdownFilterSelect = () => {
|
||||
const [subMenuFieldType, setSubMenuFieldType] =
|
||||
useState<CompositeFilterableFieldType | null>(null);
|
||||
|
||||
const [firstLevelFilterDefinition, setFirstLevelFilterDefinition] =
|
||||
useState<FilterDefinition | null>(null);
|
||||
|
||||
const {
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setSelectedOperandInDropdown,
|
||||
setObjectFilterDropdownSearchInput,
|
||||
objectFilterDropdownSearchInputState,
|
||||
} = useFilterDropdown();
|
||||
@ -70,16 +58,41 @@ export const ObjectFilterDropdownFilterSelect = () => {
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
|
||||
const { hiddenTableColumnsSelector, visibleTableColumnsSelector } =
|
||||
useRecordTableStates(recordIndexId);
|
||||
|
||||
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.filter((item) =>
|
||||
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
|
||||
const visibleColumnsIds = visibleTableColumns.map(
|
||||
(column) => column.fieldMetadataId,
|
||||
);
|
||||
const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector());
|
||||
const hiddenColumnIds = hiddenTableColumns.map(
|
||||
(column) => column.fieldMetadataId,
|
||||
);
|
||||
|
||||
const filteredSearchInputFilterDefinitions =
|
||||
availableFilterDefinitions.filter((item) =>
|
||||
item.label
|
||||
.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,
|
||||
);
|
||||
|
||||
@ -88,7 +101,7 @@ export const ObjectFilterDropdownFilterSelect = () => {
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const handleEnter = (itemId: string) => {
|
||||
const selectedFilterDefinition = sortedAvailableFilterDefinitions.find(
|
||||
const selectedFilterDefinition = availableFilterDefinitions.find(
|
||||
(item) => item.fieldMetadataId === itemId,
|
||||
);
|
||||
|
||||
@ -101,96 +114,56 @@ export const ObjectFilterDropdownFilterSelect = () => {
|
||||
selectFilter({ filterDefinition: selectedFilterDefinition });
|
||||
};
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
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);
|
||||
const shoudShowSeparator =
|
||||
visibleColumnsFilterDefinitions.length > 0 &&
|
||||
hiddenColumnsFilterDefinitions.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldShowFirstLevelMenu ? (
|
||||
<>
|
||||
<StyledInput
|
||||
value={objectFilterDropdownSearchInput}
|
||||
autoFocus
|
||||
placeholder="Search fields"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setObjectFilterDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableListItemIds}
|
||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{[...availableFilterDefinitions]
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.filter((item) =>
|
||||
item.label
|
||||
.toLocaleLowerCase()
|
||||
.includes(
|
||||
objectFilterDropdownSearchInput.toLocaleLowerCase(),
|
||||
),
|
||||
)
|
||||
.map((availableFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={availableFilterDefinition.fieldMetadataId}
|
||||
>
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
if (isCompositeField(availableFilterDefinition.type)) {
|
||||
setSubMenuFieldType(availableFilterDefinition.type);
|
||||
setFirstLevelFilterDefinition(
|
||||
availableFilterDefinition,
|
||||
);
|
||||
} else {
|
||||
handleSelectFilter(availableFilterDefinition);
|
||||
}
|
||||
}}
|
||||
LeftIcon={getIcon(availableFilterDefinition.iconName)}
|
||||
text={availableFilterDefinition.label}
|
||||
hasSubMenu={isCompositeField(
|
||||
availableFilterDefinition.type,
|
||||
)}
|
||||
/>
|
||||
</SelectableItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
) : (
|
||||
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu
|
||||
fieldType={subMenuFieldType}
|
||||
firstLevelFieldDefinition={firstLevelFilterDefinition}
|
||||
onBack={handleSubMenuBack}
|
||||
/>
|
||||
)}
|
||||
<StyledInput
|
||||
value={objectFilterDropdownSearchInput}
|
||||
autoFocus
|
||||
placeholder="Search fields"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setObjectFilterDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableListItemIds}
|
||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFilterDefinitions.map(
|
||||
(visibleFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={visibleFilterDefinition.fieldMetadataId}
|
||||
key={`visible-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={visibleFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
{shoudShowSeparator && <DropdownMenuSeparator />}
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenColumnsFilterDefinitions.map(
|
||||
(hiddenFilterDefinition, index) => (
|
||||
<SelectableItem
|
||||
itemId={hiddenFilterDefinition.fieldMetadataId}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
filterDefinition={hiddenFilterDefinition}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
|
||||
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 { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
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 { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useState } from 'react';
|
||||
import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui';
|
||||
import { IconApps, IconChevronLeft, isDefined, useIcons } from 'twenty-ui';
|
||||
|
||||
type ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps = {
|
||||
fieldType: CompositeFilterableFieldType;
|
||||
firstLevelFieldDefinition: FilterDefinition | null;
|
||||
onBack: () => void;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
|
||||
fieldType,
|
||||
firstLevelFieldDefinition,
|
||||
onBack,
|
||||
}: ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps) => {
|
||||
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const [
|
||||
objectFilterDropdownFirstLevelFilterDefinition,
|
||||
setObjectFilterDropdownFirstLevelFilterDefinition,
|
||||
] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const [
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
setObjectFilterDropdownSubMenuFieldType,
|
||||
] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownSubMenuFieldTypeComponentState,
|
||||
);
|
||||
|
||||
const {
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setSelectedOperandInDropdown,
|
||||
@ -42,11 +53,26 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
|
||||
);
|
||||
|
||||
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[
|
||||
fieldType
|
||||
objectFilterDropdownSubMenuFieldType
|
||||
].filterableSubFields
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.filter((item) =>
|
||||
@ -55,8 +81,11 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={onBack}>
|
||||
{getFilterableFieldTypeLabel(fieldType)}
|
||||
<DropdownMenuHeader
|
||||
StartIcon={IconChevronLeft}
|
||||
onClick={handleSubMenuBack}
|
||||
>
|
||||
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
|
||||
</DropdownMenuHeader>
|
||||
<StyledInput
|
||||
value={searchText}
|
||||
@ -71,25 +100,34 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(firstLevelFieldDefinition);
|
||||
handleSelectFilter(objectFilterDropdownFirstLevelFilterDefinition);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(fieldType)} field`}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{options.map((subFieldName, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() =>
|
||||
firstLevelFieldDefinition &&
|
||||
handleSelectFilter({
|
||||
...firstLevelFieldDefinition,
|
||||
label: getCompositeSubFieldLabel(fieldType, subFieldName),
|
||||
compositeFieldName: subFieldName,
|
||||
})
|
||||
}
|
||||
text={getCompositeSubFieldLabel(fieldType, subFieldName)}
|
||||
LeftIcon={getIcon(firstLevelFieldDefinition?.iconName)}
|
||||
onClick={() => {
|
||||
if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) {
|
||||
handleSelectFilter({
|
||||
...objectFilterDropdownFirstLevelFilterDefinition,
|
||||
label: getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
),
|
||||
compositeFieldName: subFieldName,
|
||||
});
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(
|
||||
objectFilterDropdownFirstLevelFilterDefinition?.iconName,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
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 { 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 { 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 { 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 { useIcons } from 'twenty-ui';
|
||||
|
||||
@ -16,6 +27,24 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
}: ObjectFilterDropdownFilterSelectMenuItemProps) => {
|
||||
const { selectFilter } = useSelectFilter();
|
||||
|
||||
const [, setObjectFilterDropdownFirstLevelFilterDefinition] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownSubMenuFieldType] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownSubMenuFieldTypeComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownIsSelectingCompositeField] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList(
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
@ -24,12 +53,52 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
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 handleClick = () => {
|
||||
resetSelectedItem();
|
||||
|
||||
selectFilter({ filterDefinition });
|
||||
|
||||
if (isACompositeField) {
|
||||
// TODO: create isCompositeFilterableFieldType type guard
|
||||
setObjectFilterDropdownSubMenuFieldType(
|
||||
filterDefinition.type as CompositeFilterableFieldType,
|
||||
);
|
||||
setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition);
|
||||
setObjectFilterDropdownIsSelectingCompositeField(true);
|
||||
} else {
|
||||
handleSelectFilter(filterDefinition);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -39,6 +108,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
onClick={handleClick}
|
||||
LeftIcon={getIcon(filterDefinition.iconName)}
|
||||
text={filterDefinition.label}
|
||||
hasSubMenu={isACompositeField}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,10 +3,15 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
|
||||
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
|
||||
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 { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { within } from '@storybook/test';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
@ -25,6 +30,43 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
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([
|
||||
{
|
||||
fieldMetadataId: '1',
|
||||
@ -32,12 +74,6 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
label: 'Text',
|
||||
type: FieldMetadataType.Text,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: '2',
|
||||
iconName: 'Icon123',
|
||||
label: 'Email',
|
||||
type: FieldMetadataType.Emails,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: '3',
|
||||
iconName: 'IconNumber',
|
||||
@ -52,11 +88,19 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
|
||||
<ObjectFilterDropdownScope filterScopeId={instanceId}>
|
||||
<Story />
|
||||
</ObjectFilterDropdownScope>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<RecordTableScopeInternalContext.Provider
|
||||
value={{ scopeId: instanceId, onColumnsChange: () => {} }}
|
||||
>
|
||||
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
|
||||
<ObjectFilterDropdownScope filterScopeId={instanceId}>
|
||||
<Story />
|
||||
</ObjectFilterDropdownScope>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordTableScopeInternalContext.Provider>
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
);
|
||||
},
|
||||
ObjectMetadataItemsDecorator,
|
||||
|
||||
@ -4,6 +4,9 @@ import { useFilterDropdownStates } from '@/object-record/object-filter-dropdown/
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
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 { 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(
|
||||
({ set }) =>
|
||||
() => {
|
||||
@ -62,6 +77,11 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
set(selectedFilterState, undefined);
|
||||
set(filterDefinitionUsedInDropdownState, null);
|
||||
set(selectedOperandInDropdownState, null);
|
||||
set(setObjectFilterDropdownFilterIsSelectedCallbackState, false);
|
||||
set(
|
||||
setObjectFilterDropdownIsSelectingCompositeFieldCallbackState,
|
||||
false,
|
||||
);
|
||||
},
|
||||
[
|
||||
filterDefinitionUsedInDropdownState,
|
||||
@ -69,6 +89,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
objectFilterDropdownSelectedRecordIdsState,
|
||||
selectedFilterState,
|
||||
selectedOperandInDropdownState,
|
||||
setObjectFilterDropdownFilterIsSelectedCallbackState,
|
||||
setObjectFilterDropdownIsSelectingCompositeFieldCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const ObjectFilterDropdownComponentInstanceContext =
|
||||
createComponentInstanceContext();
|
||||
@ -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,
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
@ -5,14 +5,17 @@ import { IconChevronDown, useIcons } from 'twenty-ui';
|
||||
import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId';
|
||||
import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown';
|
||||
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 { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { SORT_DIRECTIONS } from '../types/SortDirection';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
@ -94,6 +97,43 @@ export const ObjectSortDropdownButton = ({
|
||||
|
||||
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 (
|
||||
<ObjectSortDropdownScope sortScopeId={sortDropdownId}>
|
||||
<Dropdown
|
||||
@ -142,27 +182,37 @@ export const ObjectSortDropdownButton = ({
|
||||
}
|
||||
/>
|
||||
<DropdownMenuItemsContainer>
|
||||
{[...availableSortDefinitions]
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.filter((item) =>
|
||||
item.label
|
||||
.toLocaleLowerCase()
|
||||
.includes(
|
||||
objectSortDropdownSearchInput.toLocaleLowerCase(),
|
||||
),
|
||||
)
|
||||
.map((availableSortDefinition, index) => (
|
||||
{visibleColumnsSortDefinitions.map(
|
||||
(visibleSortDefinition, index) => (
|
||||
<MenuItem
|
||||
testId={`select-sort-${index}`}
|
||||
testId={`visible-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setObjectSortDropdownSearchInput('');
|
||||
handleAddSort(availableSortDefinition);
|
||||
handleAddSort(visibleSortDefinition);
|
||||
}}
|
||||
LeftIcon={getIcon(availableSortDefinition.iconName)}
|
||||
text={availableSortDefinition.label}
|
||||
LeftIcon={getIcon(visibleSortDefinition.iconName)}
|
||||
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>
|
||||
</StyledContainer>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user