From c8e4d0ab9a63ba5f1c57195b96c8937b7e0cbd9e Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 31 Jan 2024 11:37:03 +0100 Subject: [PATCH] Board compact view and Company Picker for opportunity special case (#3713) * Re-enabled board compact mode * Add specific case for opportunity to display company picker * Add infinite scroll * Remove useEffect * Fix * Fix --- .../hooks/internal/useRecordBoardStates.ts | 11 +++ .../record-board/hooks/useRecordBoard.ts | 4 + .../components/RecordBoardCard.tsx | 42 +++++++-- .../RecordBoardColumnCardsContainer.tsx | 18 ++-- .../RecordBoardColumnFetchMoreLoader.tsx | 36 ++++++++ .../RecordBoardColumnNewOpportunityButton.tsx | 90 +++++++++++++++++++ ...RecordBoardFetchingRecordsStateScopeMap.ts | 7 ++ ...dFetchMoreVisibilityChangeStateScopeMap.ts | 7 ++ .../utils/computeDraftValueFromFieldValue.ts | 21 +++-- .../utils/computeEmptyDraftValue.ts | 4 +- .../components/RecordIndexBoardContainer.tsx | 6 +- .../RecordIndexBoardContainerEffect.tsx | 38 +++++--- .../RecordIndexOptionsDropdownContent.tsx | 25 +++++- .../hooks/useRecordIndexOptionsForBoard.ts | 20 ++++- .../action-bar/components/ActionBar.tsx | 5 +- .../context-menu/components/ContextMenu.tsx | 3 - 16 files changed, 291 insertions(+), 46 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardFetchingRecordsStateScopeMap.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/states/onRecordBoardFetchMoreVisibilityChangeStateScopeMap.ts 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 63b98c8046..291db2655e 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 @@ -3,6 +3,8 @@ import { isFirstRecordBoardColumnFamilyStateScopeMap } from '@/object-record/rec import { isLastRecordBoardColumnFamilyStateScopeMap } from '@/object-record/record-board/states/isLastRecordBoardColumnFamilyStateScopeMap'; import { isRecordBoardCardSelectedFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardCardSelectedFamilyStateScopeMap'; import { isRecordBoardCompactModeActiveStateScopeMap } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveStateScopeMap'; +import { isRecordBoardFetchingRecordsStateScopeMap } from '@/object-record/record-board/states/isRecordBoardFetchingRecordsStateScopeMap'; +import { onRecordBoardFetchMoreVisibilityChangeStateScopeMap } from '@/object-record/record-board/states/onRecordBoardFetchMoreVisibilityChangeStateScopeMap'; import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap'; import { recordBoardFieldDefinitionsStateScopeMap } from '@/object-record/record-board/states/recordBoardFieldDefinitionsStateScopeMap'; import { recordBoardFiltersStateScopeMap } from '@/object-record/record-board/states/recordBoardFiltersStateScopeMap'; @@ -30,6 +32,10 @@ export const useRecordBoardStates = (recordBoardId?: string) => { recordBoardObjectSingularNameStateScopeMap, scopeId, ), + getIsFetchingRecordState: getState( + isRecordBoardFetchingRecordsStateScopeMap, + scopeId, + ), getColumnIdsState: getState(recordBoardColumnIdsStateScopeMap, scopeId), isFirstColumnFamilyState: getFamilyState( isFirstRecordBoardColumnFamilyStateScopeMap, @@ -72,5 +78,10 @@ export const useRecordBoardStates = (recordBoardId?: string) => { isRecordBoardCompactModeActiveStateScopeMap, scopeId, ), + + getOnFetchMoreVisibilityChangeState: getState( + onRecordBoardFetchMoreVisibilityChangeStateScopeMap, + scopeId, + ), }; }; 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 486bce6e1a..59429b2546 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 @@ -10,6 +10,8 @@ export const useRecordBoard = (recordBoardId?: string) => { getFieldDefinitionsState, getObjectSingularNameState, getSelectedRecordIdsSelector, + getIsCompactModeActiveState, + getOnFetchMoreVisibilityChangeState, } = useRecordBoardStates(recordBoardId); const { setColumns } = useSetRecordBoardColumns(recordBoardId); @@ -24,5 +26,7 @@ export const useRecordBoard = (recordBoardId?: string) => { setFieldDefinitions, setObjectSingularName, getSelectedRecordIdsSelector, + getIsCompactModeActiveState, + getOnFetchMoreVisibilityChangeState, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index efbc96d1f7..15f2424e97 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -1,4 +1,5 @@ import { ReactNode, useContext, useState } from 'react'; +import { useInView } from 'react-intersection-observer'; import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; @@ -20,6 +21,7 @@ import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut'; +import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper'; const StyledBoardCard = styled.div<{ selected: boolean }>` background-color: ${({ theme, selected }) => @@ -119,22 +121,26 @@ const StyledCompactIconContainer = styled.div` align-items: center; display: flex; justify-content: center; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledRecordInlineCellPlaceholder = styled.div` + height: 24px; `; export const RecordBoardCard = () => { const { recordId } = useContext(RecordBoardCardContext); - const { updateOneRecord } = useContext(RecordBoardContext); + const { updateOneRecord, objectMetadataItem } = + useContext(RecordBoardContext); const { - getObjectSingularNameState, getIsCompactModeActiveState, isRecordBoardCardSelectedFamilyState, getVisibleFieldDefinitionsState, } = useRecordBoardStates(); const isCompactModeActive = useRecoilValue(getIsCompactModeActiveState()); - const objectNameSingular = useRecoilValue(getObjectSingularNameState()); - const [isCardInCompactMode, setIsCardInCompactMode] = - useState(isCompactModeActive); + + const [isCardInCompactMode, setIsCardInCompactMode] = useState(true); const [isCurrentCardSelected, setIsCurrentCardSelected] = useRecoilState( isRecordBoardCardSelectedFamilyState(recordId), @@ -190,13 +196,21 @@ export const RecordBoardCard = () => { return [updateEntity, { loading: false }]; }; - if (!objectNameSingular || !record) { + const scrollWrapperRef = useContext(ScrollWrapperContext); + + const { ref: cardRef, inView } = useInView({ + root: scrollWrapperRef.current, + rootMargin: '1000px', + }); + + if (!record) { return null; } return ( { @@ -204,7 +218,10 @@ export const RecordBoardCard = () => { }} > - + {isCompactModeActive && ( { - + {visibleBoardCardFieldDefinitions.map((fieldDefinition) => ( { hotkeyScope: InlineCellHotkeyScope.InlineCell, }} > - + {inView ? ( + + ) : ( + + )} ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx index 236a431455..96d14a011b 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx @@ -2,14 +2,14 @@ import React, { useContext } from 'react'; import styled from '@emotion/styled'; import { Draggable, DroppableProvided } from '@hello-pangea/dnd'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo'; +import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader'; import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton'; +import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; -const StyledPlaceholder = styled.div` - min-height: 1px; -`; - const StyledColumnCardsContainer = styled.div` display: flex; flex: 1; @@ -30,6 +30,7 @@ export const RecordBoardColumnCardsContainer = ({ droppableProvided, }: RecordBoardColumnCardsContainerProps) => { const { columnDefinition } = useContext(RecordBoardColumnContext); + const { objectMetadataItem } = useContext(RecordBoardContext); return ( - {droppableProvided?.placeholder} + - + {objectMetadataItem.nameSingular === + CoreObjectNameSingular.Opportunity ? ( + + ) : ( + + )} )} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader.tsx new file mode 100644 index 0000000000..98ca2e8666 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader.tsx @@ -0,0 +1,36 @@ +import { useInView } from 'react-intersection-observer'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; + +import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { grayScale } from '@/ui/theme/constants/colors'; + +const StyledText = styled.div` + align-items: center; + box-shadow: none; + color: ${grayScale.gray40}; + display: flex; + height: 32px; + margin-left: ${({ theme }) => theme.spacing(8)}; + padding-left: ${({ theme }) => theme.spacing(2)}; +`; + +export const RecordBoardColumnFetchMoreLoader = () => { + const { getIsFetchingRecordState, getOnFetchMoreVisibilityChangeState } = + useRecordBoardStates(); + const isFetchingRecords = useRecoilValue(getIsFetchingRecordState()); + + const onFetchMoreVisibilityChange = useRecoilValue( + getOnFetchMoreVisibilityChangeState(), + ); + + const { ref } = useInView({ + onChange: onFetchMoreVisibilityChange, + }); + + return ( +
+ {isFetchingRecords && Loading more...} +
+ ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx new file mode 100644 index 0000000000..9cac6da4ff --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx @@ -0,0 +1,90 @@ +import { useCallback, useContext, useState } from 'react'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; +import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; +import { IconPlus } from '@/ui/display/icon'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; + +const StyledButton = styled.button` + align-items: center; + align-self: baseline; + background-color: ${({ theme }) => theme.background.primary}; + border: none; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ theme }) => theme.font.color.tertiary}; + cursor: pointer; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(1)}; + + &:hover { + background-color: ${({ theme }) => theme.background.tertiary}; + } +`; + +export const RecordBoardColumnNewOpportunityButton = () => { + const [isCreatingCard, setIsCreatingCard] = useState(false); + + const theme = useTheme(); + const { columnDefinition } = useContext(RecordBoardColumnContext); + const { createOneRecord, selectFieldMetadataItem } = + useContext(RecordBoardContext); + + const { + goBackToPreviousHotkeyScope, + setHotkeyScopeAndMemorizePreviousScope, + } = usePreviousHotkeyScope(); + + const handleEntitySelect = (company?: EntityForSelect) => { + setIsCreatingCard(false); + goBackToPreviousHotkeyScope(); + + if (!company) { + return; + } + + createOneRecord({ + name: company.name, + companyId: company.id, + [selectFieldMetadataItem.name]: columnDefinition.value, + }); + }; + + const handleNewClick = useCallback(() => { + setIsCreatingCard(true); + setHotkeyScopeAndMemorizePreviousScope( + RelationPickerHotkeyScope.RelationPicker, + ); + }, [setIsCreatingCard, setHotkeyScopeAndMemorizePreviousScope]); + + const handleCancel = () => { + goBackToPreviousHotkeyScope(); + setIsCreatingCard(false); + }; + + return ( + <> + {isCreatingCard ? ( + + ) : ( + + + New + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardFetchingRecordsStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardFetchingRecordsStateScopeMap.ts new file mode 100644 index 0000000000..83c35a4be9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardFetchingRecordsStateScopeMap.ts @@ -0,0 +1,7 @@ +import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap'; + +export const isRecordBoardFetchingRecordsStateScopeMap = + createStateScopeMap({ + key: 'isRecordBoardFetchingRecordsStateScopeMap', + defaultValue: false, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/onRecordBoardFetchMoreVisibilityChangeStateScopeMap.ts b/packages/twenty-front/src/modules/object-record/record-board/states/onRecordBoardFetchMoreVisibilityChangeStateScopeMap.ts new file mode 100644 index 0000000000..fb5da620aa --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/onRecordBoardFetchMoreVisibilityChangeStateScopeMap.ts @@ -0,0 +1,7 @@ +import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap'; + +export const onRecordBoardFetchMoreVisibilityChangeStateScopeMap = + createStateScopeMap<(visbility: boolean) => void>({ + key: 'onRecordBoardFetchMoreVisibilityChangeStateScopeMap', + defaultValue: () => {}, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index bca25224da..ee11e822cb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -4,6 +4,7 @@ import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldIn import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; +import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { computeEmptyDraftValue } from '@/object-record/record-field/utils/computeEmptyDraftValue'; import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty'; @@ -21,18 +22,20 @@ export const computeDraftValueFromFieldValue = ({ // than the intputDraftValue type as string can be typed anywhere if (isFieldCurrency(fieldDefinition)) { - if (isFieldValueEmpty({ fieldValue, fieldDefinition })) { + if ( + isFieldValueEmpty({ fieldValue, fieldDefinition }) || + !isFieldCurrencyValue(fieldValue) + ) { return computeEmptyDraftValue({ fieldDefinition }); } - if (isFieldCurrencyValue(fieldValue)) { - return { - amount: fieldValue?.amountMicros - ? fieldValue.amountMicros / 1000000 - : '', - currenyCode: CurrencyCode.USD, - } as unknown as FieldInputDraftValue; - } + return { + amount: fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : '', + currenyCode: CurrencyCode.USD, + } as unknown as FieldInputDraftValue; + } + if (isFieldRelation(fieldDefinition)) { + return computeEmptyDraftValue({ fieldDefinition }); } return fieldValue as FieldInputDraftValue; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts index 5a680ff13a..2af0ea25c1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts @@ -8,6 +8,7 @@ import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldE import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; +import { isFieldRelationValue } from '@/object-record/record-field/types/guards/isFieldRelationValue'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; @@ -24,7 +25,8 @@ export const computeEmptyDraftValue = ({ isFieldText(fieldDefinition) || isFieldDateTime(fieldDefinition) || isFieldNumber(fieldDefinition) || - isFieldEmail(fieldDefinition) + isFieldEmail(fieldDefinition) || + isFieldRelationValue(fieldDefinition) ) { return '' as FieldInputDraftValue; } diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx index d11fcb4ac4..2fd30265fc 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx @@ -1,4 +1,4 @@ -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; @@ -19,7 +19,9 @@ export const RecordIndexBoardContainer = ({ recordBoardId, objectNameSingular, }: RecordIndexBoardContainerProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); + const { objectMetadataItem } = useObjectMetadataItemOnly({ + objectNameSingular, + }); const selectFieldMetadataItem = objectMetadataItem.fields.find( (field) => field.type === FieldMetadataType.Select, 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 fc430fd162..44f0ab12af 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 @@ -1,8 +1,8 @@ import { useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly'; import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar'; import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; @@ -21,11 +21,35 @@ export const RecordIndexBoardContainerEffect = ({ recordBoardId, viewBarId, }: RecordIndexBoardContainerEffectProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ + const { objectMetadataItem } = useObjectMetadataItemOnly({ objectNameSingular, }); - useLoadRecordIndexBoard({ objectNameSingular, recordBoardId, viewBarId }); + const { + setColumns, + setObjectSingularName, + getSelectedRecordIdsSelector, + setFieldDefinitions, + getOnFetchMoreVisibilityChangeState, + } = useRecordBoard(recordBoardId); + + const { fetchMoreRecords, loading } = useLoadRecordIndexBoard({ + objectNameSingular, + recordBoardId, + viewBarId, + }); + + const setOnFetchMoreVisibilityChange = useSetRecoilState( + getOnFetchMoreVisibilityChangeState(), + ); + + useEffect(() => { + setOnFetchMoreVisibilityChange(() => () => { + if (!loading) { + fetchMoreRecords?.(); + } + }); + }, [fetchMoreRecords, loading, setOnFetchMoreVisibilityChange]); const navigate = useNavigate(); @@ -33,12 +57,6 @@ export const RecordIndexBoardContainerEffect = ({ navigate(`/settings/objects/${objectMetadataItem.namePlural}`); }, [navigate, objectMetadataItem.namePlural]); - const { - setColumns, - setObjectSingularName, - getSelectedRecordIdsSelector, - setFieldDefinitions, - } = useRecordBoard(recordBoardId); const { resetRecordSelection } = useRecordBoardSelection(recordBoardId); useEffect(() => { 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 8d13086047..8c1f855970 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 @@ -7,13 +7,19 @@ import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/opti import { useRecordIndexOptionsForTable } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForTable'; import { useRecordIndexOptionsImport } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsImport'; import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope'; -import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/display/icon'; +import { + IconBaselineDensitySmall, + IconChevronLeft, + IconFileImport, + 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'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; +import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; @@ -86,8 +92,11 @@ export const RecordIndexOptionsDropdownContent = ({ hiddenBoardFields, handleReorderBoardFields, handleBoardFieldVisibilityChange, + isCompactModeActive, + setIsCompactModeActive, } = useRecordIndexOptionsForBoard({ objectNameSingular, + recordBoardId: recordIndexId, viewBarId: recordIndexId, }); @@ -170,6 +179,20 @@ export const RecordIndexOptionsDropdownContent = ({ )} )} + {viewType === ViewType.Kanban && ( + <> + + + + + + )} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard.ts index 040403db74..027ca3cafe 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard.ts @@ -5,32 +5,48 @@ import { useRecoilState } from 'recoil'; import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly'; +import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { useViewFields } from '@/views/hooks/internal/useViewFields'; type useRecordIndexOptionsForBoardParams = { objectNameSingular: string; + recordBoardId: string; viewBarId: string; }; export const useRecordIndexOptionsForBoard = ({ objectNameSingular, + recordBoardId, viewBarId, }: useRecordIndexOptionsForBoardParams) => { const [recordIndexFieldDefinitions, setRecordIndexFieldDefinitions] = useRecoilState(recordIndexFieldDefinitionsState); const { persistViewFields } = useViewFields(viewBarId); + const { getIsCompactModeActiveState } = useRecordBoard(recordBoardId); + + const [isCompactModeActive, setIsCompactModeActive] = useRecoilState( + getIsCompactModeActiveState(), + ); const { objectMetadataItem } = useObjectMetadataItemOnly({ objectNameSingular, }); - const { columnDefinitions } = + const { columnDefinitions: availableColumnDefinitions } = useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + // Todo replace this with label identifier logic + const columnDefinitions = availableColumnDefinitions + .filter( + (columnDefinition) => columnDefinition.metadata.fieldName !== 'name', + ) + .filter(filterAvailableTableColumns); + const visibleBoardFields = useMemo( () => columnDefinitions.filter((columnDefinition) => { @@ -152,5 +168,7 @@ export const useRecordIndexOptionsForBoard = ({ handleBoardFieldVisibilityChange, visibleBoardFields, hiddenBoardFields, + isCompactModeActive, + setIsCompactModeActive, }; }; diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx index d5d1996b3b..bf5c2586a0 100644 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx @@ -5,8 +5,6 @@ import { useRecoilValue } from 'recoil'; import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; -import { actionBarOpenState } from '../states/actionBarIsOpenState'; - import { ActionBarItem } from './ActionBarItem'; const StyledContainerActionBar = styled.div` @@ -30,12 +28,11 @@ const StyledContainerActionBar = styled.div` `; export const ActionBar = () => { - const actionBarOpen = useRecoilValue(actionBarOpenState); const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const actionBarEntries = useRecoilValue(actionBarEntriesState); const wrapperRef = useRef(null); - if (!actionBarOpen || contextMenuIsOpen) { + if (contextMenuIsOpen) { return null; } diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx index 0c1ee250bc..8a3a1cfd0e 100644 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx @@ -4,7 +4,6 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -42,14 +41,12 @@ export const ContextMenu = () => { const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const contextMenuEntries = useRecoilValue(contextMenuEntriesState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); - const setActionBarOpenState = useSetRecoilState(actionBarOpenState); const wrapperRef = useRef(null); useListenClickOutside({ refs: [wrapperRef], callback: () => { setContextMenuOpenState(false); - setActionBarOpenState(true); }, });