diff --git a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts
index 9cf5f15d3..feaa73ed9 100644
--- a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts
+++ b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts
@@ -1,6 +1,14 @@
import { ThemeColor } from '@/ui/theme/constants/MainColorNames';
import { Field, Relation } from '~/generated-metadata/graphql';
+export type FieldMetadataItemOption = {
+ color: ThemeColor;
+ id: string;
+ label: string;
+ position: number;
+ value: string;
+};
+
export type FieldMetadataItem = Omit<
Field,
| '__typename'
@@ -27,11 +35,5 @@ export type FieldMetadataItem = Omit<
})
| null;
defaultValue?: any;
- options?: {
- color: ThemeColor;
- id: string;
- label: string;
- position: number;
- value: string;
- }[];
+ options?: FieldMetadataItemOption[];
};
diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
index 59e622f86..7dcf29ab0 100644
--- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
+++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
@@ -18,6 +18,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
FieldMetadataType.Link,
FieldMetadataType.FullName,
FieldMetadataType.Relation,
+ FieldMetadataType.Select,
FieldMetadataType.Currency,
].includes(field.type)
) {
@@ -67,5 +68,7 @@ export const formatFieldMetadataItemAsFilterDefinition = ({
? 'TEXT'
: field.type === FieldMetadataType.Relation
? 'RELATION'
- : 'TEXT',
+ : field.type === FieldMetadataType.Select
+ ? 'SELECT'
+ : 'TEXT',
});
diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts
index 736b8b512..4e48dcfa9 100644
--- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts
+++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions.ts
@@ -15,6 +15,7 @@ export const formatFieldMetadataItemsAsSortDefinitions = ({
FieldMetadataType.Number,
FieldMetadataType.Text,
FieldMetadataType.Boolean,
+ FieldMetadataType.Select,
].includes(field.type)
) {
return acc;
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
index 67dc3c3d4..070d8bf8e 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
@@ -1,13 +1,14 @@
-import { ObjectFilterDropdownRecordSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput';
+import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
-import { ObjectFilterDropdownDateSearchInput } from './ObjectFilterDropdownDateSearchInput';
+import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
-import { ObjectFilterDropdownNumberSearchInput } from './ObjectFilterDropdownNumberSearchInput';
+import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInput';
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
+import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
@@ -40,17 +41,24 @@ export const MultipleFiltersDropdownContent = ({
) && }
{['NUMBER', 'CURRENCY'].includes(
filterDefinitionUsedInDropdown.type,
- ) && }
+ ) && }
{filterDefinitionUsedInDropdown.type === 'DATE_TIME' && (
-
+
)}
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
<>
-
+
>
)}
+ {filterDefinitionUsedInDropdown.type === 'SELECT' && (
+ <>
+
+
+
+ >
+ )}
>
)
)}
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx
similarity index 94%
rename from packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx
rename to packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx
index f397312f4..19b5a9897 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateSearchInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx
@@ -2,7 +2,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { isNonNullable } from '~/utils/isNonNullable';
-export const ObjectFilterDropdownDateSearchInput = () => {
+export const ObjectFilterDropdownDateInput = () => {
const {
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx
similarity index 81%
rename from packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberSearchInput.tsx
rename to packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx
index e99411065..aec637714 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberSearchInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput.tsx
@@ -1,9 +1,9 @@
import { ChangeEvent } from 'react';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
-import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
-export const ObjectFilterDropdownNumberSearchInput = () => {
+export const ObjectFilterDropdownNumberInput = () => {
const {
selectedOperandInDropdown,
filterDefinitionUsedInDropdown,
@@ -13,7 +13,7 @@ export const ObjectFilterDropdownNumberSearchInput = () => {
return (
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
- {
+ const {
+ filterDefinitionUsedInDropdown,
+ objectFilterDropdownSearchInput,
+ selectedOperandInDropdown,
+ objectFilterDropdownSelectedOptionValues,
+ selectFilter,
+ } = useFilterDropdown();
+
+ const fieldMetaDataId = filterDefinitionUsedInDropdown?.fieldMetadataId ?? '';
+
+ const { selectOptions } = useOptionsForSelect(fieldMetaDataId);
+
+ const [selectableOptions, setSelectableOptions] = useState<
+ SelectOptionForFilter[]
+ >([]);
+
+ useEffect(() => {
+ if (selectOptions) {
+ const options = selectOptions.map((option) => {
+ const isSelected =
+ objectFilterDropdownSelectedOptionValues?.includes(option.value) ??
+ false;
+
+ return {
+ ...option,
+ isSelected,
+ };
+ });
+
+ setSelectableOptions(options);
+ }
+ }, [objectFilterDropdownSelectedOptionValues, selectOptions]);
+
+ const handleMultipleOptionSelectChange = (
+ optionChanged: SelectOptionForFilter,
+ isSelected: boolean,
+ ) => {
+ if (!selectOptions) {
+ return;
+ }
+
+ const newSelectableOptions = selectableOptions.map((option) =>
+ option.id === optionChanged.id ? { ...option, isSelected } : option,
+ );
+
+ setSelectableOptions(newSelectableOptions);
+
+ const selectedOptions = newSelectableOptions.filter(
+ (option) => option.isSelected,
+ );
+
+ const filterDisplayValue =
+ selectedOptions.length > MAX_OPTIONS_TO_DISPLAY
+ ? `${selectedOptions.length} options`
+ : selectedOptions.map((option) => option.label).join(', ');
+
+ if (filterDefinitionUsedInDropdown && selectedOperandInDropdown) {
+ const newFilterValue =
+ selectedOptions.length > 0
+ ? JSON.stringify(selectedOptions.map((option) => option.value))
+ : EMPTY_FILTER_VALUE;
+
+ selectFilter({
+ definition: filterDefinitionUsedInDropdown,
+ operand: selectedOperandInDropdown,
+ displayValue: filterDisplayValue,
+ fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
+ value: newFilterValue,
+ });
+ }
+ };
+
+ const optionsInDropdown = selectableOptions?.filter((option) =>
+ option.label.toLowerCase().includes(objectFilterDropdownSearchInput),
+ );
+
+ const showNoResult = optionsInDropdown?.length === 0;
+
+ return (
+
+ {optionsInDropdown?.map((option) => (
+
+ handleMultipleOptionSelectChange(option, selected)
+ }
+ text={option.label}
+ className=""
+ />
+ ))}
+ {showNoResult && }
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx
similarity index 93%
rename from packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx
rename to packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx
index 19db840d2..1a45f6aa3 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput.tsx
@@ -3,7 +3,7 @@ import { ChangeEvent } from 'react';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
-export const ObjectFilterDropdownRecordSearchInput = () => {
+export const ObjectFilterDropdownSearchInput = () => {
const {
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
index aa978a47d..8e8d52fe5 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
@@ -13,8 +13,8 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
-import { ObjectFilterDropdownRecordSearchInput } from './ObjectFilterDropdownEntitySearchInput';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
+import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
export const SingleEntityObjectFilterDropdownButton = ({
hotkeyScope,
@@ -66,7 +66,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
}
dropdownComponents={
<>
-
+
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts
index 2b4932cba..90d6f77f1 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts
@@ -27,6 +27,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
+ objectFilterDropdownSelectedOptionValues,
+ setObjectFilterDropdownSelectedOptionValues,
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownUnfolded,
@@ -87,6 +89,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
+ objectFilterDropdownSelectedOptionValues,
+ setObjectFilterDropdownSelectedOptionValues,
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownUnfolded,
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownStates.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownStates.ts
index 3e6d25891..94b969b84 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownStates.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownStates.ts
@@ -8,6 +8,7 @@ import { isObjectFilterDropdownOperandSelectUnfoldedScopedState } from '../state
import { isObjectFilterDropdownUnfoldedScopedState } from '../states/isObjectFilterDropdownUnfoldedScopedState';
import { objectFilterDropdownSearchInputScopedState } from '../states/objectFilterDropdownSearchInputScopedState';
import { objectFilterDropdownSelectedEntityIdScopedState } from '../states/objectFilterDropdownSelectedEntityIdScopedState';
+import { objectFilterDropdownSelectedOptionValuesScopedState } from '../states/objectFilterDropdownSelectedOptionValuesScopedState';
import { selectedFilterScopedState } from '../states/selectedFilterScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
@@ -37,6 +38,14 @@ export const useFilterDropdownStates = (scopeId: string) => {
scopeId,
);
+ const [
+ objectFilterDropdownSelectedOptionValues,
+ setObjectFilterDropdownSelectedOptionValues,
+ ] = useRecoilScopedStateV2(
+ objectFilterDropdownSelectedOptionValuesScopedState,
+ scopeId,
+ );
+
const [
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
@@ -71,6 +80,8 @@ export const useFilterDropdownStates = (scopeId: string) => {
objectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
+ objectFilterDropdownSelectedOptionValues,
+ setObjectFilterDropdownSelectedOptionValues,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useOptionsForSelect.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useOptionsForSelect.ts
new file mode 100644
index 000000000..8db977b57
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useOptionsForSelect.ts
@@ -0,0 +1,26 @@
+import { useParams } from 'react-router-dom';
+
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
+
+export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
+
+export const useOptionsForSelect = (fieldMetadataId: string) => {
+ const objectNamePlural = useParams().objectNamePlural ?? '';
+
+ const { objectNameSingular } = useObjectNameSingularFromPlural({
+ objectNamePlural,
+ });
+
+ const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
+
+ const fieldMetadataItem = objectMetadataItem.fields.find(
+ (field) => field.id === fieldMetadataId,
+ );
+
+ const selectOptions = fieldMetadataItem?.options;
+
+ return {
+ selectOptions,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesScopedState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesScopedState.ts
new file mode 100644
index 000000000..372da4c5d
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesScopedState.ts
@@ -0,0 +1,7 @@
+import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
+
+export const objectFilterDropdownSelectedOptionValuesScopedState =
+ createStateScopeMap({
+ key: 'objectFilterDropdownSelectedOptionValuesScopedState',
+ defaultValue: [],
+ });
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts
index 2c182f0a8..7ec43a222 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts
@@ -7,4 +7,5 @@ export type FilterType =
| 'CURRENCY'
| 'FULL_NAME'
| 'LINK'
- | 'RELATION';
+ | 'RELATION'
+ | 'SELECT';
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts
index cd70c9334..94c6f92d5 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts
@@ -16,6 +16,7 @@ export const getOperandsForFilterType = (
case 'DATE_TIME':
return [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan];
case 'RELATION':
+ case 'SELECT':
return [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
default:
return [];
diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx
index 4c730f429..94127f562 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx
@@ -1,10 +1,13 @@
+import { useState } from 'react';
import styled from '@emotion/styled';
-import { MenuItem } from 'tsup.ui.index';
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { MenuItemSelectTag } from '@/ui/navigation/menu-item/components/MenuItemSelectTag';
const StyledRelationPickerContainer = styled.div`
left: -1px;
@@ -17,16 +20,36 @@ export type SelectFieldInputProps = {
};
export const SelectFieldInput = ({ onSubmit }: SelectFieldInputProps) => {
- const { persistField, fieldDefinition } = useSelectField();
+ const { persistField, fieldDefinition, fieldValue } = useSelectField();
+ const [searchFilter, setSearchFilter] = useState('');
+
+ const selectedOption = fieldDefinition.metadata.options.find(
+ (option) => option.value === fieldValue,
+ );
+ const optionsToSelect =
+ fieldDefinition.metadata.options.filter((option) => {
+ return option.value !== fieldValue && option.label.includes(searchFilter);
+ }) || [];
+ const optionsInDropDown = selectedOption
+ ? [selectedOption, ...optionsToSelect]
+ : optionsToSelect;
return (
-
- {fieldDefinition.metadata.options.map((option) => {
+ setSearchFilter(event.currentTarget.value)}
+ autoFocus
+ />
+
+
+ {optionsInDropDown.map((option) => {
return (
-