Redirect to select option creation when no result (#14460)

fix:Ability to add a new option for a multi-select on the fly 
Issues:#13877

---------

Co-authored-by: ehconitin <nitinkoche03@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
ANIMESH DUTTA
2025-10-07 20:09:48 +05:30
committed by GitHub
parent 2445668e96
commit 9c8e0f628e
13 changed files with 242 additions and 17 deletions
@@ -18,6 +18,7 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { MAX_OPTIONS_TO_DISPLAY } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
@@ -145,6 +146,7 @@ export const ObjectFilterDropdownOptionSelect = ({
);
const showNoResult = optionsInDropdown?.length === 0;
const objectRecordsIds = optionsInDropdown.map((option) => option.id);
return (
@@ -155,7 +157,7 @@ export const ObjectFilterDropdownOptionSelect = ({
>
<DropdownMenuItemsContainer hasMaxHeight>
{showNoResult ? (
<MenuItem text="No results" />
<MenuItem text={t`No results`} />
) : (
optionsInDropdown?.map((option) => (
<MenuItemMultiSelect
@@ -0,0 +1,45 @@
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { shouldNavigateBackToMemorizedUrlOnSaveState } from '@/ui/navigation/states/shouldNavigateBackToMemorizedUrlOnSaveState';
import { useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
export const useAddSelectOption = (fieldName: string) => {
const { objectNamePlural } = useParams();
const navigateSettings = useNavigateSettings();
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
const setShouldNavigateBackToMemorizedUrlOnSave = useSetRecoilState(
shouldNavigateBackToMemorizedUrlOnSaveState,
);
const addSelectOption = useCallback(
(optionName: string) => {
if (!fieldName || !objectNamePlural) return;
setNavigationMemorizedUrl(
window.location.pathname + window.location.search,
);
setShouldNavigateBackToMemorizedUrlOnSave(true);
navigateSettings(
SettingsPath.ObjectFieldEdit,
{ objectNamePlural, fieldName },
{ newOption: optionName },
);
},
[
fieldName,
objectNamePlural,
navigateSettings,
setNavigationMemorizedUrl,
setShouldNavigateBackToMemorizedUrlOnSave,
],
);
return { addSelectOption };
};
@@ -0,0 +1,19 @@
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { isNonEmptyString } from '@sniptt/guards';
import { useParams } from 'react-router-dom';
import { PermissionFlagType } from '~/generated-metadata/graphql';
export const useCanAddSelectOption = (fieldName: string) => {
const { objectNamePlural } = useParams();
const userHasPermissionToEditDataModel = useHasPermissionFlag(
PermissionFlagType.DATA_MODEL,
);
const canAddSelectOption =
userHasPermissionToEditDataModel &&
isNonEmptyString(fieldName) &&
isNonEmptyString(objectNamePlural);
return { canAddSelectOption };
};
@@ -1,15 +1,22 @@
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
import { useAddSelectOption } from '@/object-record/record-field/ui/meta-types/hooks/useAddSelectOption';
import { useCanAddSelectOption } from '@/object-record/record-field/ui/meta-types/hooks/useCanAddSelectOption';
import { useMultiSelectField } from '@/object-record/record-field/ui/meta-types/hooks/useMultiSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/ui/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { type FieldMultiSelectValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useContext } from 'react';
export const MultiSelectFieldInput = () => {
const { fieldDefinition, draftValue, setDraftValue } = useMultiSelectField();
const { addSelectOption } = useAddSelectOption(
fieldDefinition?.metadata?.fieldName,
);
const { canAddSelectOption } = useCanAddSelectOption(
fieldDefinition?.metadata?.fieldName,
);
const { onSubmit } = useContext(FieldInputEventContext);
@@ -25,6 +32,13 @@ export const MultiSelectFieldInput = () => {
onSubmit?.({ newValue: draftValue });
};
const handleAddSelectOption = (optionName: string) => {
if (!canAddSelectOption) {
return;
}
addSelectOption(optionName);
};
return (
<MultiSelectInput
selectableListComponentInstanceId={
@@ -35,6 +49,7 @@ export const MultiSelectFieldInput = () => {
onCancel={handleCancel}
onOptionSelected={handleOptionSelected}
values={draftValue}
onAddSelectOption={handleAddSelectOption}
/>
);
};
@@ -1,5 +1,7 @@
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
import { useClearField } from '@/object-record/record-field/ui/hooks/useClearField';
import { useAddSelectOption } from '@/object-record/record-field/ui/meta-types/hooks/useAddSelectOption';
import { useCanAddSelectOption } from '@/object-record/record-field/ui/meta-types/hooks/useCanAddSelectOption';
import { useSelectField } from '@/object-record/record-field/ui/meta-types/hooks/useSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/ui/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
@@ -14,6 +16,12 @@ import { type SelectOption } from 'twenty-ui/input';
export const SelectFieldInput = () => {
const { fieldDefinition, fieldValue } = useSelectField();
const { addSelectOption } = useAddSelectOption(
fieldDefinition?.metadata?.fieldName,
);
const { canAddSelectOption } = useCanAddSelectOption(
fieldDefinition?.metadata?.fieldName,
);
const { onCancel, onSubmit } = useContext(FieldInputEventContext);
@@ -37,6 +45,13 @@ export const SelectFieldInput = () => {
onCancel?.();
};
const handleAddSelectOption = (optionName: string) => {
if (!canAddSelectOption) {
return;
}
addSelectOption(optionName);
};
const handleSubmit = (option: SelectOption) => {
onSubmit?.({ newValue: option.value });
@@ -82,6 +97,7 @@ export const SelectFieldInput = () => {
fieldDefinition.metadata.isNullable ? handleClearField : undefined
}
clearLabel={fieldDefinition.label}
onAddSelectOption={handleAddSelectOption}
/>
);
};