Refactored ObjectFilterDropdown into ViewBarFilterDropdown (#11794)
This PR refactors the non-generic part around ObjectFilterDropdown which has been left in statu quo for months. It also removes unused components. Overall this PR is doing renaming and it re-organizes files into their relevant modules. This clarifies a lot what's at the intersection between object-filter-dropdown and views modules. This PR was originally about removing any remaining useEffect around ObjectFilterDropdown but there wasn't any. ## Details ### Removed unused files - GenericEntityFilterChip - SingleEntityObjectFilterDropdownButton (was used for the Task/Note standalone page which doesn't exist anymore) ### Re-organized non-generic components into ViewBarFilterDropdown - Use VIEW_BAR_FILTER_DROPDOWN_ID instead of OBJECT_FILTER_DROPDOWN_ID - Use FILTER_FIELD_LIST_ID for selectable list - Refactored ObjectFilterDropdownButton into a simple ViewBarFilterDropdown - Renamed MultipleFiltersDropdownContent to ViewBarFilterDropdownContent - Renamed MultipleFiltersButton to ViewBarFilterButton - Integrated MultipleFiltersDropdownButton to ViewBarFilterDropdown - Renamed AdvancedFilterButton to ViewBarDetailsAddFilterButton ### Tests Fixed storybook test for ViewBarFilterDrodpown
This commit is contained in:
@ -1,33 +0,0 @@
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { LightButton } from 'twenty-ui/input';
|
||||
|
||||
type AddObjectFilterFromDetailsButtonProps = {
|
||||
filterDropdownId?: string;
|
||||
};
|
||||
|
||||
export const AddObjectFilterFromDetailsButton = ({
|
||||
filterDropdownId,
|
||||
}: AddObjectFilterFromDetailsButtonProps) => {
|
||||
const { toggleDropdown } = useDropdown(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const { resetFilterDropdown } = useResetFilterDropdown(filterDropdownId);
|
||||
|
||||
const handleClick = () => {
|
||||
resetFilterDropdown();
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<LightButton
|
||||
onClick={handleClick}
|
||||
Icon={IconPlus}
|
||||
title={t`Add filter`}
|
||||
accent="tertiary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,142 +0,0 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
||||
|
||||
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
|
||||
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
|
||||
import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Pill } from 'twenty-ui/components';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
import { MenuItemLeftContent, StyledMenuItemBase } from 'twenty-ui/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
export const StyledMenuItemSelect = styled(StyledMenuItemBase)`
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledPill = styled(Pill)`
|
||||
background: ${({ theme }) => theme.color.blueAccent10};
|
||||
color: ${({ theme }) => theme.color.blue};
|
||||
`;
|
||||
|
||||
export const AdvancedFilterButton = () => {
|
||||
const advancedFilterQuerySubFilterCount = 0; // TODO
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const { openDropdown: openAdvancedFilterDropdown } = useDropdown(
|
||||
ADVANCED_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { closeDropdown: closeObjectFilterDropdown } = useDropdown(
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup();
|
||||
|
||||
const { upsertRecordFilter } = useUpsertRecordFilter();
|
||||
|
||||
const objectMetadataId = currentView?.objectMetadataId;
|
||||
|
||||
if (!objectMetadataId) {
|
||||
throw new Error('Object metadata id is missing from current view');
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: objectMetadataId ?? null,
|
||||
});
|
||||
|
||||
const availableFieldMetadataItemsForFilter = useRecoilValue(
|
||||
availableFieldMetadataItemsForFilterFamilySelector({
|
||||
objectMetadataItemId: objectMetadataItem.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const currentRecordFilterGroups = useRecoilComponentValueV2(
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
|
||||
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
|
||||
|
||||
const { createEmptyRecordFilterFromFieldMetadataItem } =
|
||||
useCreateEmptyRecordFilterFromFieldMetadataItem();
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isDefined(currentView)) {
|
||||
throw new Error('Missing current view id');
|
||||
}
|
||||
|
||||
const alreadyHasAdvancedFilterGroup = currentRecordFilterGroups.length > 0;
|
||||
|
||||
if (!alreadyHasAdvancedFilterGroup) {
|
||||
const newRecordFilterGroup = {
|
||||
id: v4(),
|
||||
viewId: currentView.id,
|
||||
logicalOperator: RecordFilterGroupLogicalOperator.AND,
|
||||
};
|
||||
|
||||
upsertRecordFilterGroup(newRecordFilterGroup);
|
||||
|
||||
const defaultFieldMetadataItem =
|
||||
availableFieldMetadataItemsForFilter.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id ===
|
||||
objectMetadataItem?.labelIdentifierFieldMetadataId,
|
||||
) ?? availableFieldMetadataItemsForFilter[0];
|
||||
|
||||
if (!isDefined(defaultFieldMetadataItem)) {
|
||||
throw new Error('Missing default filter definition');
|
||||
}
|
||||
|
||||
const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem(
|
||||
defaultFieldMetadataItem,
|
||||
);
|
||||
|
||||
newRecordFilter.recordFilterGroupId = newRecordFilterGroup.id;
|
||||
|
||||
upsertRecordFilter(newRecordFilter);
|
||||
|
||||
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
|
||||
}
|
||||
|
||||
closeObjectFilterDropdown();
|
||||
openAdvancedFilterDropdown({
|
||||
scope: ADVANCED_FILTER_DROPDOWN_ID,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledMenuItemSelect onClick={handleClick}>
|
||||
<MenuItemLeftContent LeftIcon={IconFilter} text={t`Advanced filter`} />
|
||||
{advancedFilterQuerySubFilterCount > 0 && (
|
||||
<StyledPill label={advancedFilterQuerySubFilterCount.toString()} />
|
||||
)}
|
||||
</StyledMenuItemSelect>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import { RecordFilter } from '../../record-filter/types/RecordFilter';
|
||||
import { AvatarChip } from 'twenty-ui/components';
|
||||
import { IconComponent } from 'twenty-ui/display';
|
||||
|
||||
type GenericEntityFilterChipProps = {
|
||||
filter: RecordFilter;
|
||||
Icon?: IconComponent;
|
||||
};
|
||||
|
||||
export const GenericEntityFilterChip = ({
|
||||
filter,
|
||||
Icon,
|
||||
}: GenericEntityFilterChipProps) => (
|
||||
<AvatarChip
|
||||
placeholderColorSeed={filter.value}
|
||||
name={filter.displayValue}
|
||||
avatarType="rounded"
|
||||
avatarUrl={filter.displayAvatarUrl}
|
||||
LeftIcon={Icon}
|
||||
/>
|
||||
);
|
||||
@ -1,27 +0,0 @@
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const MultipleFiltersButton = () => {
|
||||
const { resetFilterDropdown } = useResetFilterDropdown();
|
||||
|
||||
const { toggleDropdown, isDropdownOpen } = useDropdown(
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
toggleDropdown();
|
||||
resetFilterDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledHeaderDropdownButton
|
||||
onClick={handleClick}
|
||||
isUnfolded={isDropdownOpen}
|
||||
>
|
||||
<Trans>Filter</Trans>
|
||||
</StyledHeaderDropdownButton>
|
||||
);
|
||||
};
|
||||
@ -1,54 +0,0 @@
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MultipleFiltersButton } from './MultipleFiltersButton';
|
||||
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
|
||||
|
||||
type MultipleFiltersDropdownButtonProps = {
|
||||
hotkeyScope: HotkeyScope;
|
||||
};
|
||||
|
||||
export const MultipleFiltersDropdownButton = ({
|
||||
hotkeyScope,
|
||||
}: MultipleFiltersDropdownButtonProps) => {
|
||||
const { resetFilterDropdown } = useResetFilterDropdown();
|
||||
|
||||
const { removeRecordFilter } = useRemoveRecordFilter();
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const handleDropdownClickOutside = () => {
|
||||
const recordFilterIsEmpty =
|
||||
isDefined(selectedFilter) &&
|
||||
isRecordFilterConsideredEmpty(selectedFilter);
|
||||
|
||||
if (recordFilterIsEmpty) {
|
||||
removeRecordFilter({ recordFilterId: selectedFilter.id });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdownClose = () => {
|
||||
resetFilterDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onClose={handleDropdownClose}
|
||||
clickableComponent={<MultipleFiltersButton />}
|
||||
dropdownComponents={<MultipleFiltersDropdownContent />}
|
||||
dropdownHotkeyScope={hotkeyScope}
|
||||
dropdownOffset={{ y: 8 }}
|
||||
onClickOutside={handleDropdownClickOutside}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,44 +0,0 @@
|
||||
import { ObjectFilterDropdownSubFieldSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSubFieldSelect';
|
||||
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
|
||||
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 { ObjectFilterDropdownFieldSelect } from './ObjectFilterDropdownFieldSelect';
|
||||
|
||||
type MultipleFiltersDropdownContentProps = {
|
||||
filterDropdownId?: string;
|
||||
};
|
||||
|
||||
export const MultipleFiltersDropdownContent = ({
|
||||
filterDropdownId,
|
||||
}: MultipleFiltersDropdownContentProps) => {
|
||||
const [objectFilterDropdownIsSelectingCompositeField] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const shouldShowCompositeSelectionSubMenu =
|
||||
objectFilterDropdownIsSelectingCompositeField;
|
||||
|
||||
const shouldShowFilterInput = objectFilterDropdownFilterIsSelected;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldShowFilterInput ? (
|
||||
<ObjectFilterOperandSelectAndInput
|
||||
filterDropdownId={filterDropdownId}
|
||||
/>
|
||||
) : shouldShowCompositeSelectionSubMenu ? (
|
||||
<ObjectFilterDropdownSubFieldSelect />
|
||||
) : (
|
||||
<ObjectFilterDropdownFieldSelect isAdvancedFilterButtonVisible />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,39 +0,0 @@
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton';
|
||||
import { SingleEntityObjectFilterDropdownButton } from './SingleEntityObjectFilterDropdownButton';
|
||||
|
||||
type ObjectFilterDropdownButtonProps = {
|
||||
filterDropdownId: string;
|
||||
hotkeyScope: HotkeyScope;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownButton = ({
|
||||
filterDropdownId,
|
||||
hotkeyScope,
|
||||
}: ObjectFilterDropdownButtonProps) => {
|
||||
const { filterableFieldMetadataItems } =
|
||||
useFilterableFieldMetadataItemsInRecordIndexContext();
|
||||
|
||||
const hasOnlyOneEntityFilter =
|
||||
filterableFieldMetadataItems.length === 1 &&
|
||||
filterableFieldMetadataItems[0].type === 'RELATION';
|
||||
|
||||
if (!filterableFieldMetadataItems.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: filterDropdownId }}
|
||||
>
|
||||
{hasOnlyOneEntityFilter ? (
|
||||
<SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} />
|
||||
) : (
|
||||
<MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} />
|
||||
)}
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -2,9 +2,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
import { AdvancedFilterButton } from '@/object-record/object-filter-dropdown/components/AdvancedFilterButton';
|
||||
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
|
||||
@ -14,12 +12,11 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
@ -47,13 +44,7 @@ export const StyledInput = styled.input`
|
||||
}
|
||||
`;
|
||||
|
||||
type ObjectFilterDropdownFieldSelectProps = {
|
||||
isAdvancedFilterButtonVisible?: boolean;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownFieldSelect = ({
|
||||
isAdvancedFilterButtonVisible,
|
||||
}: ObjectFilterDropdownFieldSelectProps) => {
|
||||
export const ObjectFilterDropdownFieldSelect = () => {
|
||||
const { recordIndexId } = useRecordIndexContextOrThrow();
|
||||
|
||||
const [objectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput] =
|
||||
@ -95,11 +86,6 @@ export const ObjectFilterDropdownFieldSelect = ({
|
||||
visibleColumnsFieldMetadataItems.length > 0 &&
|
||||
hiddenColumnsFieldMetadataItems.length > 0;
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
const shouldShowAdvancedFilterButton =
|
||||
isDefined(currentView?.objectMetadataId) && isAdvancedFilterButtonVisible;
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const selectableFieldMetadataItemIds = [
|
||||
@ -124,7 +110,7 @@ export const ObjectFilterDropdownFieldSelect = ({
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableFieldMetadataItemIds}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
selectableListInstanceId={FILTER_FIELD_LIST_ID}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map((visibleFieldMetadataItem) => (
|
||||
@ -142,7 +128,6 @@ export const ObjectFilterDropdownFieldSelect = ({
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
|
||||
@ -8,6 +7,7 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
@ -50,7 +50,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
const { resetSelectedItem } = useSelectableList(FILTER_FIELD_LIST_ID);
|
||||
|
||||
const isSelectedItem = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
@ -17,7 +16,7 @@ export const ObjectFilterDropdownFilterSelectMenuItemV2 = ({
|
||||
fieldMetadataItemToSelect,
|
||||
onClick,
|
||||
}: ObjectFilterDropdownFilterSelectMenuItemV2Props) => {
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
const { resetSelectedItem } = useSelectableList(FILTER_FIELD_LIST_ID);
|
||||
|
||||
const isSelectedItem = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFieldSelect';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
@ -138,9 +138,10 @@ export const ObjectFilterDropdownSubFieldSelect = () => {
|
||||
setObjectFilterDropdownFilterIsSelected(false);
|
||||
setSubFieldNameUsedInDropdown(null);
|
||||
};
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
FILTER_FIELD_LIST_ID,
|
||||
);
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
@ -189,7 +190,7 @@ export const ObjectFilterDropdownSubFieldSelect = () => {
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={['-1', ...options]}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
selectableListInstanceId={FILTER_FIELD_LIST_ID}
|
||||
>
|
||||
{compositeFieldTypeFilterableByAnySubField ? (
|
||||
<SelectableListItem
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { ObjectFilterDropdownRecordRemoveFilterMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordRemoveFilterMenuItem';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { SingleEntityObjectFilterDropdownButtonEffect } from '@/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButtonEffect';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
|
||||
import { IconChevronDown } from 'twenty-ui/display';
|
||||
|
||||
const SINGLE_ENTITY_FILTER_DROPDOWN_ID = 'single-entity-filter-dropdown';
|
||||
|
||||
export const SingleEntityObjectFilterDropdownButton = ({
|
||||
hotkeyScope,
|
||||
}: {
|
||||
hotkeyScope: HotkeyScope;
|
||||
}) => {
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SingleEntityObjectFilterDropdownButtonEffect />
|
||||
<Dropdown
|
||||
dropdownId={SINGLE_ENTITY_FILTER_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={hotkeyScope}
|
||||
dropdownOffset={{ x: 0, y: -28 }}
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton>
|
||||
{selectedFilter ? (
|
||||
<GenericEntityFilterChip filter={selectedFilter} />
|
||||
) : (
|
||||
t`Filter`
|
||||
)}
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledHeaderDropdownButton>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordRemoveFilterMenuItem />
|
||||
<ObjectFilterDropdownRecordSelect
|
||||
viewComponentId={SINGLE_ENTITY_FILTER_DROPDOWN_ID}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,39 +0,0 @@
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const SingleEntityObjectFilterDropdownButtonEffect = () => {
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { filterableFieldMetadataItems } =
|
||||
useFilterableFieldMetadataItemsInRecordIndexContext();
|
||||
|
||||
const firstFieldMetadataItem = filterableFieldMetadataItems[0];
|
||||
|
||||
useEffect(() => {
|
||||
setFieldMetadataItemIdUsedInDropdown(firstFieldMetadataItem.id);
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(firstFieldMetadataItem.type);
|
||||
|
||||
const defaultOperand = getRecordFilterOperands({ filterType })[0];
|
||||
|
||||
setSelectedOperandInDropdown(defaultOperand);
|
||||
}, [
|
||||
firstFieldMetadataItem,
|
||||
setSelectedOperandInDropdown,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -1,163 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { within } from '@storybook/test';
|
||||
import {
|
||||
ComponentDecorator,
|
||||
getCanvasElementForDropdownTesting,
|
||||
} from 'twenty-ui/testing';
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
title:
|
||||
'Modules/ObjectRecord/ObjectFilterDropdown/MultipleFiltersDropdownButton',
|
||||
component: MultipleFiltersDropdownButton,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === CoreObjectNameSingular.Company,
|
||||
)!;
|
||||
const instanceId = 'entity-tasks-filter-scope';
|
||||
|
||||
const setTableColumns = useSetRecoilComponentStateV2(
|
||||
tableColumnsComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const columns = companyObjectMetadataItem.fields.map(
|
||||
(fieldMetadataItem, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
position: index,
|
||||
}),
|
||||
);
|
||||
|
||||
setTableColumns(columns);
|
||||
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: CoreObjectNamePlural.Company,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
recordIndexId: instanceId,
|
||||
}}
|
||||
>
|
||||
<RecordFilterGroupsComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<RecordSortsComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<RecordTableComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: instanceId,
|
||||
onColumnsChange: () => {},
|
||||
}}
|
||||
>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId }}
|
||||
>
|
||||
<Story />
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordTableComponentInstanceContext.Provider>
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</RecordSortsComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</RecordFilterGroupsComponentInstanceContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
);
|
||||
},
|
||||
ContextStoreDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
ComponentDecorator,
|
||||
IconsProviderDecorator,
|
||||
I18nFrontDecorator,
|
||||
],
|
||||
args: {
|
||||
hotkeyScope: {
|
||||
scope: 'object-filter-dropdown',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TaskGroups>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const textFilter = await canvas.findByText('Tagline');
|
||||
|
||||
textFilter.click();
|
||||
|
||||
const operatorDropdown = await canvas.findByText('Contains');
|
||||
|
||||
operatorDropdown.click();
|
||||
|
||||
const containsOption = await canvas.findByText("Doesn't contain");
|
||||
|
||||
containsOption.click();
|
||||
},
|
||||
};
|
||||
|
||||
export const Date: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const dateFilter = await canvas.findByText('Last update');
|
||||
|
||||
dateFilter.click();
|
||||
},
|
||||
};
|
||||
|
||||
export const Number: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
const filterButton = await canvas.findByText('Filter');
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const dateFilter = await canvas.findByText('Employees');
|
||||
|
||||
dateFilter.click();
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const FILTER_FIELD_LIST_ID = 'filter-field-list';
|
||||
@ -1 +0,0 @@
|
||||
export const OBJECT_FILTER_DROPDOWN_ID = 'filter';
|
||||
Reference in New Issue
Block a user