diff --git a/.cursor/rules/code-style.mdc b/.cursor/rules/code-style.mdc index b3df1da557..77473f09a0 100644 --- a/.cursor/rules/code-style.mdc +++ b/.cursor/rules/code-style.mdc @@ -80,6 +80,38 @@ const processUserData = ( }; ``` +## Collection Transformations +```typescript +// ✅ Prefer array transformations for simple mapping, filtering, and classification +const activeUsers = users.filter((user) => user.isActive === true); +const userNames = users.map((user) => user.name); + +const { payingUsers, nonPayingUsers } = users.reduce<{ + payingUsers: User[]; + nonPayingUsers: User[]; +}>( + (accumulator, user) => { + if (user.isPaying === true) { + accumulator.payingUsers.push(user); + } else { + accumulator.nonPayingUsers.push(user); + } + + return accumulator; + }, + { payingUsers: [], nonPayingUsers: [] }, +); + +// ❌ Avoid manual loops when map, filter, or reduce keeps the code clear +const payingUsers: User[] = []; + +for (const user of users) { + if (user.isPaying === true) { + payingUsers.push(user); + } +} +``` + ## Comments ```typescript // ✅ Use short-form comments, NOT JSDoc blocks diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx index 5c66cdcf64..aa6faeeb22 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx @@ -190,6 +190,7 @@ export const ChartSettingItem = ({ } dropdownPlacement="bottom-end" + dropdownOffset={{ y: 4 }} description={getChartSettingsValues(item.id) as string} contextualTextPosition={'right'} hasSubMenu diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx index 20e0369c48..4b7766010b 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx @@ -6,9 +6,10 @@ import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/ import { useResetChartDraftFiltersSettings } from '@/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; -import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; -import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { + StyledPageLayoutDropdownContentContainer, + StyledPageLayoutDropdownMenuItemsContainer, +} from '@/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; @@ -19,19 +20,13 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { t } from '@lingui/core/macro'; -import { Trans } from '@lingui/react/macro'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { IconChevronLeft, IconSettings } from 'twenty-ui/icon'; -import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; +import { MenuItemSelect } from 'twenty-ui/navigation'; import { filterBySearchQuery } from '~/utils/filterBySearchQuery'; -const ADVANCED_OBJECTS_MENU_ITEM_ID = 'advanced-objects'; - export const ChartDataSourceDropdownContent = () => { const [searchQuery, setSearchQuery] = useState(''); - const [isAdvancedObjectsMenuOpened, setIsAdvancedObjectsMenuOpened] = - useState(false); const { objectMetadataItems } = useObjectMetadataItems(); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const { pageLayoutId } = usePageLayoutIdFromContextStore(); @@ -51,21 +46,25 @@ export const ChartDataSourceDropdownContent = () => { dropdownId, ); - const objectsWithReadAccess = filterReadableActiveObjectMetadataItems( - objectMetadataItems, - objectPermissionsByObjectMetadataId, - ); + const searchableObjects = useMemo(() => { + const objectsWithReadAccess = filterReadableActiveObjectMetadataItems( + objectMetadataItems, + objectPermissionsByObjectMetadataId, + ); - const regularObjects = objectsWithReadAccess - .filter((item) => !item.isSystem) - .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + const regularObjects = objectsWithReadAccess + .filter((item) => item.isSystem === false) + .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); - const systemObjects = objectsWithReadAccess - .filter((item) => item.isSystem) - .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + const systemObjects = objectsWithReadAccess + .filter((item) => item.isSystem === true) + .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural)); + + return [...regularObjects, ...systemObjects]; + }, [objectMetadataItems, objectPermissionsByObjectMetadataId]); const availableObjectMetadataItems = filterBySearchQuery({ - items: isAdvancedObjectsMenuOpened ? systemObjects : regularObjects, + items: searchableObjects, searchQuery, getSearchableValues: (item) => [item.labelPlural, item.namePlural], }); @@ -104,30 +103,8 @@ export const ChartDataSourceDropdownContent = () => { closeDropdown(); }; - const handleAdvancedObjectsClick = () => { - setIsAdvancedObjectsMenuOpened(true); - setSearchQuery(''); - }; - - const handleBack = () => { - setIsAdvancedObjectsMenuOpened(false); - setSearchQuery(''); - }; - return ( - <> - {isAdvancedObjectsMenuOpened && ( - - } - > - Advanced objects - - )} + { value={searchQuery} /> - + objectMetadataItem.id, - ), - ...(!isAdvancedObjectsMenuOpened - ? [ADVANCED_OBJECTS_MENU_ITEM_ID] - : []), - ]} + selectableItemIdArray={availableObjectMetadataItems.map( + (objectMetadataItem) => objectMetadataItem.id, + )} > {availableObjectMetadataItems.map((objectMetadataItem) => ( { /> ))} - {!isAdvancedObjectsMenuOpened && ( - - - - )} - - + + ); }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx index 1778703d3c..a7f6ec35af 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx @@ -11,9 +11,10 @@ import { import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; -import { DropdownAdvancedSectionHeader } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionHeader'; -import { DropdownAdvancedSectionMenuItem } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionMenuItem'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { + StyledPageLayoutDropdownContentContainer, + StyledPageLayoutDropdownMenuItemsContainer, +} from '@/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; @@ -24,7 +25,6 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { t } from '@lingui/core/macro'; -import { isNonEmptyString } from '@sniptt/guards'; import { useMemo, useState } from 'react'; import { useIcons } from 'twenty-ui/icon'; import { MenuItemSelect } from 'twenty-ui/navigation'; @@ -33,7 +33,6 @@ import { filterBySearchQuery } from '~/utils/filterBySearchQuery'; export const FieldWidgetFieldDropdownContent = () => { const [searchQuery, setSearchQuery] = useState(''); - const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); const { pageLayoutId, objectNameSingular } = usePageLayoutIdFromContextStore(); @@ -51,25 +50,30 @@ export const FieldWidgetFieldDropdownContent = () => { const { objectMetadataItems } = useObjectMetadataItems(); - const advancedFieldMetadataItems = useMemo( + const { advancedFieldMetadataItems, regularFieldMetadataItems } = useMemo( () => - allFieldWidgetFieldMetadataItems.filter((fieldMetadataItem) => - isAdvancedRelationFieldMetadataItem( - fieldMetadataItem, - objectMetadataItems, - ), - ), - [allFieldWidgetFieldMetadataItems, objectMetadataItems], - ); - - const regularFieldMetadataItems = useMemo( - () => - allFieldWidgetFieldMetadataItems.filter( - (fieldMetadataItem) => - !isAdvancedRelationFieldMetadataItem( + allFieldWidgetFieldMetadataItems.reduce<{ + advancedFieldMetadataItems: typeof allFieldWidgetFieldMetadataItems; + regularFieldMetadataItems: typeof allFieldWidgetFieldMetadataItems; + }>( + (accumulator, fieldMetadataItem) => { + const isAdvancedField = isAdvancedRelationFieldMetadataItem( fieldMetadataItem, objectMetadataItems, - ), + ); + + if (isAdvancedField) { + accumulator.advancedFieldMetadataItems.push(fieldMetadataItem); + } else { + accumulator.regularFieldMetadataItems.push(fieldMetadataItem); + } + + return accumulator; + }, + { + advancedFieldMetadataItems: [], + regularFieldMetadataItems: [], + }, ), [allFieldWidgetFieldMetadataItems, objectMetadataItems], ); @@ -92,30 +96,17 @@ export const FieldWidgetFieldDropdownContent = () => { const { getIcon } = useIcons(); + const searchableFieldMetadataItems = [ + ...regularFieldMetadataItems, + ...advancedFieldMetadataItems, + ]; + const availableFields = filterBySearchQuery({ - items: isAdvancedOpen - ? advancedFieldMetadataItems - : regularFieldMetadataItems, + items: searchableFieldMetadataItems, searchQuery, getSearchableValues: (item) => [item.label], }); - const shouldShowAdvanced = - !isAdvancedOpen && - advancedFieldMetadataItems.length > 0 && - (!isNonEmptyString(searchQuery) || - searchQuery.toLowerCase().includes('advanced')); - - const handleOpenAdvanced = () => { - setIsAdvancedOpen(true); - setSearchQuery(''); - }; - - const handleBackFromAdvanced = () => { - setIsAdvancedOpen(false); - setSearchQuery(''); - }; - const { fieldMetadataItem: currentFieldMetadataItem } = useFieldMetadataItemById(currentFieldMetadataId ?? ''); @@ -146,7 +137,7 @@ export const FieldWidgetFieldDropdownContent = () => { }, }); - if (widgetInEditMode && selectedField) { + if (isDefined(widgetInEditMode) && isDefined(selectedField)) { updatePageLayoutWidget(widgetInEditMode.id, { title: selectedField.label, }); @@ -156,10 +147,7 @@ export const FieldWidgetFieldDropdownContent = () => { }; return ( - <> - {isAdvancedOpen && ( - - )} + { value={searchQuery} /> - + { ))} - {shouldShowAdvanced && ( - - )} - - + + ); }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx new file mode 100644 index 0000000000..17320e283c --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx @@ -0,0 +1,19 @@ +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { styled } from '@linaria/react'; + +const PAGE_LAYOUT_DROPDOWN_CONTENT_MAX_HEIGHT = 340; + +export const StyledPageLayoutDropdownContentContainer = styled.div` + display: flex; + flex-direction: column; + max-height: ${PAGE_LAYOUT_DROPDOWN_CONTENT_MAX_HEIGHT}px; + min-height: 0; + overflow: hidden; +`; + +export const StyledPageLayoutDropdownMenuItemsContainer = styled( + DropdownMenuItemsContainer, +)` + flex: 1; + min-height: 0; +`; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx index e8509a2317..1586bece65 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx @@ -204,6 +204,7 @@ export const WorkflowEditActionFindRecords = ({ dropdownComponents={ !isFormDisabled && ( ) diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx index 159932a25d..44b46e6167 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx @@ -1,36 +1,38 @@ import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { DropdownAdvancedSectionHeader } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionHeader'; -import { DropdownAdvancedSectionMenuItem } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionMenuItem'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; -import { isNonEmptyString } from '@sniptt/guards'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useState } from 'react'; import { MenuItem } from 'twenty-ui/navigation'; type WorkflowObjectDropdownContentProps = { + dropdownId: string; onOptionClick: (value: string) => void; - showAdvancedOption?: boolean; }; export const WorkflowObjectDropdownContent = ({ + dropdownId, onOptionClick, - showAdvancedOption = true, }: WorkflowObjectDropdownContentProps) => { const [searchInputValue, setSearchInputValue] = useState(''); - const [isSystemObjectsOpen, setIsSystemObjectsOpen] = useState(false); const { objectMetadataItems } = useFilteredObjectMetadataItems(); const nonSystemObjectMetadataItems = objectMetadataItems.filter( (objectMetadataItem) => - objectMetadataItem.isActive && !objectMetadataItem.isSystem, + objectMetadataItem.isActive === true && + objectMetadataItem.isSystem === false, ); const systemObjectMetadataItems = objectMetadataItems.filter( (objectMetadataItem) => - objectMetadataItem.isActive && objectMetadataItem.isSystem, + objectMetadataItem.isActive === true && + objectMetadataItem.isSystem === true, ); const matchesSearchFilter = ( @@ -52,12 +54,6 @@ export const WorkflowObjectDropdownContent = ({ const searchInputLowerCase = searchInputValue.toLowerCase(); - const shouldShowAdvanced = - showAdvancedOption && - !isSystemObjectsOpen && - (!isNonEmptyString(searchInputValue) || - searchInputLowerCase.includes('advanced')); - const filteredNonSystemObjects = nonSystemObjectMetadataItems.filter( (objectMetadataItem) => matchesSearchFilter(objectMetadataItem, searchInputLowerCase), @@ -68,19 +64,19 @@ export const WorkflowObjectDropdownContent = ({ matchesSearchFilter(objectMetadataItem, searchInputLowerCase), ); - const filteredObjects = isSystemObjectsOpen - ? filteredSystemObjects - : filteredNonSystemObjects; + const filteredObjects = [ + ...filteredNonSystemObjects, + ...filteredSystemObjects, + ]; - const handleSystemObjectsClick = () => { - setIsSystemObjectsOpen(true); - setSearchInputValue(''); - }; + const selectableItemIdArray = filteredObjects.map( + (objectMetadataItem) => objectMetadataItem.nameSingular, + ); - const handleBack = () => { - setIsSystemObjectsOpen(false); - setSearchInputValue(''); - }; + const selectedItemId = useAtomComponentStateValue( + selectedItemIdComponentState, + dropdownId, + ); const handleSearchInputChange = ( event: React.ChangeEvent, @@ -88,17 +84,8 @@ export const WorkflowObjectDropdownContent = ({ setSearchInputValue(event.target.value); }; - const handleAdvancedClick = () => { - if (!isSystemObjectsOpen) { - handleSystemObjectsClick(); - } - }; - return ( - {isSystemObjectsOpen && ( - - )} - {filteredObjects.map((objectMetadataItem) => ( - ( - - )} - text={objectMetadataItem.labelPlural} - onClick={() => onOptionClick(objectMetadataItem.nameSingular)} - /> - ))} - {shouldShowAdvanced && ( - - )} + + {filteredObjects.map((objectMetadataItem) => ( + onOptionClick(objectMetadataItem.nameSingular)} + > + ( + + )} + text={objectMetadataItem.labelPlural} + onClick={() => onOptionClick(objectMetadataItem.nameSingular)} + /> + + ))} + ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx index 74f378c4d9..c11448d7e7 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx @@ -259,6 +259,7 @@ export const WorkflowEditActionPickRecord = ({ dropdownComponents={ !isFormDisabled && ( ) @@ -293,6 +294,7 @@ export const WorkflowEditActionPickRecord = ({ dropdownComponents={ !isFormDisabled && ( ) diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx index e5c92ed199..11029db6cb 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx @@ -4,13 +4,15 @@ import { type FieldMultiSelectValue } from '@/object-record/record-field/ui/type import { SelectControl } from '@/ui/input/components/SelectControl'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; -import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; -import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { WorkflowFieldsMultiSelect } from '@/workflow/components/WorkflowEditUpdateEventFieldsMultiSelect'; import { type WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow'; import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; @@ -19,11 +21,11 @@ import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/Workflo import { WorkflowStepFilterBuilder } from '@/workflow/workflow-steps/filters/components/WorkflowStepFilterBuilder'; import { type FilterSettings } from '@/workflow/workflow-steps/filters/types/FilterSettings'; import { styled } from '@linaria/react'; -import { Trans, useLingui } from '@lingui/react/macro'; -import { useCallback, useMemo, useState } from 'react'; +import { useLingui } from '@lingui/react/macro'; +import { useMemo, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; -import { IconChevronLeft, IconSettings } from 'twenty-ui/icon'; +import { type SelectOption } from 'twenty-ui/input'; import { MenuItem } from 'twenty-ui/navigation'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -39,13 +41,18 @@ const StyledRecordTypeSelectContainer = styled.div<{ fullWidth?: boolean }>` width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; `; -const filterOptionsBySearch = ( +const filterOptionsBySearch = ( options: T[], searchValue: string, ): T[] => { - if (!searchValue) return options; + if (searchValue === '') return options; + + const searchValueLowerCase = searchValue.toLowerCase(); + return options.filter((option) => - option.label.toLowerCase().includes(searchValue.toLowerCase()), + [option.label, option.value].some((searchableValue) => + searchableValue.toLowerCase().includes(searchValueLowerCase), + ), ); }; @@ -70,7 +77,6 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ const { getSelectIconPropsFromObjectMetadataItem } = useObjectMetadataSelectHelpers(); const [searchInputValue, setSearchInputValue] = useState(''); - const [isSystemObjectsOpen, setIsSystemObjectsOpen] = useState(false); const dropdownId = 'workflow-edit-trigger-record-type'; const { closeDropdown } = useCloseDropdown(); @@ -84,29 +90,46 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ const isUpsertEvent = triggerEvent.event === 'upserted'; const isFieldFilteringSupported = isUpdateEvent || isUpsertEvent; - const defaultSelectedOption = useMemo( + const defaultSelectedOption = useMemo>( () => ({ label: t`Select an option`, value: '' }), [t], ); - const regularObjects = objectMetadataItems - .filter((item) => item.isActive && !item.isSystem) - .map((item) => ({ - label: item.labelPlural, - value: item.nameSingular, - ...getSelectIconPropsFromObjectMetadataItem(item), - })); + const { regularObjects, systemObjects } = useMemo(() => { + return objectMetadataItems.reduce<{ + regularObjects: SelectOption[]; + systemObjects: SelectOption[]; + }>( + (accumulator, item) => { + if (item.isActive === false) { + return accumulator; + } - const systemObjects = objectMetadataItems - .filter((item) => item.isActive && item.isSystem) - .map((item) => ({ - label: item.labelPlural, - value: item.nameSingular, - ...getSelectIconPropsFromObjectMetadataItem(item), - })); + const option = { + label: item.labelPlural, + value: item.nameSingular, + ...getSelectIconPropsFromObjectMetadataItem(item), + }; + + if (item.isSystem === true) { + accumulator.systemObjects.push(option); + } else { + accumulator.regularObjects.push(option); + } + + return accumulator; + }, + { regularObjects: [], systemObjects: [] }, + ); + }, [getSelectIconPropsFromObjectMetadataItem, objectMetadataItems]); + + const selectableOptions = useMemo( + () => [...regularObjects, ...systemObjects], + [regularObjects, systemObjects], + ); const selectedOption = - [...regularObjects, ...systemObjects].find( + selectableOptions.find( (option) => option.value === triggerEvent?.objectType, ) || defaultSelectedOption; @@ -114,14 +137,19 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ (item) => item.nameSingular === selectedOption.value, ); - const filteredRegularObjects = useMemo( - () => filterOptionsBySearch(regularObjects, searchInputValue), - [regularObjects, searchInputValue], + const filteredObjects = useMemo( + () => [ + ...filterOptionsBySearch(regularObjects, searchInputValue), + ...filterOptionsBySearch(systemObjects, searchInputValue), + ], + [regularObjects, searchInputValue, systemObjects], ); - const filteredSystemObjects = useMemo( - () => filterOptionsBySearch(systemObjects, searchInputValue), - [systemObjects, searchInputValue], + const selectableItemIdArray = filteredObjects.map((option) => option.value); + + const selectedItemId = useAtomComponentStateValue( + selectedItemIdComponentState, + dropdownId, ); const handleOptionClick = (value: string) => { @@ -174,30 +202,13 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ }); }; - const handleSystemObjectsClick = () => { - setIsSystemObjectsOpen(true); - setSearchInputValue(''); - }; - - const handleBack = () => { - setIsSystemObjectsOpen(false); - setSearchInputValue(''); - }; - - const handleSearchInputChange = useCallback( - (event: React.ChangeEvent) => { - setSearchInputValue(event.target.value); - }, - [], - ); - return ( <> {t`Record Type`} - {!triggerOptions.readonly && - (isSystemObjectsOpen ? ( - - - } + {!triggerOptions.readonly && ( + + + setSearchInputValue(event.target.value) + } + /> + + + - Advanced - - - - - {filteredSystemObjects.map((option) => ( - ( + handleOptionClick(option.value)} - /> + itemId={option.value} + onEnter={() => handleOptionClick(option.value)} + > + handleOptionClick(option.value)} + /> + ))} - - - ) : ( - - - - - {filteredRegularObjects.map((option) => ( - handleOptionClick(option.value)} - /> - ))} - {(!searchInputValue || - 'advanced'.includes( - searchInputValue.toLowerCase(), - )) && ( - - )} - - - ))} + + + + )} } dropdownOffset={{ y: 4 }}