Record filters - Introduced fieldMetadataItemUsedInDropdown instead of filterDefinitionUsedInDropdown (#10044)
This PR progressively introduces fieldMetadataItemUsedInDropdown instead of filterDefinitionUsedInDropdown where most easy to replace. This allows to use `fieldMetadataItemUsedInDropdown.id` instead of `filterDefinition.fieldMetadataId`, which is one easy dependency to remove on filter definition. We still derive filterDefinition instead of fully replacing it, because it will be easier to remove RecordFilterDefinition usage in a bottom-up approach instead. In multiple components of the filter dropdown, we try to replace filterDefinition by fieldMetadataItem derivation : Icon, label, id, type, etc. We also introduce the usage of subFieldNameUsedInDropdown instead of storing it dynamically on filterDefinition, for handling filtering on composite sub fields. The method `formatFieldMetadataItemAsFilterDefinition()` that is used to derive filterDefinition from fieldMetadataItem is what was being used originally to create the availableFilterDefinition state. (That is already removed) Fixed associated unit tests accordingly.
This commit is contained in:
@ -54,10 +54,8 @@ describe('useColumnDefinitionsFromFieldMetadata', () => {
|
||||
);
|
||||
|
||||
expect(Array.isArray(result.current.columnDefinitions)).toBe(true);
|
||||
expect(Array.isArray(result.current.filterDefinitions)).toBe(true);
|
||||
expect(Array.isArray(result.current.sortDefinitions)).toBe(true);
|
||||
expect(result.current.columnDefinitions.length).toBe(0);
|
||||
expect(result.current.filterDefinitions.length).toBe(0);
|
||||
expect(result.current.sortDefinitions.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -76,11 +74,9 @@ describe('useColumnDefinitionsFromFieldMetadata', () => {
|
||||
},
|
||||
);
|
||||
|
||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||
result.current;
|
||||
const { columnDefinitions, sortDefinitions } = result.current;
|
||||
|
||||
expect(columnDefinitions.length).toBe(21);
|
||||
expect(filterDefinitions.length).toBe(17);
|
||||
expect(sortDefinitions.length).toBe(14);
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
@ -79,7 +80,9 @@ export const getRelationObjectMetadataNamePlural = ({
|
||||
return field.relationDefinition?.targetObjectMetadata.namePlural;
|
||||
};
|
||||
|
||||
export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => {
|
||||
export const getFilterTypeFromFieldType = (
|
||||
fieldType: FieldMetadataType,
|
||||
): FilterableFieldType => {
|
||||
switch (fieldType) {
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
return 'DATE_TIME';
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getFilterTypeFromFieldType,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { useUpsertCombinedViewFilterGroup } from '@/object-record/advanced-filter/hooks/useUpsertCombinedViewFilterGroup';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
@ -86,12 +89,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
field: defaultFieldMetadataItem,
|
||||
});
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
defaultFieldMetadataItem.type,
|
||||
);
|
||||
|
||||
upsertCombinedViewFilter({
|
||||
id: v4(),
|
||||
fieldMetadataId: defaultFieldMetadataItem.id,
|
||||
operand: getRecordFilterOperandsForRecordFilterDefinition(
|
||||
defaultFilterDefinition,
|
||||
)[0],
|
||||
type: filterType,
|
||||
operand: getRecordFilterOperands({
|
||||
filterType,
|
||||
})[0],
|
||||
definition: defaultFilterDefinition,
|
||||
value: '',
|
||||
displayValue: '',
|
||||
@ -123,12 +131,17 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
field: defaultFieldMetadataItem,
|
||||
});
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
defaultFieldMetadataItem.type,
|
||||
);
|
||||
|
||||
upsertCombinedViewFilter({
|
||||
id: v4(),
|
||||
fieldMetadataId: defaultFieldMetadataItem.id,
|
||||
operand: getRecordFilterOperandsForRecordFilterDefinition(
|
||||
defaultFilterDefinition,
|
||||
)[0],
|
||||
type: filterType,
|
||||
operand: getRecordFilterOperands({
|
||||
filterType,
|
||||
})[0],
|
||||
definition: defaultFilterDefinition,
|
||||
value: '',
|
||||
displayValue: '',
|
||||
|
||||
@ -25,9 +25,9 @@ export const AdvancedFilterViewFilterFieldSelect = ({
|
||||
}: AdvancedFilterViewFilterFieldSelectProps) => {
|
||||
const { advancedFilterDropdownId } = useAdvancedFilterDropdown(viewFilterId);
|
||||
|
||||
const filter = useCurrentViewFilter({ viewFilterId });
|
||||
const recordFilter = useCurrentViewFilter({ viewFilterId });
|
||||
|
||||
const selectedFieldLabel = filter?.definition.label ?? '';
|
||||
const selectedFieldLabel = recordFilter?.label ?? '';
|
||||
|
||||
const setAdvancedFilterViewFilterId = useSetRecoilComponentStateV2(
|
||||
advancedFilterViewFilterIdComponentState,
|
||||
@ -58,8 +58,8 @@ export const AdvancedFilterViewFilterFieldSelect = ({
|
||||
/>
|
||||
}
|
||||
onOpen={() => {
|
||||
setAdvancedFilterViewFilterId(filter?.id);
|
||||
setAdvancedFilterViewFilterGroupId(filter?.viewFilterGroupId);
|
||||
setAdvancedFilterViewFilterId(recordFilter?.id);
|
||||
setAdvancedFilterViewFilterGroupId(recordFilter?.viewFilterGroupId);
|
||||
}}
|
||||
dropdownComponents={
|
||||
shouldShowCompositeSelectionSubMenu ? (
|
||||
|
||||
@ -3,7 +3,8 @@ import styled from '@emotion/styled';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
@ -39,8 +40,8 @@ export const ObjectFilterDropdownBooleanSelect = () => {
|
||||
const theme = useTheme();
|
||||
const options = [true, false];
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
@ -65,18 +66,22 @@ export const ObjectFilterDropdownBooleanSelect = () => {
|
||||
|
||||
const handleOptionSelect = (value: boolean) => {
|
||||
if (
|
||||
!isDefined(filterDefinitionUsedInDropdown) ||
|
||||
!isDefined(fieldMetadataItemUsedInDropdown) ||
|
||||
!isDefined(selectedOperandInDropdown)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ?? v4(),
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: value ? 'True' : 'False',
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: value.toString(),
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
|
||||
@ -19,8 +20,8 @@ import { isDefined } from 'twenty-shared';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const ObjectFilterDropdownDateInput = () => {
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
@ -41,16 +42,21 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
);
|
||||
|
||||
const isDateTimeInput =
|
||||
filterDefinitionUsedInDropdown?.type === FieldMetadataType.DATE_TIME;
|
||||
fieldMetadataItemUsedInDropdown?.type === FieldMetadataType.DATE_TIME;
|
||||
|
||||
const handleAbsoluteDateChange = (newDate: Date | null) => {
|
||||
setInternalDate(newDate);
|
||||
|
||||
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
definition: filterDefinition,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: newDate?.toISOString() ?? '',
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: isDefined(newDate)
|
||||
@ -58,7 +64,6 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
? newDate.toLocaleString()
|
||||
: newDate.toLocaleDateString()
|
||||
: '',
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
};
|
||||
@ -70,7 +75,11 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
unit: VariableDateViewFilterValueUnit;
|
||||
} | null,
|
||||
) => {
|
||||
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
const value = relativeDate
|
||||
? computeVariableDateViewFilterValue(
|
||||
@ -82,11 +91,11 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: getRelativeDateDisplayValue(relativeDate),
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
};
|
||||
|
||||
@ -151,9 +151,7 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
filterDefinition: selectedFilterDefinition,
|
||||
});
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(
|
||||
selectedFilterDefinition.fieldMetadataId,
|
||||
);
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
|
||||
|
||||
closeAdvancedFilterDropdown();
|
||||
};
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
|
||||
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
|
||||
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState';
|
||||
@ -9,12 +12,13 @@ import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/o
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
|
||||
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@ -37,6 +41,14 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
|
||||
);
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
);
|
||||
@ -83,39 +95,60 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
advancedFilterViewFilterId,
|
||||
);
|
||||
|
||||
const handleSelectFilter = (definition: RecordFilterDefinition | null) => {
|
||||
if (definition !== null) {
|
||||
const handleSelectFilter = (
|
||||
fieldMetadataItem: FieldMetadataItem | null | undefined,
|
||||
subFieldName?: string | null | undefined,
|
||||
) => {
|
||||
if (isDefined(fieldMetadataItem)) {
|
||||
const filterDefinition: RecordFilterDefinition = {
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItem.type),
|
||||
label: fieldMetadataItem.label,
|
||||
iconName: fieldMetadataItem.icon ?? '',
|
||||
compositeFieldName: subFieldName ?? undefined,
|
||||
};
|
||||
|
||||
if (
|
||||
isDefined(advancedFilterViewFilterId) &&
|
||||
isDefined(advancedFilterViewFilterGroupId)
|
||||
) {
|
||||
closeAdvancedFilterDropdown();
|
||||
|
||||
const operand =
|
||||
getRecordFilterOperandsForRecordFilterDefinition(definition)[0];
|
||||
const { value, displayValue } = getInitialFilterValue(
|
||||
definition.type,
|
||||
operand,
|
||||
);
|
||||
const type = getFilterTypeFromFieldType(fieldMetadataItem.type);
|
||||
|
||||
const operand = getRecordFilterOperands({
|
||||
filterType: type,
|
||||
subFieldName: subFieldName,
|
||||
})[0];
|
||||
|
||||
const { value, displayValue } = getInitialFilterValue(type, operand);
|
||||
|
||||
applyRecordFilter({
|
||||
id: advancedFilterViewFilterId,
|
||||
fieldMetadataId: definition.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
value,
|
||||
operand,
|
||||
displayValue,
|
||||
definition,
|
||||
definition: filterDefinition,
|
||||
viewFilterGroupId: advancedFilterViewFilterGroupId,
|
||||
subFieldName: subFieldName,
|
||||
});
|
||||
}
|
||||
|
||||
setFilterDefinitionUsedInDropdown(definition);
|
||||
setFieldMetadataItemIdUsedInDropdown(definition.fieldMetadataId);
|
||||
setFilterDefinitionUsedInDropdown(filterDefinition);
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
|
||||
|
||||
const type = getFilterTypeFromFieldType(fieldMetadataItem.type);
|
||||
|
||||
setSelectedOperandInDropdown(
|
||||
getRecordFilterOperandsForRecordFilterDefinition(definition)[0],
|
||||
getRecordFilterOperands({
|
||||
filterType: type,
|
||||
subFieldName: subFieldName,
|
||||
})[0],
|
||||
);
|
||||
|
||||
setSubFieldNameUsedInDropdown(subFieldName);
|
||||
|
||||
setObjectFilterDropdownSearchInput('');
|
||||
|
||||
setObjectFilterDropdownFilterIsSelected(true);
|
||||
@ -167,36 +200,30 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(objectFilterDropdownFirstLevelFilterDefinition);
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{/* TODO: fix this with a backend field on ViewFilter for composite field filter */}
|
||||
{objectFilterDropdownFirstLevelFilterDefinition.type === 'ACTOR' &&
|
||||
{fieldMetadataItemUsedInDropdown?.type === 'ACTOR' &&
|
||||
options.map((subFieldName, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) {
|
||||
handleSelectFilter({
|
||||
...objectFilterDropdownFirstLevelFilterDefinition,
|
||||
label: getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
),
|
||||
compositeFieldName: subFieldName,
|
||||
});
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(
|
||||
objectFilterDropdownFirstLevelFilterDefinition?.iconName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -7,14 +7,15 @@ import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getFilterTypeFromFieldType,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
@ -57,16 +58,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const filterDefinitionToSelect = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemToSelect,
|
||||
});
|
||||
|
||||
const isSelectedItem = useRecoilValue(
|
||||
isSelectedItemIdSelector(fieldMetadataItemToSelect.id),
|
||||
);
|
||||
|
||||
const isACompositeField = isCompositeField(fieldMetadataItemToSelect.type);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -85,27 +80,28 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
advancedFilterViewFilterId,
|
||||
);
|
||||
|
||||
const handleSelectFilterDefinition = (
|
||||
availableFilterDefinition: RecordFilterDefinition,
|
||||
) => {
|
||||
const handleSelectFilter = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
closeAdvancedFilterDropdown();
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(
|
||||
availableFilterDefinition.fieldMetadataId,
|
||||
);
|
||||
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItem,
|
||||
});
|
||||
|
||||
setFilterDefinitionUsedInDropdown(filterDefinition);
|
||||
|
||||
if (
|
||||
availableFilterDefinition.type === 'RELATION' ||
|
||||
availableFilterDefinition.type === 'SELECT'
|
||||
filterDefinition.type === 'RELATION' ||
|
||||
filterDefinition.type === 'SELECT'
|
||||
) {
|
||||
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
|
||||
}
|
||||
|
||||
setSelectedOperandInDropdown(
|
||||
getRecordFilterOperandsForRecordFilterDefinition(
|
||||
availableFilterDefinition,
|
||||
)[0],
|
||||
getRecordFilterOperands({
|
||||
filterType: filterDefinition.type,
|
||||
})[0],
|
||||
);
|
||||
|
||||
setObjectFilterDropdownFilterIsSelected(true);
|
||||
@ -113,20 +109,29 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const Icon = getIcon(fieldMetadataItemToSelect.icon);
|
||||
|
||||
const shouldShowSubMenu = isCompositeField(fieldMetadataItemToSelect.type);
|
||||
|
||||
const handleClick = () => {
|
||||
resetSelectedItem();
|
||||
|
||||
if (isACompositeField) {
|
||||
// TODO: create isCompositeFilterableFieldType type guard
|
||||
setObjectFilterDropdownSubMenuFieldType(
|
||||
filterDefinitionToSelect.type as CompositeFilterableFieldType,
|
||||
);
|
||||
setObjectFilterDropdownFirstLevelFilterDefinition(
|
||||
filterDefinitionToSelect,
|
||||
);
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemToSelect.type,
|
||||
);
|
||||
|
||||
if (isCompositeField(filterType)) {
|
||||
setObjectFilterDropdownSubMenuFieldType(filterType);
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemToSelect,
|
||||
});
|
||||
|
||||
setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition);
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemToSelect.id);
|
||||
setObjectFilterDropdownIsSelectingCompositeField(true);
|
||||
} else {
|
||||
handleSelectFilterDefinition(filterDefinitionToSelect);
|
||||
handleSelectFilter(fieldMetadataItemToSelect);
|
||||
}
|
||||
};
|
||||
|
||||
@ -135,9 +140,9 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
selected={false}
|
||||
hovered={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={getIcon(filterDefinitionToSelect.iconName)}
|
||||
text={filterDefinitionToSelect.label}
|
||||
hasSubMenu={isACompositeField}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
hasSubMenu={shouldShowSubMenu}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { ChangeEvent, useCallback, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
@ -13,8 +14,8 @@ export const ObjectFilterDropdownNumberInput = () => {
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
@ -39,27 +40,32 @@ export const ObjectFilterDropdownNumberInput = () => {
|
||||
},
|
||||
[hasFocused],
|
||||
);
|
||||
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
fieldMetadataItemUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuInput
|
||||
ref={handleInputRef}
|
||||
value={inputValue}
|
||||
autoFocus
|
||||
type="number"
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
placeholder={fieldMetadataItemUsedInDropdown.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
|
||||
setInputValue(newValue);
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
|
||||
value: newValue,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: newValue,
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
}}
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getFilterTypeFromFieldType,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -13,7 +19,6 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '../../record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { getOperandLabel } from '../utils/getOperandLabel';
|
||||
|
||||
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
@ -22,8 +27,8 @@ const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
`;
|
||||
|
||||
export const ObjectFilterDropdownOperandSelect = () => {
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
@ -34,14 +39,21 @@ export const ObjectFilterDropdownOperandSelect = () => {
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
const { closeDropdown } = useDropdown();
|
||||
|
||||
const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown)
|
||||
? getRecordFilterOperandsForRecordFilterDefinition(
|
||||
filterDefinitionUsedInDropdown,
|
||||
)
|
||||
const operandsForFilterType = isDefined(fieldMetadataItemUsedInDropdown)
|
||||
? getRecordFilterOperands({
|
||||
filterType: getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
),
|
||||
subFieldName: subFieldNameUsedInDropdown,
|
||||
})
|
||||
: [];
|
||||
|
||||
const handleOperandChange = (newOperand: ViewFilterOperand) => {
|
||||
@ -55,36 +67,48 @@ export const ObjectFilterDropdownOperandSelect = () => {
|
||||
|
||||
setSelectedOperandInDropdown(newOperand);
|
||||
|
||||
if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) {
|
||||
if (isValuelessOperand && isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: v4(),
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '',
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
displayValue: '',
|
||||
operand: newOperand,
|
||||
value: '',
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(filterDefinitionUsedInDropdown) &&
|
||||
isDefined(fieldMetadataItemUsedInDropdown) &&
|
||||
isDefined(selectedFilter)
|
||||
) {
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const { value, displayValue } = getInitialFilterValue(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
filterType,
|
||||
newOperand,
|
||||
selectedFilter.value,
|
||||
selectedFilter.displayValue,
|
||||
);
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: selectedFilter.fieldMetadataId,
|
||||
displayValue,
|
||||
operand: newOperand,
|
||||
value,
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -13,7 +13,8 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
|
||||
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
@ -32,8 +33,8 @@ type SelectOptionForFilter = FieldMetadataItemOption & {
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownOptionSelect = () => {
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const objectFilterDropdownSelectedOptionValues = useRecoilComponentValueV2(
|
||||
@ -66,7 +67,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
|
||||
const fieldMetaDataId = filterDefinitionUsedInDropdown?.fieldMetadataId ?? '';
|
||||
const fieldMetaDataId = fieldMetadataItemUsedInDropdown?.id ?? '';
|
||||
|
||||
const { selectOptions } = useOptionsForSelect(fieldMetaDataId);
|
||||
|
||||
@ -125,7 +126,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
: selectedOptions.map((option) => option.label).join(', ');
|
||||
|
||||
if (
|
||||
isDefined(filterDefinitionUsedInDropdown) &&
|
||||
isDefined(fieldMetadataItemUsedInDropdown) &&
|
||||
isDefined(selectedOperandInDropdown)
|
||||
) {
|
||||
const newFilterValue =
|
||||
@ -133,12 +134,16 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
? JSON.stringify(selectedOptions.map((option) => option.value))
|
||||
: EMPTY_FILTER_VALUE;
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: filterDisplayValue,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: newFilterValue,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
|
||||
import { FieldRatingValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
@ -9,6 +8,8 @@ import { RatingInput } from '@/ui/field/input/components/RatingInput';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
const convertFieldRatingValueToNumber = (
|
||||
rating: Exclude<FieldRatingValue, null>,
|
||||
@ -37,8 +38,8 @@ export const ObjectFilterDropdownRatingInput = () => {
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
@ -48,7 +49,7 @@ export const ObjectFilterDropdownRatingInput = () => {
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
fieldMetadataItemUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<RatingInput
|
||||
@ -58,13 +59,17 @@ export const ObjectFilterDropdownRatingInput = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter?.({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: convertFieldRatingValueToNumber(newValue),
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: convertFieldRatingValueToNumber(newValue),
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
}}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getRelationObjectMetadataNameSingular } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getRelationObjectMetadataNameSingular,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { ObjectFilterDropdownRecordPinnedItems } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems';
|
||||
import { CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID } from '@/object-record/object-filter-dropdown/constants/CurrentWorkspaceMemberSelectableItemId';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
@ -24,6 +24,7 @@ import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validat
|
||||
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconUserCircle } from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const EMPTY_FILTER_VALUE: string = JSON.stringify({
|
||||
isCurrentWorkspaceMemberSelected: false,
|
||||
@ -68,8 +69,6 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
const { currentViewWithCombinedFiltersAndSorts } =
|
||||
useGetCurrentView(viewComponentId);
|
||||
|
||||
const [fieldId] = useState(v4());
|
||||
|
||||
const { isCurrentWorkspaceMemberSelected } = jsonRelationFilterValueSchema
|
||||
.catch({
|
||||
isCurrentWorkspaceMemberSelected: false,
|
||||
@ -200,17 +199,21 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId ===
|
||||
filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataItemUsedInFilterDropdown.id,
|
||||
);
|
||||
|
||||
const filterId = viewFilter?.id ?? fieldId;
|
||||
const filterId = viewFilter?.id ?? v4();
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInFilterDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : filterId,
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: filterDisplayValue,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id,
|
||||
value: newFilterValue,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, useCallback, useState } from 'react';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@ -8,8 +8,8 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
export const ObjectFilterDropdownSearchInput = () => {
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
@ -37,14 +37,14 @@ export const ObjectFilterDropdownSearchInput = () => {
|
||||
[hasFocused],
|
||||
);
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
fieldMetadataItemUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearchInput
|
||||
ref={handleInputRef}
|
||||
autoFocus
|
||||
type="text"
|
||||
value={objectFilterDropdownSearchInput}
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
placeholder={fieldMetadataItemUsedInDropdown.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setObjectFilterDropdownSearchInput(event.target.value);
|
||||
}}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { useEmptyRecordFilter } from '@/object-record/object-filter-dropdown/hooks/useEmptyRecordFilter';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
@ -50,8 +51,8 @@ export const ObjectFilterDropdownSourceSelect = ({
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const { applyRecordFilter } = useApplyRecordFilter(viewComponentId);
|
||||
@ -87,13 +88,15 @@ export const ObjectFilterDropdownSourceSelect = ({
|
||||
(id) => id !== itemToSelect.id,
|
||||
);
|
||||
|
||||
if (!filterDefinitionUsedInDropdown) {
|
||||
throw new Error('Filter definition used in dropdown should be defined');
|
||||
if (!isDefined(fieldMetadataItemUsedInFilterDropdown)) {
|
||||
throw new Error(
|
||||
'Field metadata item used in filter dropdown should be defined',
|
||||
);
|
||||
}
|
||||
|
||||
if (newSelectedItemIds.length === 0) {
|
||||
emptyRecordFilter();
|
||||
removeRecordFilter(filterDefinitionUsedInDropdown.fieldMetadataId);
|
||||
removeRecordFilter(fieldMetadataItemUsedInFilterDropdown.id);
|
||||
deleteCombinedViewFilter(fieldId);
|
||||
return;
|
||||
}
|
||||
@ -110,7 +113,7 @@ export const ObjectFilterDropdownSourceSelect = ({
|
||||
: selectedItemNames.join(', ');
|
||||
|
||||
if (
|
||||
isDefined(filterDefinitionUsedInDropdown) &&
|
||||
isDefined(fieldMetadataItemUsedInFilterDropdown) &&
|
||||
isDefined(selectedOperandInDropdown)
|
||||
) {
|
||||
const newFilterValue =
|
||||
@ -122,17 +125,21 @@ export const ObjectFilterDropdownSourceSelect = ({
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
(viewFilter) =>
|
||||
viewFilter.fieldMetadataId ===
|
||||
filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataItemUsedInFilterDropdown.id,
|
||||
);
|
||||
|
||||
const filterId = viewFilter?.id ?? fieldId;
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInFilterDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : filterId,
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
operand: selectedOperandInDropdown || ViewFilterOperand.Is,
|
||||
displayValue: filterDisplayValue,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
fieldMetadataId: fieldMetadataItemUsedInFilterDropdown.id,
|
||||
value: newFilterValue,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeEvent, useCallback, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -9,13 +9,13 @@ import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApp
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const ObjectFilterDropdownTextSearchInput = () => {
|
||||
const [filterId] = useState(v4());
|
||||
const [hasFocused, setHasFocused] = useState(false);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
@ -47,24 +47,28 @@ export const ObjectFilterDropdownTextSearchInput = () => {
|
||||
[hasFocused],
|
||||
);
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
fieldMetadataItemUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearchInput
|
||||
ref={handleInputRef}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
placeholder={fieldMetadataItemUsedInDropdown.label}
|
||||
value={selectedFilter?.value ?? objectFilterDropdownSearchInput}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setObjectFilterDropdownSearchInput(event.target.value);
|
||||
|
||||
const filterDefinition = formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItemUsedInDropdown,
|
||||
});
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ?? filterId,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
id: selectedFilter?.id ?? v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: event.target.value,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: event.target.value,
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
definition: filterDefinition,
|
||||
viewFilterGroupId: selectedFilter?.viewFilterGroupId,
|
||||
});
|
||||
}}
|
||||
|
||||
@ -2,7 +2,7 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/ob
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const subFieldNameUsedInDropdownComponentState = createComponentStateV2<
|
||||
string | null
|
||||
string | null | undefined
|
||||
>({
|
||||
key: 'subFieldNameUsedInDropdownComponentState',
|
||||
defaultValue: null,
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import { FieldActorValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
export const isFilterOnActorSourceSubField = (
|
||||
subFieldName?: string | null | undefined,
|
||||
) => {
|
||||
return subFieldName === ('source' satisfies keyof FieldActorValue);
|
||||
};
|
||||
@ -47,6 +47,8 @@ describe('useRemoveRecordFilter', () => {
|
||||
label: 'Test Field',
|
||||
iconName: 'IconText',
|
||||
},
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
};
|
||||
|
||||
// First add a filter
|
||||
@ -96,6 +98,8 @@ describe('useRemoveRecordFilter', () => {
|
||||
label: 'Test Field',
|
||||
iconName: 'IconText',
|
||||
},
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
};
|
||||
|
||||
// Add a filter
|
||||
|
||||
@ -41,6 +41,8 @@ describe('useUpsertRecordFilter', () => {
|
||||
label: 'Test Field',
|
||||
iconName: 'IconText',
|
||||
},
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
@ -79,6 +81,8 @@ describe('useUpsertRecordFilter', () => {
|
||||
label: 'Test Field',
|
||||
iconName: 'IconText',
|
||||
},
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
};
|
||||
|
||||
const updatedFilter: RecordFilter = {
|
||||
@ -93,6 +97,8 @@ describe('useUpsertRecordFilter', () => {
|
||||
label: 'Test Field',
|
||||
iconName: 'IconText',
|
||||
},
|
||||
label: 'Test Field',
|
||||
type: 'TEXT',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
|
||||
@ -17,16 +17,25 @@ export const useUpsertRecordFilter = () => {
|
||||
currentRecordFiltersCallbackState,
|
||||
);
|
||||
|
||||
// TODO: This is a temporary solution to ensure that the record filter is compatible with filter definitions
|
||||
// Label and type will be set without definition
|
||||
const recordFilterToSet: RecordFilter = {
|
||||
...filter,
|
||||
label: filter.definition.label,
|
||||
type: filter.definition.type,
|
||||
};
|
||||
|
||||
const foundRecordFilterInCurrentRecordFilters =
|
||||
currentRecordFilters.some(
|
||||
(existingFilter) =>
|
||||
existingFilter.fieldMetadataId === filter.fieldMetadataId,
|
||||
existingFilter.fieldMetadataId ===
|
||||
recordFilterToSet.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!foundRecordFilterInCurrentRecordFilters) {
|
||||
set(currentRecordFiltersCallbackState, [
|
||||
...currentRecordFilters,
|
||||
filter,
|
||||
recordFilterToSet,
|
||||
]);
|
||||
} else {
|
||||
set(currentRecordFiltersCallbackState, (currentRecordFilters) => {
|
||||
@ -34,11 +43,12 @@ export const useUpsertRecordFilter = () => {
|
||||
|
||||
const indexOfFilterToUpdate = newCurrentRecordFilters.findIndex(
|
||||
(existingFilter) =>
|
||||
existingFilter.fieldMetadataId === filter.fieldMetadataId,
|
||||
existingFilter.fieldMetadataId ===
|
||||
recordFilterToSet.fieldMetadataId,
|
||||
);
|
||||
|
||||
newCurrentRecordFilters[indexOfFilterToUpdate] = {
|
||||
...filter,
|
||||
...recordFilterToSet,
|
||||
};
|
||||
|
||||
return newCurrentRecordFilters;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
@ -7,11 +8,12 @@ export type RecordFilter = {
|
||||
fieldMetadataId: string;
|
||||
value: string;
|
||||
displayValue: string;
|
||||
type?: FilterableFieldType;
|
||||
viewFilterGroupId?: string;
|
||||
displayAvatarUrl?: string;
|
||||
operand: ViewFilterOperand;
|
||||
positionInViewFilterGroup?: number | null;
|
||||
definition: RecordFilterDefinition;
|
||||
label?: string;
|
||||
subFieldName?: string;
|
||||
subFieldName?: string | null | undefined;
|
||||
};
|
||||
|
||||
@ -795,7 +795,7 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.definition.label} filter`,
|
||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
export type GetRecordFilterOperandsParams = {
|
||||
filterType: FilterableFieldType;
|
||||
subFieldName?: string | null | undefined;
|
||||
};
|
||||
|
||||
export const getRecordFilterOperands = ({
|
||||
filterType,
|
||||
subFieldName,
|
||||
}: GetRecordFilterOperandsParams): RecordFilterOperand[] => {
|
||||
const emptyOperands = [
|
||||
RecordFilterOperand.IsEmpty,
|
||||
RecordFilterOperand.IsNotEmpty,
|
||||
];
|
||||
|
||||
const relationOperands = [RecordFilterOperand.Is, RecordFilterOperand.IsNot];
|
||||
|
||||
switch (filterType) {
|
||||
case 'TEXT':
|
||||
case 'EMAILS':
|
||||
case 'FULL_NAME':
|
||||
case 'ADDRESS':
|
||||
case 'LINKS':
|
||||
case 'PHONES':
|
||||
return [
|
||||
RecordFilterOperand.Contains,
|
||||
RecordFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'CURRENCY':
|
||||
case 'NUMBER':
|
||||
return [
|
||||
RecordFilterOperand.GreaterThan,
|
||||
RecordFilterOperand.LessThan,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'RAW_JSON':
|
||||
return [
|
||||
RecordFilterOperand.Contains,
|
||||
RecordFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'DATE_TIME':
|
||||
case 'DATE':
|
||||
return [
|
||||
RecordFilterOperand.Is,
|
||||
RecordFilterOperand.IsRelative,
|
||||
RecordFilterOperand.IsInPast,
|
||||
RecordFilterOperand.IsInFuture,
|
||||
RecordFilterOperand.IsToday,
|
||||
RecordFilterOperand.IsBefore,
|
||||
RecordFilterOperand.IsAfter,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'RATING':
|
||||
return [
|
||||
RecordFilterOperand.Is,
|
||||
RecordFilterOperand.GreaterThan,
|
||||
RecordFilterOperand.LessThan,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'RELATION':
|
||||
return [...relationOperands, ...emptyOperands];
|
||||
case 'MULTI_SELECT':
|
||||
return [
|
||||
RecordFilterOperand.Contains,
|
||||
RecordFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'SELECT':
|
||||
return [
|
||||
RecordFilterOperand.Is,
|
||||
RecordFilterOperand.IsNot,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'ACTOR': {
|
||||
if (isFilterOnActorSourceSubField(subFieldName)) {
|
||||
return [
|
||||
RecordFilterOperand.Is,
|
||||
RecordFilterOperand.IsNot,
|
||||
...emptyOperands,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
RecordFilterOperand.Contains,
|
||||
RecordFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
}
|
||||
case 'ARRAY':
|
||||
return [
|
||||
RecordFilterOperand.Contains,
|
||||
RecordFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
];
|
||||
case 'BOOLEAN':
|
||||
return [RecordFilterOperand.Is];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@ -30,9 +30,7 @@ export const RecordTableEmptyStateSoftDelete = () => {
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const deletedFilter = tableFilters.find(
|
||||
(filter) =>
|
||||
filter.definition.label === 'Deleted' &&
|
||||
filter.operand === 'isNotEmpty',
|
||||
(filter) => filter.label === 'Deleted' && filter.operand === 'isNotEmpty',
|
||||
);
|
||||
|
||||
if (!deletedFilter) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { getOperandLabelShort } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||
@ -14,13 +15,20 @@ export const EditableFilterChip = ({
|
||||
onRemove,
|
||||
}: EditableFilterChipProps) => {
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { fieldMetadataItem } = useFieldMetadataItemById(
|
||||
viewFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon);
|
||||
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
key={viewFilter.id}
|
||||
testId={viewFilter.id}
|
||||
labelKey={`${viewFilter.definition.label}${getOperandLabelShort(viewFilter.operand)}`}
|
||||
labelKey={`${viewFilter.label}${getOperandLabelShort(viewFilter.operand)}`}
|
||||
labelValue={viewFilter.displayValue}
|
||||
Icon={getIcon(viewFilter.definition.iconName)}
|
||||
Icon={FieldMetadataItemIcon}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
@ -9,12 +10,12 @@ import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedVie
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
type VariantFilterChipProps = {
|
||||
viewFilter: RecordFilter;
|
||||
recordFilter: RecordFilter;
|
||||
viewBarId: string;
|
||||
};
|
||||
|
||||
export const VariantFilterChip = ({
|
||||
viewFilter,
|
||||
recordFilter,
|
||||
viewBarId,
|
||||
}: VariantFilterChipProps) => {
|
||||
const { deleteCombinedViewFilter } = useDeleteCombinedViewFilters();
|
||||
@ -30,17 +31,23 @@ export const VariantFilterChip = ({
|
||||
viewBarId,
|
||||
});
|
||||
|
||||
const { fieldMetadataItem } = useFieldMetadataItemById(
|
||||
recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
const { removeRecordFilter } = useRemoveRecordFilter();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon);
|
||||
|
||||
const handleRemoveClick = () => {
|
||||
deleteCombinedViewFilter(viewFilter.id);
|
||||
removeRecordFilter(viewFilter.fieldMetadataId);
|
||||
deleteCombinedViewFilter(recordFilter.id);
|
||||
removeRecordFilter(recordFilter.fieldMetadataId);
|
||||
|
||||
if (
|
||||
viewFilter.definition.label === 'Deleted' &&
|
||||
viewFilter.operand === 'isNotEmpty'
|
||||
recordFilter.label === 'Deleted' &&
|
||||
recordFilter.operand === 'isNotEmpty'
|
||||
) {
|
||||
toggleSoftDeleteFilterState(false);
|
||||
}
|
||||
@ -48,11 +55,11 @@ export const VariantFilterChip = ({
|
||||
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
key={viewFilter.fieldMetadataId}
|
||||
testId={viewFilter.fieldMetadataId}
|
||||
variant={viewFilter.variant}
|
||||
labelValue={viewFilter.definition.label}
|
||||
Icon={getIcon(viewFilter.definition.iconName)}
|
||||
key={recordFilter.fieldMetadataId}
|
||||
testId={recordFilter.fieldMetadataId}
|
||||
variant={recordFilter.variant}
|
||||
labelValue={recordFilter.label ?? ''}
|
||||
Icon={FieldMetadataItemIcon}
|
||||
onRemove={handleRemoveClick}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -193,7 +193,7 @@ export const ViewBarDetails = ({
|
||||
{otherViewFilters.map((viewFilter) => (
|
||||
<VariantFilterChip
|
||||
key={viewFilter.fieldMetadataId}
|
||||
viewFilter={viewFilter}
|
||||
recordFilter={viewFilter}
|
||||
viewBarId={viewBarId}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -4,7 +4,7 @@ import { useEffect } from 'react';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
@ -21,8 +21,8 @@ export const ViewBarFilterEffect = ({
|
||||
}: ViewBarFilterEffectProps) => {
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
@ -38,12 +38,11 @@ export const ViewBarFilterEffect = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
|
||||
if (fieldMetadataItemUsedInDropdown?.type === 'RELATION') {
|
||||
const viewFilterUsedInDropdown =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
(filter) =>
|
||||
filter.fieldMetadataId ===
|
||||
filterDefinitionUsedInDropdown?.fieldMetadataId,
|
||||
filter.fieldMetadataId === fieldMetadataItemUsedInDropdown?.id,
|
||||
);
|
||||
|
||||
const { selectedRecordIds } = jsonRelationFilterValueSchema
|
||||
@ -57,14 +56,13 @@ export const ViewBarFilterEffect = ({
|
||||
|
||||
setObjectFilterDropdownSelectedRecordIds(selectedRecordIds);
|
||||
} else if (
|
||||
isDefined(filterDefinitionUsedInDropdown) &&
|
||||
['SELECT', 'MULTI_SELECT'].includes(filterDefinitionUsedInDropdown.type)
|
||||
isDefined(fieldMetadataItemUsedInDropdown) &&
|
||||
['SELECT', 'MULTI_SELECT'].includes(fieldMetadataItemUsedInDropdown.type)
|
||||
) {
|
||||
const viewFilterUsedInDropdown =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
(filter) =>
|
||||
filter.fieldMetadataId ===
|
||||
filterDefinitionUsedInDropdown?.fieldMetadataId,
|
||||
filter.fieldMetadataId === fieldMetadataItemUsedInDropdown?.id,
|
||||
);
|
||||
|
||||
const viewFilterSelectedRecords = isNonEmptyString(
|
||||
@ -75,7 +73,7 @@ export const ViewBarFilterEffect = ({
|
||||
setObjectFilterDropdownSelectedOptionValues(viewFilterSelectedRecords);
|
||||
}
|
||||
}, [
|
||||
filterDefinitionUsedInDropdown,
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
currentViewWithCombinedFiltersAndSorts,
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getFilterTypeFromFieldType,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -39,7 +43,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
fieldMetadataId: mockFieldMetadataItem.id,
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
displayValue: mockFieldMetadataItem.label,
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
definition: mockFilterDefinition,
|
||||
@ -99,7 +103,9 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
|
||||
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
|
||||
definition: mockFilterDefinition,
|
||||
},
|
||||
label: mockViewFilter.displayValue,
|
||||
type: getFilterTypeFromFieldType(mockFieldMetadataItem.type),
|
||||
} satisfies RecordFilter,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import {
|
||||
formatFieldMetadataItemAsFilterDefinition,
|
||||
getFilterTypeFromFieldType,
|
||||
} from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
@ -35,7 +39,7 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => {
|
||||
fieldMetadataId: mockFieldMetadataItem.id,
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
displayValue: mockFieldMetadataItem.label,
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
definition: mockAvailableFilterDefinition,
|
||||
@ -72,7 +76,9 @@ describe('useApplyViewFiltersToCurrentRecordFilters', () => {
|
||||
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
|
||||
definition: mockAvailableFilterDefinition,
|
||||
},
|
||||
label: mockViewFilter.displayValue,
|
||||
type: getFilterTypeFromFieldType(mockFieldMetadataItem.type),
|
||||
} satisfies RecordFilter,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
const baseDefinition = {
|
||||
@ -65,6 +66,10 @@ describe('mapViewFiltersToFilters', () => {
|
||||
...baseDefinition,
|
||||
type: 'FULL_NAME',
|
||||
},
|
||||
label: baseDefinition.label,
|
||||
type: 'FULL_NAME',
|
||||
positionInViewFilterGroup: undefined,
|
||||
viewFilterGroupId: undefined,
|
||||
},
|
||||
];
|
||||
expect(
|
||||
|
||||
@ -26,6 +26,8 @@ export const mapViewFiltersToFilters = (
|
||||
viewFilterGroupId: viewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup: viewFilter.positionInViewFilterGroup,
|
||||
definition: viewFilter.definition ?? availableFilterDefinition,
|
||||
label: viewFilter.definition?.label ?? availableFilterDefinition.label,
|
||||
type: viewFilter.definition?.type ?? availableFilterDefinition.type,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
Reference in New Issue
Block a user