diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDataChangedEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDataChangedEffect.tsx index 3f6f9ca13d..fa13327fe2 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDataChangedEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDataChangedEffect.tsx @@ -1,6 +1,7 @@ import { useStore } from 'jotai'; import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent'; +import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail'; import { useGetShouldInitializeRecordBoardForUpdateInputs } from '@/object-record/record-board/hooks/useGetShouldInitializeRecordBoardForUpdateInputs'; import { useRemoveRecordsFromBoard } from '@/object-record/record-board/hooks/useRemoveRecordsFromBoard'; import { useTriggerRecordBoardInitialQuery } from '@/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery'; @@ -9,7 +10,6 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail'; import { useAtomComponentFamilySelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorCallbackState'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx index af78d6cd2e..8d086c637e 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx @@ -1,4 +1,5 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState'; import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector'; import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag'; import { useProcessBoardCardDrop } from '@/object-record/record-drag/hooks/useProcessBoardCardDrop'; @@ -8,14 +9,14 @@ import { originalDragSelectionComponentState } from '@/object-record/record-drag import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState'; -import { useStore } from 'jotai'; +import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { DragDropContext, type DragStart, type OnDragEndResponder, } from '@hello-pangea/dnd'; +import { useStore } from 'jotai'; import { useCallback, useContext } from 'react'; export const RecordBoardDragDropContext = ({ @@ -35,7 +36,7 @@ export const RecordBoardDragDropContext = ({ const store = useStore(); - const originalDragSelection = useAtomComponentStateCallbackState( + const originalDragSelectionCallbackState = useAtomComponentStateCallbackState( originalDragSelectionComponentState, recordBoardId, ); @@ -45,41 +46,70 @@ export const RecordBoardDragDropContext = ({ const { processBoardCardDrop } = useProcessBoardCardDrop(); + const isRecordBoardDropProcessingCallbackState = + useAtomComponentStateCallbackState( + isRecordBoardDropProcessingComponentState, + ); + const { openModal } = useModal(); const handleDragStart = useCallback( (start: DragStart) => { const currentSelectedRecordIds = store.get(recordBoardSelectedRecordIds); + store.set(isRecordBoardDropProcessingCallbackState, true); + startRecordDrag(start, currentSelectedRecordIds); }, - [recordBoardSelectedRecordIds, startRecordDrag, store], + [ + recordBoardSelectedRecordIds, + startRecordDrag, + store, + isRecordBoardDropProcessingCallbackState, + ], ); const handleDragEnd: OnDragEndResponder = useCallback( (result) => { - endRecordDrag(); + const originalDragSelection = store.get( + originalDragSelectionCallbackState, + ); - if (!result.destination) return; + if (!result.destination) { + store.set(isRecordBoardDropProcessingCallbackState, false); + endRecordDrag(); + return; + } const existingRecordSorts = store.get(currentRecordSorts); if (existingRecordSorts.length > 0) { + store.set(isRecordBoardDropProcessingCallbackState, false); + endRecordDrag(); openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID); return; } - const originalSelection = store.get(originalDragSelection) as string[]; + try { + processBoardCardDrop(result, originalDragSelection); + } catch (error) { + store.set(isRecordBoardDropProcessingCallbackState, false); + endRecordDrag(); - processBoardCardDrop(result, originalSelection); + throw error; + } + + store.set(isRecordBoardDropProcessingCallbackState, false); + endRecordDrag(); }, [ processBoardCardDrop, - originalDragSelection, endRecordDrag, currentRecordSorts, openModal, store, + originalDragSelectionCallbackState, + isRecordBoardDropProcessingCallbackState, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardFetchMoreInViewTriggerComponent.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardFetchMoreInViewTriggerComponent.tsx index ca0bfc75f2..5bd6a44e39 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardFetchMoreInViewTriggerComponent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardFetchMoreInViewTriggerComponent.tsx @@ -7,8 +7,8 @@ import { RECORD_BOARD_COLUMN_PADDING_AND_BORDER_WIDTH } from '@/object-record/re import { RECORD_BOARD_COLUMN_WIDTH } from '@/object-record/record-board/constants/RecordBoardColumnWidth'; import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/constants/RecordBoardQueryPageSize'; -import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState'; import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; import { recordIndexRecordGroupsAreInInitialLoadingComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupsAreInInitialLoadingComponentState'; @@ -38,12 +38,12 @@ export const RecordBoardFetchMoreInViewTriggerComponent = () => { const [recordBoardShouldFetchMore, setRecordBoardShouldFetchMore] = useAtomComponentState(recordBoardShouldFetchMoreComponentState); - const recordIndexRecordGroupsAreInInitialLoading = useAtomComponentStateValue( - recordIndexRecordGroupsAreInInitialLoadingComponentState, + const isDraggingRecord = useAtomComponentStateValue( + isDraggingRecordComponentState, ); - const recordBoardIsFetchingMore = useAtomComponentStateValue( - recordBoardIsFetchingMoreComponentState, + const recordIndexRecordGroupsAreInInitialLoading = useAtomComponentStateValue( + recordIndexRecordGroupsAreInInitialLoadingComponentState, ); const visibleRecordFields = useAtomComponentSelectorValue( @@ -76,22 +76,21 @@ export const RecordBoardFetchMoreInViewTriggerComponent = () => { 1; useEffect(() => { - if ( - !recordIndexRecordGroupsAreInInitialLoading && - !recordBoardIsFetchingMore - ) { - const newShouldFetchMore = inView; + if (recordIndexRecordGroupsAreInInitialLoading || isDraggingRecord) { + return; + } - if (recordBoardShouldFetchMore !== newShouldFetchMore) { - setRecordBoardShouldFetchMore(newShouldFetchMore); - } + const newShouldFetchMore = inView; + + if (recordBoardShouldFetchMore !== newShouldFetchMore) { + setRecordBoardShouldFetchMore(newShouldFetchMore); } }, [ recordBoardShouldFetchMore, setRecordBoardShouldFetchMore, inView, recordIndexRecordGroupsAreInInitialLoading, - recordBoardIsFetchingMore, + isDraggingRecord, ]); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardQueryEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardQueryEffect.tsx index 74af4fcf6d..d17b2eebc3 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardQueryEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardQueryEffect.tsx @@ -3,8 +3,8 @@ import { useTriggerRecordBoardInitialQuery } from '@/object-record/record-board/ import { lastRecordBoardQueryIdentifierComponentState } from '@/object-record/record-board/states/lastRecordBoardQueryIdentifierComponentState'; import { lastRecordGroupIdsComponentState } from '@/object-record/record-board/states/lastRecordGroupIdsComponentState'; import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState'; -import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState'; import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexGroupCommonQueryVariables } from '@/object-record/record-index/hooks/useRecordIndexGroupCommonQueryVariables'; @@ -22,6 +22,10 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; export const RecordBoardQueryEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); + const isDraggingRecord = useAtomComponentStateValue( + isDraggingRecordComponentState, + ); + const [lastRecordBoardQueryIdentifier, setLastRecordBoardQueryIdentifier] = useAtomComponentState(lastRecordBoardQueryIdentifierComponentState); @@ -51,14 +55,10 @@ export const RecordBoardQueryEffect = () => { const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement(); - const [recordBoardShouldFetchMore] = useAtomComponentState( + const recordBoardShouldFetchMore = useAtomComponentStateValue( recordBoardShouldFetchMoreComponentState, ); - const recordBoardIsFetchingMore = useAtomComponentStateValue( - recordBoardIsFetchingMoreComponentState, - ); - const { triggerRecordBoardFetchMore } = useTriggerRecordBoardFetchMore(); const { triggerRecordBoardInitialQuery } = @@ -73,6 +73,10 @@ export const RecordBoardQueryEffect = () => { ); useEffect(() => { + if (isDraggingRecord) { + return; + } + if ( !recordIndexRecordGroupsAreInInitialLoading && (queryIdentifierHasChanged || recordGroupIdsHaveChanged) @@ -82,8 +86,7 @@ export const RecordBoardQueryEffect = () => { } else if ( !recordIndexRecordGroupsAreInInitialLoading && recordBoardShouldFetchMore && - !queryIdentifierHasChanged && - !recordBoardIsFetchingMore + !queryIdentifierHasChanged ) { triggerRecordBoardFetchMore(); } @@ -96,11 +99,11 @@ export const RecordBoardQueryEffect = () => { scrollWrapperHTMLElement, recordIndexRecordGroupsAreInInitialLoading, recordBoardShouldFetchMore, - recordBoardIsFetchingMore, triggerRecordBoardFetchMore, setLastRecordGroupIds, recordGroupIds, recordGroupIdsHaveChanged, + isDraggingRecord, ]); return null; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardFetchMore.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardFetchMore.ts index 2f06cceec1..cf140b7a6f 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardFetchMore.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardFetchMore.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react'; import { useStore } from 'jotai'; +import { useCallback } from 'react'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { RECORD_BOARD_FETCH_MORE_THROTTLING_WAIT_TIME_IN_MILLISECONDS_TO_AVOID_REACT_FREEZE } from '@/object-record/record-board/constants/RecordBoardFetchMoreThrottlingWaitTimeInMillisecondsToAvoidReactFreeze'; @@ -7,7 +7,6 @@ import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/const import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState'; import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState'; -import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState'; import { recordBoardShouldFetchMoreInColumnComponentFamilyState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreInColumnComponentFamilyState'; import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; @@ -17,10 +16,10 @@ import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; -import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; +import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { isNonEmptyArray } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; import { computeRecordGroupOptionsFilter } from '@/object-record/record-group/utils/computeRecordGroupOptionsFilter'; @@ -68,11 +67,6 @@ export const useTriggerRecordBoardFetchMore = () => { recordBoardIsFetchingMoreComponentState, ); - const recordBoardShouldFetchMoreCallbackState = - useAtomComponentStateCallbackState( - recordBoardShouldFetchMoreComponentState, - ); - const triggerRecordBoardFetchMore = useCallback(async () => { const isAlreadyFetchingMore = store.get(recordBoardIsFetchingMore); @@ -103,7 +97,6 @@ export const useTriggerRecordBoardFetchMore = () => { .map((recordGroupDefinition) => recordGroupDefinition.value); if (!isNonEmptyArray(recordGroupValuesThatShouldBeFetched)) { - store.set(recordBoardShouldFetchMoreCallbackState, false); cleanStateBeforeExit(); return; @@ -227,7 +220,6 @@ export const useTriggerRecordBoardFetchMore = () => { recordBoardIsFetchingMore, recordBoardCurrentGroupByQueryOffsetCallbackState, recordBoardShouldFetchMoreInColumnFamilyCallbackState, - recordBoardShouldFetchMoreCallbackState, combinedFilters, recordIndexGroupFieldMetadataItem, ]); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery.ts index ddb1c42cd8..3400c7a04d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery.ts @@ -15,13 +15,13 @@ import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier'; import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; +import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { isNonEmptyArray } from '@sniptt/guards'; -import { useCallback } from 'react'; import { useStore } from 'jotai'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const useTriggerRecordBoardInitialQuery = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx index c6c5343f6c..3f85099651 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx @@ -1,5 +1,5 @@ -import { styled } from '@linaria/react'; import { Draggable } from '@hello-pangea/dnd'; +import { styled } from '@linaria/react'; import { useContext } from 'react'; import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly'; @@ -10,10 +10,15 @@ import { RecordBoardCardMultiDragPreview } from '@/object-record/record-board/re import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState'; +import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState'; import { DragAndDropLibraryLegacyReRenderBreaker } from '@/ui/drag-and-drop/components/DragAndDropReRenderBreaker'; import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -const StyledDraggableContainer = styled.div` +const StyledDraggableContainer = styled.div<{ + isDragDisabled: boolean; +}>` + cursor: ${({ isDragDisabled }) => (isDragDisabled ? 'default' : 'grab')}; position: relative; scroll-margin-left: 8px; scroll-margin-right: 8px; @@ -34,6 +39,10 @@ export const RecordBoardCardDraggableContainer = ({ objectMetadataId: objectMetadataItem.id, }); + const isRecordBoardDropProcessing = useAtomComponentStateValue( + isRecordBoardDropProcessingComponentState, + ); + const { columnIndex } = useContext(RecordBoardColumnContext); const isRecordBoardCardFocused = useAtomComponentFamilyStateValue( @@ -48,9 +57,15 @@ export const RecordBoardCardDraggableContainer = ({ - + {(draggableProvided) => ( ({ + key: 'isRecordBoardDropProcessingComponentState', + defaultValue: false, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts index 6aae035a4f..323fd99173 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts @@ -6,9 +6,12 @@ import { isDefined } from 'twenty-shared/utils'; import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState'; import { useUpdateDroppedRecordOnBoard } from '@/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; +import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; +import { useDebouncedCallback } from 'use-debounce'; export const useProcessBoardCardDrop = () => { const store = useStore(); @@ -21,6 +24,21 @@ export const useProcessBoardCardDrop = () => { const { updateDroppedRecordOnBoard } = useUpdateDroppedRecordOnBoard(); + const isRecordBoardDropProcessingCallbackState = + useAtomComponentStateCallbackState( + isRecordBoardDropProcessingComponentState, + ); + + // TODO: this is necessary to avoid race conditions when dragging right after a previous drag (~200ms to 500ms) + // A way to fix this would be to have a proper optimistic logic on drop that doesn't just resets the whole board with trigger initial query but updates everything without waiting for the request return + // Which is the problem here because it kind of destroys the existing columns that have more records than page size, and dnd library has issues computing drag when the underlying data change. + const debouncedUpdateDropProcessing = useDebouncedCallback( + (isPending: boolean) => { + store.set(isRecordBoardDropProcessingCallbackState, isPending); + }, + 500, + ); + const processBoardCardDrop = useCallback( (boardCardDropResult: DropResult, selectedRecordIds: string[]) => { if (!isDefined(selectFieldMetadataItem)) return; @@ -38,12 +56,15 @@ export const useProcessBoardCardDrop = () => { ); }, }); + + debouncedUpdateDropProcessing(false); }, [ store, selectFieldMetadataItem, recordIndexRecordIdsByGroupCallbackFamilyState, updateDroppedRecordOnBoard, + debouncedUpdateDropProcessing, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts index ccf5c26b0d..25a1295b21 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react'; import { useStore } from 'jotai'; +import { useCallback } from 'react'; import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState'; import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard.ts index 0cd039562c..5d36da4485 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard.ts @@ -1,6 +1,5 @@ import { useStore } from 'jotai'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { extractRecordPositions } from '@/object-record/record-drag/utils/extractRecordPositions'; import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector'; @@ -8,6 +7,7 @@ import { type RecordGroupDefinition } from '@/object-record/record-group/types/R import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useCallback, useContext } from 'react'; @@ -35,7 +35,10 @@ export const useUpdateDroppedRecordOnBoard = () => { { recordId, position: newPosition, - }: { recordId: string; position?: number }, + }: { + recordId: string; + position?: number; + }, targetRecordGroupValue: RecordGroupDefinition['value'], ) => { const initialRecord = store.get( @@ -50,18 +53,32 @@ export const useUpdateDroppedRecordOnBoard = () => { return; } - const initialRecordGroupValue = initialRecord[ - selectFieldMetadataItem.name - ] as string | undefined; + let currentRecordIdsInInitialRecordGroup: string[] = []; const initialRecordGroup = recordGroupDefinitions.find( - findByProperty('value', initialRecordGroupValue), + (recordGroupDefinition) => { + const recordIdsInGroup = store.get( + recordIndexRecordIdsByGroupCallbackFamilyState( + recordGroupDefinition.id, + ), + ); + + const recordIsInGroup = recordIdsInGroup.includes(recordId); + + if (recordIsInGroup) { + currentRecordIdsInInitialRecordGroup = recordIdsInGroup; + } + + return recordIsInGroup; + }, ); if (!isDefined(initialRecordGroup)) { return; } + const initialRecordGroupId = initialRecordGroup.id; + const targetRecordGroup = recordGroupDefinitions.find( findByProperty('value', targetRecordGroupValue), ); @@ -70,7 +87,6 @@ export const useUpdateDroppedRecordOnBoard = () => { return; } - const initialRecordGroupId = initialRecordGroup.id; const targetRecordGroupId = targetRecordGroup.id; const movingInsideSameRecordGroup = @@ -82,26 +98,22 @@ export const useUpdateDroppedRecordOnBoard = () => { return; } - const currentRecordIdsInInitialRecordGroup = store.get( - recordIndexRecordIdsByGroupCallbackFamilyState(initialRecordGroupId), - ) as string[]; - - const positionOfDroppedRecordInInitialRecordIds = + const indexOfDroppedRecordInInitialRecordGroup = currentRecordIdsInInitialRecordGroup.findIndex((id) => id === recordId); let currentRecordIdsInTargetRecordGroup = store.get( recordIndexRecordIdsByGroupCallbackFamilyState(targetRecordGroupId), ) as string[]; - if (positionOfDroppedRecordInInitialRecordIds === -1) { + if (indexOfDroppedRecordInInitialRecordGroup === -1) { throw new Error( - `Cannot find record id in initial record group ids on drop, this should not happen`, + `Cannot find record id in initial record group ids on drop, this should not happen, recordId: ${recordId}, initialRecordGroupId: ${initialRecordGroupId}`, ); } const newInitialGroupRecordIds = currentRecordIdsInInitialRecordGroup.toSpliced( - positionOfDroppedRecordInInitialRecordIds, + indexOfDroppedRecordInInitialRecordGroup, 1, ); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts index 9a432dd71a..0b8266de9a 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts @@ -15,7 +15,10 @@ type ProcessGroupDropParams = { selectedRecordIds: string[]; recordIdsByGroupFamilyState: any; onUpdateRecord: ( - update: { recordId: string; position?: number }, + update: { + recordId: string; + position?: number; + }, targetRecordGroupId: RecordGroupDefinition['value'], ) => void; };