Implement parallel code path for fieldMetadataItem instead of filterDefinition (#9931)
This PR doesn't remove or change the current behavior of the filter definition used in filter dropdown, but adds a parallel code path where we set the field metadata item used in filter dropdown, which is enough to replace the filter definition. The goal at the end is to compute dynamically the equivalent of filter definition where needed, by deriving from objectMetadataItems global state + fieldMetadataItemId used in dropdown, that way we don't create any other source of truth for the concept of filter definition and everything is easier to work with, especially with advanced filters. The general spirit is that it's always better to derive everywhere from a unique state as much as possible, and only create the equivalent of selectors where needed that will only take the relevant chunk of state for the small zone of the code operating some reading/writing. - Added utils and hooks to get a FieldMetadataItem more easily - Removed some properties from RecordFilterDefinition (the easiest to remove) and replaced them with a dynamic logic, deriving what's needed where it is needed - Added a new fieldMetadataItemIdUsedInDropdownComponentState that is set in parallel of filterDefinitionUsedInDropdown (to prepare the removal of filter definition used in dropdown) - Fixed some stories --------- Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
@ -36,6 +36,6 @@ export const Default: Story = {
|
||||
await canvas.findByText('Search');
|
||||
await canvas.findByText('Settings');
|
||||
await canvas.findByText('Linkedin');
|
||||
await canvas.findByText('All');
|
||||
await canvas.findByText('All companies');
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useFieldMetadataItemById = (fieldMetadataId: string) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const fieldMetadataItem = objectMetadataItems
|
||||
.flatMap((objectMetadataItem) => objectMetadataItem.fields)
|
||||
.find((field) => field.id === fieldMetadataId);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
throw new Error(`Field metadata item not found for id ${fieldMetadataId}`);
|
||||
}
|
||||
|
||||
return { fieldMetadataItem };
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useGetFieldMetadataItemById = () => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const getFieldMetadataItemById = (fieldMetadataId: string) => {
|
||||
const fieldMetadataItem = objectMetadataItems
|
||||
.flatMap((objectMetadataItem) => objectMetadataItem.fields)
|
||||
.find((field) => field.id === fieldMetadataId);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
throw new Error(
|
||||
`Field metadata item not found for id ${fieldMetadataId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return fieldMetadataItem;
|
||||
};
|
||||
|
||||
return { getFieldMetadataItemById };
|
||||
};
|
||||
@ -60,13 +60,25 @@ export const formatFieldMetadataItemAsFilterDefinition = ({
|
||||
fieldMetadataId: field.id,
|
||||
label: field.label,
|
||||
iconName: field.icon ?? 'Icon123',
|
||||
relationObjectMetadataNamePlural:
|
||||
field.relationDefinition?.targetObjectMetadata.namePlural,
|
||||
relationObjectMetadataNameSingular:
|
||||
field.relationDefinition?.targetObjectMetadata.nameSingular,
|
||||
type: getFilterTypeFromFieldType(field.type),
|
||||
});
|
||||
|
||||
export const getRelationObjectMetadataNameSingular = ({
|
||||
field,
|
||||
}: {
|
||||
field: ObjectMetadataItem['fields'][0];
|
||||
}): string | undefined => {
|
||||
return field.relationDefinition?.targetObjectMetadata.nameSingular;
|
||||
};
|
||||
|
||||
export const getRelationObjectMetadataNamePlural = ({
|
||||
field,
|
||||
}: {
|
||||
field: ObjectMetadataItem['fields'][0];
|
||||
}): string | undefined => {
|
||||
return field.relationDefinition?.targetObjectMetadata.namePlural;
|
||||
};
|
||||
|
||||
export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => {
|
||||
switch (fieldType) {
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useCurrentViewFilter } from '@/object-record/advanced-filter/hooks/useCurrentViewFilter';
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -26,6 +27,10 @@ export const AdvancedFilterViewFilterValueInput = ({
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -58,6 +63,7 @@ export const AdvancedFilterViewFilterValueInput = ({
|
||||
/>
|
||||
}
|
||||
onOpen={() => {
|
||||
setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId);
|
||||
setFilterDefinitionUsedInDropdown(filter.definition);
|
||||
setSelectedOperandInDropdown(filter.operand);
|
||||
setSelectedFilter(filter);
|
||||
|
||||
@ -25,6 +25,7 @@ import { isDefined } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
@ -126,11 +127,15 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
const { selectFilterDefinitionUsedInDropdown } =
|
||||
useSelectFilterDefinitionUsedInDropdown();
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const handleEnter = (itemId: string) => {
|
||||
const handleEnter = (fieldMetadataItemId: string) => {
|
||||
const selectedFilterDefinition = availableFilterDefinitions.find(
|
||||
(item) => item.fieldMetadataId === itemId,
|
||||
(item) => item.fieldMetadataId === fieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(selectedFilterDefinition)) {
|
||||
@ -143,6 +148,10 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
filterDefinition: selectedFilterDefinition,
|
||||
});
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(
|
||||
selectedFilterDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
closeAdvancedFilterDropdown();
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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 { 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';
|
||||
@ -61,6 +62,10 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -110,6 +115,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
}
|
||||
|
||||
setFilterDefinitionUsedInDropdown(definition);
|
||||
setFieldMetadataItemIdUsedInDropdown(definition.fieldMetadataId);
|
||||
|
||||
setSelectedOperandInDropdown(
|
||||
getRecordFilterOperandsForRecordFilterDefinition(definition)[0],
|
||||
@ -122,6 +128,7 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
};
|
||||
|
||||
const handleSubMenuBack = () => {
|
||||
setFieldMetadataItemIdUsedInDropdown(null);
|
||||
setFilterDefinitionUsedInDropdown(null);
|
||||
setObjectFilterDropdownSubMenuFieldType(null);
|
||||
setObjectFilterDropdownFirstLevelFilterDefinition(null);
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
|
||||
import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown';
|
||||
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType';
|
||||
|
||||
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||
import { CompositeFilterableFieldType } from '@/object-record/record-filter/types/CompositeFilterableFieldType';
|
||||
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
@ -31,6 +32,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
const { selectFilterDefinitionUsedInDropdown } =
|
||||
useSelectFilterDefinitionUsedInDropdown();
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownFirstLevelFilterDefinition] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownFirstLevelFilterDefinitionComponentState,
|
||||
@ -82,6 +87,10 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
filterDefinition: availableFilterDefinition,
|
||||
});
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(
|
||||
availableFilterDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (
|
||||
availableFilterDefinition.type === 'RELATION' ||
|
||||
availableFilterDefinition.type === 'SELECT'
|
||||
|
||||
@ -2,8 +2,10 @@ import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { 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';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
@ -41,6 +43,10 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -73,15 +79,20 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
})
|
||||
.parse(selectedFilter?.value);
|
||||
|
||||
const objectNameSingular =
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular;
|
||||
if (!isDefined(fieldMetadataItemUsedInFilterDropdown)) {
|
||||
throw new Error('fieldMetadataItemUsedInFilterDropdown is not defined');
|
||||
}
|
||||
|
||||
const objectNameSingular = getRelationObjectMetadataNameSingular({
|
||||
field: fieldMetadataItemUsedInFilterDropdown,
|
||||
});
|
||||
|
||||
if (!isDefined(objectNameSingular)) {
|
||||
throw new Error('relationObjectMetadataNameSingular is not defined');
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: objectNameSingular,
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectLabelPlural = objectMetadataItem?.labelPlural;
|
||||
|
||||
@ -7,8 +7,8 @@ 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 { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -36,6 +36,10 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -47,6 +51,9 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
const availableFilterDefinition = availableFilterDefinitions[0];
|
||||
|
||||
React.useEffect(() => {
|
||||
setFieldMetadataItemIdUsedInDropdown(
|
||||
availableFilterDefinition.fieldMetadataId,
|
||||
);
|
||||
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
|
||||
const defaultOperand = getRecordFilterOperandsForRecordFilterDefinition(
|
||||
availableFilterDefinition,
|
||||
@ -56,6 +63,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
availableFilterDefinition,
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setSelectedOperandInDropdown,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
]);
|
||||
|
||||
const theme = useTheme();
|
||||
@ -69,14 +77,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton>
|
||||
{selectedFilter ? (
|
||||
<GenericEntityFilterChip
|
||||
filter={selectedFilter}
|
||||
Icon={
|
||||
selectedFilter.operand === ViewFilterOperand.IsNotNull
|
||||
? availableFilterDefinition.SelectAllIcon
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<GenericEntityFilterChip filter={selectedFilter} />
|
||||
) : (
|
||||
t`Filter`
|
||||
)}
|
||||
|
||||
@ -3,13 +3,14 @@ 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 { formatFieldMetadataItemAsFilterDefinition } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
@ -18,7 +19,6 @@ import {
|
||||
ComponentDecorator,
|
||||
getCanvasElementForDropdownTesting,
|
||||
} from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
@ -45,59 +45,26 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
instanceId,
|
||||
);
|
||||
|
||||
setTableColumns([
|
||||
{
|
||||
fieldMetadataId: '1',
|
||||
iconName: 'IconUser',
|
||||
label: 'Text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isVisible: true,
|
||||
metadata: {
|
||||
fieldName: 'text',
|
||||
},
|
||||
} as ColumnDefinition<any>,
|
||||
{
|
||||
fieldMetadataId: '3',
|
||||
iconName: 'IconNumber',
|
||||
label: 'Number',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
isVisible: true,
|
||||
metadata: {
|
||||
fieldName: 'number',
|
||||
},
|
||||
} as ColumnDefinition<any>,
|
||||
{
|
||||
fieldMetadataId: '4',
|
||||
iconName: 'IconCalendar',
|
||||
label: 'Date',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
isVisible: true,
|
||||
metadata: {
|
||||
fieldName: 'date',
|
||||
},
|
||||
} as ColumnDefinition<any>,
|
||||
]);
|
||||
const columns = companyObjectMetadataItem.fields.map(
|
||||
(fieldMetadataItem, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
position: index,
|
||||
}),
|
||||
);
|
||||
|
||||
const filterDefinitions = companyObjectMetadataItem.fields.map(
|
||||
(fieldMetadataItem) =>
|
||||
formatFieldMetadataItemAsFilterDefinition({
|
||||
field: fieldMetadataItem,
|
||||
}),
|
||||
);
|
||||
|
||||
setTableColumns(columns);
|
||||
|
||||
setAvailableFilterDefinitions(filterDefinitions);
|
||||
|
||||
setAvailableFilterDefinitions([
|
||||
{
|
||||
fieldMetadataId: '1',
|
||||
iconName: 'IconUser',
|
||||
label: 'Text',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: '3',
|
||||
iconName: 'IconNumber',
|
||||
label: 'Number',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: '3',
|
||||
iconName: 'IconCalendar',
|
||||
label: 'Date',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
@ -151,7 +118,7 @@ export const Default: Story = {
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const textFilter = await canvas.findByText('Text');
|
||||
const textFilter = await canvas.findByText('Tagline');
|
||||
|
||||
textFilter.click();
|
||||
|
||||
@ -173,7 +140,7 @@ export const Date: Story = {
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const dateFilter = await canvas.findByText('Date');
|
||||
const dateFilter = await canvas.findByText('Last update');
|
||||
|
||||
dateFilter.click();
|
||||
},
|
||||
@ -187,7 +154,7 @@ export const Number: Story = {
|
||||
|
||||
filterButton.click();
|
||||
|
||||
const dateFilter = await canvas.findByText('Number');
|
||||
const dateFilter = await canvas.findByText('Employees');
|
||||
|
||||
dateFilter.click();
|
||||
},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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 { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -26,6 +27,11 @@ export const useSelectFilterDefinitionUsedInDropdown = (
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
componentInstanceId,
|
||||
@ -54,6 +60,7 @@ export const useSelectFilterDefinitionUsedInDropdown = (
|
||||
filterDefinition,
|
||||
}: SelectFilterParams) => {
|
||||
setFilterDefinitionUsedInDropdown(filterDefinition);
|
||||
setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId);
|
||||
|
||||
if (
|
||||
filterDefinition.type === 'RELATION' ||
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const fieldMetadataItemIdUsedInDropdownComponentState =
|
||||
createComponentStateV2<string | null>({
|
||||
key: 'fieldMetadataItemIdUsedInDropdownComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
|
||||
export const fieldMetadataItemUsedInDropdownComponentSelector =
|
||||
createComponentSelectorV2<FieldMetadataItem | null | undefined>({
|
||||
key: 'fieldMetadataItemUsedInDropdownComponentSelector',
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const fieldMetadataItemIdUsedInDropdown = get(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
|
||||
const correspondingFieldMetadataItem = objectMetadataItems
|
||||
.flatMap((objectMetadataItem) => objectMetadataItem.fields)
|
||||
.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === fieldMetadataItemIdUsedInDropdown,
|
||||
);
|
||||
|
||||
return correspondingFieldMetadataItem;
|
||||
},
|
||||
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const subFieldNameUsedInDropdownComponentState = createComponentStateV2<
|
||||
string | null
|
||||
>({
|
||||
key: 'subFieldNameUsedInDropdownComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
|
||||
});
|
||||
@ -140,7 +140,10 @@ export const Submit: Story = {
|
||||
});
|
||||
|
||||
await userEvent.click(item);
|
||||
await waitFor(() => expect(submitJestFn).toHaveBeenCalledTimes(1));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(submitJestFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -12,4 +12,6 @@ export type RecordFilter = {
|
||||
operand: ViewFilterOperand;
|
||||
positionInViewFilterGroup?: number | null;
|
||||
definition: RecordFilterDefinition;
|
||||
label?: string;
|
||||
subFieldName?: string;
|
||||
};
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
import { FilterableFieldType } from './FilterableFieldType';
|
||||
|
||||
export type RecordFilterDefinition = {
|
||||
@ -7,9 +5,5 @@ export type RecordFilterDefinition = {
|
||||
label: string;
|
||||
iconName: string;
|
||||
type: FilterableFieldType;
|
||||
relationObjectMetadataNamePlural?: string;
|
||||
relationObjectMetadataNameSingular?: string;
|
||||
selectAllLabel?: string;
|
||||
SelectAllIcon?: IconComponent;
|
||||
compositeFieldName?: string;
|
||||
};
|
||||
|
||||
@ -3,13 +3,15 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown';
|
||||
|
||||
import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||
@ -56,6 +58,11 @@ export const useHandleToggleColumnFilter = ({
|
||||
const { selectFilterDefinitionUsedInDropdown } =
|
||||
useSelectFilterDefinitionUsedInDropdown(viewBarId);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
viewBarId,
|
||||
);
|
||||
|
||||
const handleToggleColumnFilter = useCallback(
|
||||
async (fieldMetadataId: string) => {
|
||||
const correspondingColumnDefinition = columnDefinitions.find(
|
||||
@ -100,6 +107,7 @@ export const useHandleToggleColumnFilter = ({
|
||||
await upsertCombinedViewFilter(newFilter);
|
||||
|
||||
selectFilterDefinitionUsedInDropdown({ filterDefinition });
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataId);
|
||||
}
|
||||
|
||||
openDropdown(existingViewFilter?.id ?? newFilterId);
|
||||
@ -112,6 +120,7 @@ export const useHandleToggleColumnFilter = ({
|
||||
currentViewWithCombinedFiltersAndSorts,
|
||||
availableFilterDefinitions,
|
||||
upsertRecordFilter,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -24,11 +24,6 @@ export type SingleRecordSelectMenuItemsProps = {
|
||||
onCancel?: () => void;
|
||||
onRecordSelected: (entity?: RecordForSelect) => void;
|
||||
selectedRecord?: RecordForSelect;
|
||||
SelectAllIcon?: IconComponent;
|
||||
selectAllLabel?: string;
|
||||
isAllRecordsSelected?: boolean;
|
||||
isAllRecordsSelectShown?: boolean;
|
||||
onAllRecordsSelected?: () => void;
|
||||
hotkeyScope?: string;
|
||||
isFiltered: boolean;
|
||||
shouldSelectEmptyOption?: boolean;
|
||||
@ -42,11 +37,6 @@ export const SingleRecordSelectMenuItems = ({
|
||||
onCancel,
|
||||
onRecordSelected,
|
||||
selectedRecord,
|
||||
SelectAllIcon,
|
||||
selectAllLabel,
|
||||
isAllRecordsSelected,
|
||||
isAllRecordsSelectShown,
|
||||
onAllRecordsSelected,
|
||||
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
|
||||
isFiltered,
|
||||
shouldSelectEmptyOption,
|
||||
@ -61,16 +51,7 @@ export const SingleRecordSelectMenuItems = ({
|
||||
}
|
||||
: null;
|
||||
|
||||
const selectAll = isAllRecordsSelectShown
|
||||
? {
|
||||
__typename: '',
|
||||
id: 'select-all',
|
||||
name: selectAllLabel,
|
||||
}
|
||||
: null;
|
||||
|
||||
const recordsInDropdown = [
|
||||
selectAll,
|
||||
selectNone,
|
||||
selectedRecord,
|
||||
...recordsToSelect,
|
||||
@ -87,10 +68,6 @@ export const SingleRecordSelectMenuItems = ({
|
||||
isSelectedItemIdSelector('select-none'),
|
||||
);
|
||||
|
||||
const isSelectedSelectAllButton = useRecoilValue(
|
||||
isSelectedItemIdSelector('select-all'),
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
@ -120,9 +97,7 @@ export const SingleRecordSelectMenuItems = ({
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading && !isFiltered ? (
|
||||
<DropdownMenuSkeletonItem />
|
||||
) : recordsInDropdown.length === 0 &&
|
||||
!isAllRecordsSelectShown &&
|
||||
!loading ? (
|
||||
) : recordsInDropdown.length === 0 && !loading ? (
|
||||
<></>
|
||||
) : (
|
||||
recordsInDropdown?.map((record) => {
|
||||
@ -141,22 +116,6 @@ export const SingleRecordSelectMenuItems = ({
|
||||
)
|
||||
);
|
||||
}
|
||||
case 'select-all': {
|
||||
return (
|
||||
isAllRecordsSelectShown &&
|
||||
selectAllLabel &&
|
||||
onAllRecordsSelected && (
|
||||
<MenuItemSelect
|
||||
key={record.id}
|
||||
onClick={() => onAllRecordsSelected()}
|
||||
LeftIcon={SelectAllIcon}
|
||||
text={selectAllLabel}
|
||||
selected={!!isAllRecordsSelected}
|
||||
hovered={isSelectedSelectAllButton}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<SelectableMenuItemSelect
|
||||
|
||||
@ -8,6 +8,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { EditableFilterChip } from '@/views/components/EditableFilterChip';
|
||||
|
||||
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -34,6 +35,10 @@ export const EditableFilterDropdownButton = ({
|
||||
viewFilterDropdownId,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
viewFilterDropdownId,
|
||||
@ -62,12 +67,14 @@ export const EditableFilterDropdownButton = ({
|
||||
|
||||
if (isDefined(filterDefinition)) {
|
||||
setFilterDefinitionUsedInDropdown(filterDefinition);
|
||||
setFieldMetadataItemIdUsedInDropdown(filterDefinition.fieldMetadataId);
|
||||
setSelectedOperandInDropdown(viewFilter.operand);
|
||||
setSelectedFilter(viewFilter);
|
||||
}
|
||||
}, [
|
||||
availableFilterDefinitions,
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
viewFilter,
|
||||
setSelectedOperandInDropdown,
|
||||
setSelectedFilter,
|
||||
|
||||
Reference in New Issue
Block a user