diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx index 43f5210bd..750d3ab41 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx @@ -6,6 +6,7 @@ import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { GraphQLView } from '@/views/types/GraphQLView'; +import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews'; export const ObjectMetadataNavItems = () => { const { activeObjectMetadataItems } = useObjectMetadataItemForSettings(); @@ -13,7 +14,9 @@ export const ObjectMetadataNavItems = () => { const { getIcon } = useIcons(); const currentPath = useLocation().pathname; - const { records } = usePrefetchedData(PrefetchKey.AllViews); + const { records: views } = usePrefetchedData( + PrefetchKey.AllViews, + ); return ( <> @@ -45,9 +48,11 @@ export const ObjectMetadataNavItems = () => { : -1; }), ].map((objectMetadataItem) => { - const viewId = records?.find( - (view: any) => view?.objectMetadataId === objectMetadataItem.id, - )?.id; + const objectMetadataViews = getObjectMetadataItemViews( + objectMetadataItem.id, + views, + ); + const viewId = objectMetadataViews[0]?.id; const navigationPath = `/objects/${objectMetadataItem.namePlural}${ viewId ? `?view=${viewId}` : '' diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts index 1c8f8a184..7758ca97f 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardStates.ts @@ -8,6 +8,7 @@ import { onRecordBoardFetchMoreVisibilityChangeComponentState } from '@/object-r import { recordBoardColumnIdsComponentState } from '@/object-record/record-board/states/recordBoardColumnIdsComponentState'; import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState'; import { recordBoardFiltersComponentState } from '@/object-record/record-board/states/recordBoardFiltersComponentState'; +import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState'; import { recordBoardObjectSingularNameComponentState } from '@/object-record/record-board/states/recordBoardObjectSingularNameComponentState'; import { recordBoardRecordIdsByColumnIdComponentFamilyState } from '@/object-record/record-board/states/recordBoardRecordIdsByColumnIdComponentFamilyState'; import { recordBoardSortsComponentState } from '@/object-record/record-board/states/recordBoardSortsComponentState'; @@ -32,6 +33,10 @@ export const useRecordBoardStates = (recordBoardId?: string) => { recordBoardObjectSingularNameComponentState, scopeId, ), + kanbanFieldMetadataNameState: extractComponentState( + recordBoardKanbanFieldMetadataNameComponentState, + scopeId, + ), isFetchingRecordState: extractComponentState( isRecordBoardFetchingRecordsComponentState, scopeId, diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds.ts index 90f5a1ad9..8ead97030 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds.ts @@ -10,6 +10,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => { recordIdsByColumnIdFamilyState, columnsFamilySelector, columnIdsState, + kanbanFieldMetadataNameState, } = useRecordBoardStates(recordBoardId); const setRecordIds = useRecoilCallback( @@ -26,8 +27,18 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => { .getLoadable(recordIdsByColumnIdFamilyState(columnId)) .getValue(); + const kanbanFieldMetadataName = snapshot + .getLoadable(kanbanFieldMetadataNameState) + .getValue(); + + if (!kanbanFieldMetadataName) { + return; + } + const columnRecordIds = records - .filter((record) => record.stage === column?.value) + .filter( + (record) => record[kanbanFieldMetadataName] === column?.value, + ) .sort(sortRecordsByPosition) .map((record) => record.id); @@ -36,7 +47,12 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => { } }); }, - [columnsFamilySelector, columnIdsState, recordIdsByColumnIdFamilyState], + [ + columnIdsState, + columnsFamilySelector, + recordIdsByColumnIdFamilyState, + kanbanFieldMetadataNameState, + ], ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts index f6a266d47..25a358e7a 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoard.ts @@ -12,12 +12,16 @@ export const useRecordBoard = (recordBoardId?: string) => { selectedRecordIdsSelector, isCompactModeActiveState, onFetchMoreVisibilityChangeState, + kanbanFieldMetadataNameState, } = useRecordBoardStates(recordBoardId); const { setColumns } = useSetRecordBoardColumns(recordBoardId); const { setRecordIds } = useSetRecordBoardRecordIds(recordBoardId); const setFieldDefinitions = useSetRecoilState(fieldDefinitionsState); const setObjectSingularName = useSetRecoilState(objectSingularNameState); + const setKanbanFieldMetadataName = useSetRecoilState( + kanbanFieldMetadataNameState, + ); return { scopeId, @@ -25,6 +29,7 @@ export const useRecordBoard = (recordBoardId?: string) => { setRecordIds, setFieldDefinitions, setObjectSingularName, + setKanbanFieldMetadataName, selectedRecordIdsSelector, isCompactModeActiveState, onFetchMoreVisibilityChangeState, diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState.ts new file mode 100644 index 000000000..26490c929 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState.ts @@ -0,0 +1,7 @@ +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const recordBoardKanbanFieldMetadataNameComponentState = + createComponentState({ + key: 'recordBoardKanbanFieldMetadataNameComponentState', + defaultValue: undefined, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx index 3e7fff5bd..df517ccbe 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainerEffect.tsx @@ -8,7 +8,10 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; +import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isDefined } from '~/utils/isDefined'; type RecordIndexBoardContainerEffectProps = { objectNameSingular: string; @@ -31,6 +34,7 @@ export const RecordIndexBoardContainerEffect = ({ selectedRecordIdsSelector, setFieldDefinitions, onFetchMoreVisibilityChangeState, + setKanbanFieldMetadataName, } = useRecordBoard(recordBoardId); const { fetchMoreRecords, loading } = useLoadRecordIndexBoard({ @@ -43,6 +47,10 @@ export const RecordIndexBoardContainerEffect = ({ onFetchMoreVisibilityChangeState, ); + const recordIndexKanbanFieldMetadataId = useRecoilValue( + recordIndexKanbanFieldMetadataIdState, + ); + useEffect(() => { setOnFetchMoreVisibilityChange(() => () => { if (!loading) { @@ -67,6 +75,7 @@ export const RecordIndexBoardContainerEffect = ({ setColumns( computeRecordBoardColumnDefinitionsFromObjectMetadata( objectMetadataItem, + recordIndexKanbanFieldMetadataId ?? '', navigateToSelectSettings, ), ); @@ -74,6 +83,7 @@ export const RecordIndexBoardContainerEffect = ({ navigateToSelectSettings, objectMetadataItem, objectNameSingular, + recordIndexKanbanFieldMetadataId, setColumns, ]); @@ -85,6 +95,24 @@ export const RecordIndexBoardContainerEffect = ({ setFieldDefinitions(recordIndexFieldDefinitions); }, [objectMetadataItem, setFieldDefinitions, recordIndexFieldDefinitions]); + useEffect(() => { + if (isDefined(recordIndexKanbanFieldMetadataId)) { + const kanbanFieldMetadataName = objectMetadataItem?.fields.find( + (field) => + field.type === FieldMetadataType.Select && + field.id === recordIndexKanbanFieldMetadataId, + )?.name; + + if (isDefined(kanbanFieldMetadataName)) { + setKanbanFieldMetadataName(kanbanFieldMetadataName); + } + } + }, [ + objectMetadataItem, + recordIndexKanbanFieldMetadataId, + setKanbanFieldMetadataName, + ]); + const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector()); const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({ diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 1d9e89b25..3fceb3f2c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -10,10 +10,10 @@ import { RecordIndexTableContainer } from '@/object-record/record-index/componen import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect'; import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect'; import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdown'; -import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState'; +import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; @@ -65,6 +65,9 @@ export const RecordIndexContainer = ({ const setRecordIndexIsCompactModeActive = useSetRecoilState( recordIndexIsCompactModeActiveState, ); + const setRecordIndexViewKanbanFieldMetadataIdState = useSetRecoilState( + recordIndexKanbanFieldMetadataIdState, + ); const { setTableFilters, setTableSorts, setTableColumns } = useRecordTable({ recordTableId: recordIndexId, @@ -129,9 +132,11 @@ export const RecordIndexContainer = ({ mapViewSortsToSorts(view.viewSorts, sortDefinitions), ); setRecordIndexViewType(view.type); + setRecordIndexViewKanbanFieldMetadataIdState( + view.kanbanFieldMetadataId, + ); setRecordIndexIsCompactModeActive(view.isCompact); }} - optionsDropdownScopeId={RECORD_INDEX_OPTIONS_DROPDOWN_ID} /> { - const { setViewEditMode } = useViewBarEditMode(recordIndexId); - return ( } - onClickOutside={() => setViewEditMode('none')} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx index 19f1e2594..9d48f624c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx @@ -1,6 +1,5 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; import { Key } from 'ts-key-enum'; -import { v4 } from 'uuid'; import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId'; import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard'; @@ -14,7 +13,6 @@ import { IconTag, } from '@/ui/display/icon'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; -import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; @@ -23,8 +21,6 @@ import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemTog import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; -import { useHandleViews } from '@/views/hooks/useHandleViews'; -import { useViewBarEditMode } from '@/views/hooks/useViewBarEditMode'; import { ViewType } from '@/views/types/ViewType'; type RecordIndexOptionsMenu = 'fields'; @@ -40,9 +36,6 @@ export const RecordIndexOptionsDropdownContent = ({ recordIndexId, objectNameSingular, }: RecordIndexOptionsDropdownContentProps) => { - const { updateCurrentView, createEmptyView, selectView } = - useHandleViews(recordIndexId); - const { viewEditMode, setViewEditMode } = useViewBarEditMode(recordIndexId); const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); const { closeDropdown } = useDropdown(RECORD_INDEX_OPTIONS_DROPDOWN_ID); @@ -53,8 +46,6 @@ export const RecordIndexOptionsDropdownContent = ({ const resetMenu = () => setCurrentMenu(undefined); - const viewEditInputRef = useRef(null); - const handleSelectMenu = (option: RecordIndexOptionsMenu) => { setCurrentMenu(option); }; @@ -67,25 +58,6 @@ export const RecordIndexOptionsDropdownContent = ({ TableOptionsHotkeyScope.Dropdown, ); - useScopedHotkeys( - Key.Enter, - async () => { - const name = viewEditInputRef.current?.value; - if (viewEditMode === 'create') { - const id = v4(); - await createEmptyView(id, name ?? ''); - selectView(id); - } else { - updateCurrentView({ name }); - } - - resetMenu(); - setViewEditMode('none'); - closeDropdown(); - }, - TableOptionsHotkeyScope.Dropdown, - ); - const { handleColumnVisibilityChange, handleReorderColumns, @@ -128,37 +100,18 @@ export const RecordIndexOptionsDropdownContent = ({ return ( <> {!currentMenu && ( - <> - + handleSelectMenu('fields')} + LeftIcon={IconTag} + text="Fields" /> - - - handleSelectMenu('fields')} - LeftIcon={IconTag} - text="Fields" - /> - openRecordSpreadsheetImport()} - LeftIcon={IconFileImport} - text="Import" - /> - - + openRecordSpreadsheetImport()} + LeftIcon={IconFileImport} + text="Import" + /> + )} {currentMenu === 'fields' && ( <> diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts new file mode 100644 index 000000000..5ff8ba4fe --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts @@ -0,0 +1,8 @@ +import { createState } from '@/ui/utilities/state/utils/createState'; + +export const recordIndexKanbanFieldMetadataIdState = createState( + { + key: 'recordIndexKanbanFieldMetadataIdState', + defaultValue: null, + }, +); diff --git a/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts b/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts index 5af6540ae..8e70a4e5c 100644 --- a/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts +++ b/packages/twenty-front/src/modules/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata.ts @@ -5,10 +5,13 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; export const computeRecordBoardColumnDefinitionsFromObjectMetadata = ( objectMetadataItem: ObjectMetadataItem, + kanbanFieldMetadataId: string, navigateToSelectSettings: () => void, ): RecordBoardColumnDefinition[] => { const selectFieldMetadataItem = objectMetadataItem.fields.find( - (field) => field.type === FieldMetadataType.Select, + (field) => + field.id === kanbanFieldMetadataId && + field.type === FieldMetadataType.Select, ); if (!selectFieldMetadataItem) { diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx index 447ca98aa..699b82af7 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdown'; -import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect'; import { ViewBar } from '@/views/components/ViewBar'; @@ -32,7 +31,6 @@ export const SignInBackgroundMockContainer = () => { viewType={ViewType.Table} /> } - optionsDropdownScopeId={RECORD_INDEX_OPTIONS_DROPDOWN_ID} /> ) => void; @@ -28,7 +29,13 @@ export type ButtonProps = { const StyledButton = styled.button< Pick< ButtonProps, - 'fullWidth' | 'variant' | 'size' | 'position' | 'accent' | 'focus' + | 'fullWidth' + | 'variant' + | 'size' + | 'position' + | 'accent' + | 'focus' + | 'justify' > >` align-items: center; @@ -177,9 +184,7 @@ const StyledButton = styled.button< `; case 'danger': return css` - background: ${!disabled - ? theme.background.transparent.primary - : 'transparent'}; + background: transparent; border-color: ${variant === 'secondary' ? focus ? theme.color.red @@ -236,6 +241,7 @@ const StyledButton = styled.button< font-weight: 500; gap: ${({ theme }) => theme.spacing(1)}; height: ${({ size }) => (size === 'small' ? '24px' : '32px')}; + justify-content: ${({ justify }) => justify}; padding: ${({ theme }) => { return `0 ${theme.spacing(2)}`; }}; @@ -266,6 +272,7 @@ export const Button = ({ position = 'standalone', soon = false, disabled = false, + justify = 'flex-start', focus = false, onClick, }: ButtonProps) => { @@ -279,6 +286,7 @@ export const Button = ({ position={position} disabled={soon || disabled} focus={focus} + justify={justify} accent={accent} className={className} onClick={onClick} diff --git a/packages/twenty-front/src/modules/ui/input/button/components/LightIconButtonGroup.tsx b/packages/twenty-front/src/modules/ui/input/button/components/LightIconButtonGroup.tsx index ff548e5df..adb20d64b 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/LightIconButtonGroup.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/LightIconButtonGroup.tsx @@ -31,6 +31,7 @@ export const LightIconButtonGroup = ({ diff --git a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx index 2c4c19516..ebc209d63 100644 --- a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx @@ -30,6 +30,7 @@ type IconPickerProps = { onOpen?: () => void; variant?: IconButtonVariant; className?: string; + disableBlur?: boolean; }; const StyledMenuIconItemsContainer = styled.div` @@ -86,6 +87,7 @@ export const IconPicker = ({ onClose, onOpen, variant = 'secondary', + disableBlur = false, className, }: IconPickerProps) => { const [searchString, setSearchString] = useState(''); @@ -148,6 +150,7 @@ export const IconPicker = ({ /> } dropdownMenuWidth={176} + disableBlur={disableBlur} dropdownComponents={ = { export type SelectProps = { className?: string; disabled?: boolean; + disableBlur?: boolean; dropdownId: string; dropdownWidth?: `${string}px` | 'auto' | number; emptyOption?: SelectOption; @@ -75,6 +76,7 @@ const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>` export const Select = ({ className, disabled: disabledFromProps, + disableBlur = false, dropdownId, dropdownWidth = 176, emptyOption, @@ -141,6 +143,7 @@ export const Select = ({ dropdownMenuWidth={dropdownWidth} dropdownPlacement="bottom-start" clickableComponent={selectControl} + disableBlur={disableBlur} dropdownComponents={ <> {!!withSearchInput && ( diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx index 6d303a54c..88d8479be 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -35,6 +35,7 @@ type DropdownProps = { dropdownPlacement?: Placement; dropdownMenuWidth?: `${string}px` | `${number}%` | 'auto' | number; dropdownOffset?: { x?: number; y?: number }; + disableBlur?: boolean; onClickOutside?: () => void; onClose?: () => void; onOpen?: () => void; @@ -50,6 +51,7 @@ export const Dropdown = ({ dropdownHotkeyScope, dropdownPlacement = 'bottom-end', dropdownOffset = { x: 0, y: 0 }, + disableBlur = false, onClickOutside, onClose, onOpen, @@ -109,7 +111,10 @@ export const Dropdown = ({ {clickableComponent && (
{ + toggleDropdown(); + onClickOutside?.(); + }} className={className} > {clickableComponent} @@ -123,6 +128,7 @@ export const Dropdown = ({ )} {isDropdownOpen && ( theme.background.transparent.forBackdropFilter}; + background: ${({ theme, disableBlur }) => + disableBlur + ? theme.background.primary + : theme.background.transparent.secondary}; border: 1px solid ${({ theme }) => theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.md}; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader.tsx index 68446f9a9..3c652c86e 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuHeader.tsx @@ -11,14 +11,11 @@ const StyledHeader = styled.li` display: flex; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme }) => theme.font.weight.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; padding: ${({ theme }) => theme.spacing(1)}; user-select: none; - - &:hover { - background: ${({ theme }) => theme.background.transparent.light}; - } `; const StyledChildrenWrapper = styled.span` @@ -46,9 +43,10 @@ export const DropdownMenuHeader = ({ testId, }: DropdownMenuHeaderProps) => { return ( - + {StartIcon && ( {children} {EndIcon && ( ->(({ autoFocus, defaultValue, placeholder, onChange }, ref) => { +>(({ autoFocus, value, placeholder, onChange }, ref) => { return ( - + {optionsMock.map(({ name }) => ( diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuInput.stories.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuInput.stories.tsx index 21e5d7653..42838fca3 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuInput.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/__stories__/DropdownMenuInput.stories.tsx @@ -8,7 +8,7 @@ const meta: Meta = { title: 'UI/Layout/Dropdown/DropdownMenuInput', component: DropdownMenuInput, decorators: [ComponentDecorator], - args: { defaultValue: 'Lorem ipsum' }, + args: { value: 'Lorem ipsum' }, }; export default meta; diff --git a/packages/twenty-front/src/modules/views/components/FilterQueryParamsEffect.tsx b/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx similarity index 60% rename from packages/twenty-front/src/modules/views/components/FilterQueryParamsEffect.tsx rename to packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx index ad9d60800..25278a77c 100644 --- a/packages/twenty-front/src/modules/views/components/FilterQueryParamsEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx @@ -1,33 +1,24 @@ import { useEffect } from 'react'; -import { isUndefined } from '@sniptt/guards'; import { useSetRecoilState } from 'recoil'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; import { useViewStates } from '@/views/hooks/internal/useViewStates'; import { useResetCurrentView } from '@/views/hooks/useResetCurrentView'; -export const FilterQueryParamsEffect = () => { - const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } = +export const QueryParamsFiltersEffect = () => { + const { hasFiltersQueryParams, getFiltersFromQueryParams } = useViewFromQueryParams(); - const { unsavedToUpsertViewFiltersState, currentViewIdState } = - useViewStates(); + const { unsavedToUpsertViewFiltersState } = useViewStates(); const setUnsavedViewFilter = useSetRecoilState( unsavedToUpsertViewFiltersState, ); - const setCurrentViewId = useSetRecoilState(currentViewIdState); const { resetCurrentView } = useResetCurrentView(); useEffect(() => { - if (isUndefined(viewIdQueryParam) || !viewIdQueryParam) { + if (!hasFiltersQueryParams) { return; } - setCurrentViewId(viewIdQueryParam); - }, [getFiltersFromQueryParams, setCurrentViewId, viewIdQueryParam]); - - useEffect(() => { - if (!hasFiltersQueryParams) return; - getFiltersFromQueryParams().then((filtersFromParams) => { if (Array.isArray(filtersFromParams)) { setUnsavedViewFilter(filtersFromParams); @@ -44,5 +35,5 @@ export const FilterQueryParamsEffect = () => { setUnsavedViewFilter, ]); - return null; + return <>; }; diff --git a/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx b/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx new file mode 100644 index 000000000..a7130fb3b --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; +import { isUndefined } from '@sniptt/guards'; +import { useRecoilState } from 'recoil'; + +import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { useViewStates } from '@/views/hooks/internal/useViewStates'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; +import { isDefined } from '~/utils/isDefined'; + +export const QueryParamsViewIdEffect = () => { + const { getFiltersFromQueryParams, viewIdQueryParam } = + useViewFromQueryParams(); + const { currentViewIdState } = useViewStates(); + + const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState); + const { viewsOnCurrentObject } = useGetCurrentView(); + + useEffect(() => { + const indexView = viewsOnCurrentObject.find((view) => view.key === 'INDEX'); + + if (isUndefined(viewIdQueryParam) && isDefined(indexView)) { + setCurrentViewId(indexView.id); + return; + } + + if (isDefined(viewIdQueryParam)) { + setCurrentViewId(viewIdQueryParam); + } + }, [ + currentViewId, + getFiltersFromQueryParams, + setCurrentViewId, + viewIdQueryParam, + viewsOnCurrentObject, + ]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx index d4dac54f4..0bc6c4316 100644 --- a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx +++ b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx @@ -7,14 +7,18 @@ import { Button } from '@/ui/input/button/components/Button'; import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { UPDATE_VIEW_DROPDOWN_ID } from '@/views/constants/UpdateViewDropdownId'; +import { UPDATE_VIEW_BUTTON_DROPDOWN_ID } from '@/views/constants/UpdateViewButtonDropdownId'; import { useViewStates } from '@/views/hooks/internal/useViewStates'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts'; +import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; +import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; +import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates'; const StyledContainer = styled.div` - background: ${({ theme }) => theme.color.blue}; border-radius: ${({ theme }) => theme.border.radius.md}; display: inline-flex; margin-right: ${({ theme }) => theme.spacing(2)}; @@ -23,25 +27,50 @@ const StyledContainer = styled.div` export type UpdateViewButtonGroupProps = { hotkeyScope: HotkeyScope; - onViewEditModeChange?: () => void; }; export const UpdateViewButtonGroup = ({ hotkeyScope, - onViewEditModeChange, }: UpdateViewButtonGroupProps) => { - const { canPersistViewSelector, viewEditModeState } = useViewStates(); + const { canPersistViewSelector, currentViewIdState } = useViewStates(); const { saveCurrentViewFilterAndSorts } = useSaveCurrentViewFiltersAndSorts(); - const setViewEditMode = useSetRecoilState(viewEditModeState); + const { setViewPickerMode } = useViewPickerMode(); + const { viewPickerReferenceViewIdState } = useViewPickerStates(); const canPersistView = useRecoilValue(canPersistViewSelector()); - const handleCreateViewButtonClick = useCallback(() => { - setViewEditMode('create'); - onViewEditModeChange?.(); - }, [setViewEditMode, onViewEditModeChange]); + const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown( + UPDATE_VIEW_BUTTON_DROPDOWN_ID, + ); + const { openDropdown: openViewPickerDropdown } = useDropdown( + VIEW_PICKER_DROPDOWN_ID, + ); + const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); - const handleViewSubmit = async () => { + const currentViewId = useRecoilValue(currentViewIdState); + + const setViewPickerReferenceViewId = useSetRecoilState( + viewPickerReferenceViewIdState, + ); + + const handleViewCreate = useCallback(() => { + if (!currentViewId) { + return; + } + openViewPickerDropdown(); + setViewPickerReferenceViewId(currentViewId); + setViewPickerMode('create'); + + closeUpdateViewButtonDropdown(); + }, [ + closeUpdateViewButtonDropdown, + currentViewId, + openViewPickerDropdown, + setViewPickerMode, + setViewPickerReferenceViewId, + ]); + + const handleViewUpdate = async () => { await saveCurrentViewFilterAndSorts(); }; @@ -51,27 +80,42 @@ export const UpdateViewButtonGroup = ({ return ( - -