From 703d9cb08de72acff9973321b915b3fb5d7c7c88 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 11 Nov 2025 17:07:40 +0100 Subject: [PATCH] Improved Kanban performance (#15714) This PR improves Kanban performance to attain the stock library speed. There are still some performance issues at load time and drop, but they are harder to address. Before, everything is re-rendering at every card move, full re-render at drop : https://github.com/user-attachments/assets/7a1cf419-c8d8-4d56-a1a3-4269dce7b5a9 After, only the columns and card outer containers get re-rendered for d&d, only two columns re-render at drop : https://github.com/user-attachments/assets/d2dea914-5cc3-4693-9c2d-c7c789ae3452 We can still improve performance and re-render but that would represent a global effort on D&D on general, table has the same problem currently, but since we implemented virtualization it is bearable for now. --- ...tionsDropdownHiddenRecordGroupsContent.tsx | 5 +- ...jectOptionsDropdownRecordGroupsContent.tsx | 12 +- .../record-board/components/RecordBoard.tsx | 171 +-------- .../RecordBoardClickOutsideEffect.tsx | 51 +++ .../components/RecordBoardColumns.tsx | 21 ++ .../components/RecordBoardDragDropContext.tsx | 98 +++++ .../components/RecordBoardDragSelect.tsx | 46 +++ .../hooks/useSetRecordBoardRecordIds.ts | 85 ----- .../hooks/useSetRecordIdsForColumn.ts | 25 +- .../components/RecordBoardCard.tsx | 102 ++---- .../RecordBoardCardDraggableContainer.tsx | 11 +- .../RecordBoardCardMultiDragCounterChip.tsx | 27 ++ .../RecordBoardCardMultiDragPreview.tsx | 41 +-- .../RecordBoardCardMultiDragStack.tsx | 35 ++ .../components/RecordBoardColumn.tsx | 34 +- ...BoardColumnCardContainerSkeletonLoader.tsx | 27 +- .../RecordBoardColumnCardsContainer.tsx | 73 ++-- .../components/RecordBoardColumnCardsMemo.tsx | 19 - .../RecordBoardColumnHeaderWrapper.tsx | 16 + .../month/components/RecordCalendarMonth.tsx | 26 +- .../components/RecordCalendarCardHeader.tsx | 12 +- .../states/originalSelectionComponentState.ts | 8 - .../hooks/__tests__/useEndRecordDrag.test.tsx | 73 ++++ .../__tests__/useStartRecordDrag.test.tsx | 293 +++++++++++++++ .../record-drag/hooks/useEndRecordDrag.ts | 110 ++++++ .../useProcessBoardCardDrop.ts} | 15 +- .../useProcessCalendarCardDrop.ts} | 22 +- .../useProcessTableWithGroupRecordDrop.ts} | 41 ++- .../useProcessTableWithoutGroupRecordDrop.ts} | 58 +-- .../record-drag/hooks/useStartRecordDrag.ts | 110 ++++++ .../hooks/__tests__/useEndRecordDrag.test.tsx | 204 ----------- .../__tests__/useMultiDragState.test.tsx | 140 -------- .../__tests__/useRecordDragState.test.tsx | 159 -------- .../__tests__/useStartRecordDrag.test.tsx | 339 ------------------ .../shared/hooks/useEndRecordDrag.ts | 65 ---- .../shared/hooks/useMultiDragState.ts | 53 --- .../shared/hooks/useRecordDragState.ts | 52 --- .../shared/hooks/useStartRecordDrag.ts | 81 ----- .../shared/types/MultiDragState.ts | 6 - .../states/draggedRecordIdsComponentState.ts | 4 +- .../states/isDraggingRecordComponentState.ts | 8 + .../states/isMultiDragActiveComponentState.ts | 4 +- ...PrimaryDragMultipleComponentFamilyState.ts | 9 + ...condaryDragMultipleComponentFamilyState.ts | 9 + .../originalDragSelectionComponentState.ts | 10 + .../primaryDraggedRecordIdComponentState.ts | 4 +- .../draggedRecordIdsTableComponentState.ts | 10 - .../isMultiDragActiveTableComponentState.ts | 9 - .../originalSelectionTableComponentState.ts | 10 - ...imaryDraggedRecordIdTableComponentState.ts | 10 - .../{shared => }/types/DragOperationType.ts | 0 .../{shared => }/types/MultiDragResult.ts | 0 .../{shared => }/types/RecordDragContext.ts | 0 .../__tests__/extractRecordPositions.test.ts | 2 +- .../__tests__/getDragOperationType.test.ts | 2 +- .../utils/__tests__/processMultiDrag.test.ts | 2 +- .../utils/__tests__/processSingleDrag.test.ts | 2 +- .../utils/extractRecordPositions.ts | 0 .../utils/getDragOperationType.ts | 2 +- .../processGroupDrop.ts} | 22 +- .../{shared => }/utils/processMultiDrag.ts | 2 +- .../{shared => }/utils/processSingleDrag.ts | 0 .../hooks/useRecordGroupActions.ts | 4 +- .../hooks/useRecordGroupVisibility.ts | 19 +- ...mptyRecordGroupByIdComponentFamilyState.ts | 9 + ...leRecordGroupIdsComponentFamilySelector.ts | 22 +- .../components/RecordIndexBoardContainer.tsx | 1 + ...ndexRecordGroupHideComponentFamilyState.ts | 19 - ...ouldHideEmptyRecordGroupsComponentState.ts | 9 + ...dyNoRecordGroupDragDropContextProvider.tsx | 24 +- ...BodyRecordGroupDragDropContextProvider.tsx | 34 +- ...cordTableBodyVirtualizedDraggableClone.tsx | 27 +- .../components/RecordTableDraggableTr.tsx | 14 +- .../RecordTableDraggableTrFirstRowOfGroup.tsx | 14 +- .../RecordTableRowMultiDragCounterChip.tsx | 27 ++ .../RecordTableRowMultiDragPreview.tsx | 38 +- .../hooks/useIsRecordSecondaryDragged.ts | 13 + .../components/DragAndDropReRenderBreaker.tsx | 20 ++ 78 files changed, 1355 insertions(+), 1826 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardClickOutsideEffect.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardColumns.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragSelect.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordBoardRecordIds.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragCounterChip.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/board/states/originalSelectionComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useEndRecordDrag.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useStartRecordDrag.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/hooks/useEndRecordDrag.ts rename packages/twenty-front/src/modules/object-record/record-drag/{board/hooks/useRecordBoardDragOperations.ts => hooks/useProcessBoardCardDrop.ts} (79%) rename packages/twenty-front/src/modules/object-record/record-drag/{calendar/hooks/useHandleDragOneCalendarCard.ts => hooks/useProcessCalendarCardDrop.ts} (87%) rename packages/twenty-front/src/modules/object-record/record-drag/{table/hooks/useRecordTableGroupDragOperations.ts => hooks/useProcessTableWithGroupRecordDrop.ts} (74%) rename packages/twenty-front/src/modules/object-record/record-drag/{table/hooks/useRecordTableWithoutGroupDragOperations.ts => hooks/useProcessTableWithoutGroupRecordDrop.ts} (81%) create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useEndRecordDrag.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useMultiDragState.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useRecordDragState.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useStartRecordDrag.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useEndRecordDrag.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useMultiDragState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useRecordDragState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useStartRecordDrag.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragState.ts rename packages/twenty-front/src/modules/object-record/record-drag/{board => }/states/draggedRecordIdsComponentState.ts (55%) create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/states/isDraggingRecordComponentState.ts rename packages/twenty-front/src/modules/object-record/record-drag/{board => }/states/isMultiDragActiveComponentState.ts (55%) create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-drag/states/originalDragSelectionComponentState.ts rename packages/twenty-front/src/modules/object-record/record-drag/{board => }/states/primaryDraggedRecordIdComponentState.ts (57%) delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/table/states/draggedRecordIdsTableComponentState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/table/states/isMultiDragActiveTableComponentState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/table/states/originalSelectionTableComponentState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState.ts rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/types/DragOperationType.ts (100%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/types/MultiDragResult.ts (100%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/types/RecordDragContext.ts (100%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/__tests__/extractRecordPositions.test.ts (99%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/__tests__/getDragOperationType.test.ts (97%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/__tests__/processMultiDrag.test.ts (95%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/__tests__/processSingleDrag.test.ts (95%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/extractRecordPositions.ts (100%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/getDragOperationType.ts (82%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared/utils/processGroupDragOperation.ts => utils/processGroupDrop.ts} (85%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/processMultiDrag.ts (96%) rename packages/twenty-front/src/modules/object-record/record-drag/{shared => }/utils/processSingleDrag.ts (100%) create mode 100644 packages/twenty-front/src/modules/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragCounterChip.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged.ts create mode 100644 packages/twenty-front/src/modules/ui/drag-and-drop/components/DragAndDropReRenderBreaker.tsx diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx index b7d013a9ff..c5aab81541 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx @@ -25,7 +25,6 @@ import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation'; export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => { const { t } = useLingui(); const { - viewType, currentContentId, objectMetadataItem, onContentChange, @@ -45,9 +44,7 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => { }); const { handleVisibilityChange: handleRecordGroupVisibilityChange } = - useRecordGroupVisibility({ - viewType, - }); + useRecordGroupVisibility(); const viewGroupSettingsUrl = getSettingsPath(SettingsPath.ObjectFieldEdit, { objectNamePlural, diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx index b251ce6d41..6c51f6b163 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx @@ -7,8 +7,8 @@ import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/use import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; -import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState'; import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; +import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; @@ -59,9 +59,8 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => { hiddenRecordGroupIdsComponentSelector, ); - const hideEmptyRecordGroup = useRecoilComponentFamilyValue( - recordIndexRecordGroupHideComponentFamilyState, - viewType, + const hideEmptyRecordGroup = useRecoilComponentValue( + recordIndexShouldHideEmptyRecordGroupsComponentState, ); const recordGroupSort = useRecoilComponentValue( @@ -71,9 +70,8 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => { const { handleVisibilityChange: handleRecordGroupVisibilityChange, handleHideEmptyRecordGroupChange, - } = useRecordGroupVisibility({ - viewType, - }); + } = useRecordGroupVisibility(); + useEffect(() => { if ( currentContentId === 'hiddenRecordGroups' && diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index b13f3e46e1..8eb191d3d9 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -1,49 +1,18 @@ import styled from '@emotion/styled'; -import { - DragDropContext, - type DragStart, - type OnDragEndResponder, -} from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 +// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 import { useContext, useRef } from 'react'; -import { useRecoilCallback } from 'recoil'; -import { ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/action-menu/constants/ActionMenuDropdownClickOutsideId'; -import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId'; +import { RecordBoardClickOutsideEffect } from '@/object-record/record-board/components/RecordBoardClickOutsideEffect'; +import { RecordBoardColumns } from '@/object-record/record-board/components/RecordBoardColumns'; +import { RecordBoardDragDropContext } from '@/object-record/record-board/components/RecordBoardDragDropContext'; +import { RecordBoardDragSelect } from '@/object-record/record-board/components/RecordBoardDragSelect'; import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader'; import { RecordBoardScrollToFocusedCardEffect } from '@/object-record/record-board/components/RecordBoardScrollToFocusedCardEffect'; import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect'; -import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; -import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; -import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; -import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; import { RecordBoardDeactivateBoardCardEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect'; -import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; -import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; -import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector'; -import { useRecordBoardDragOperations } from '@/object-record/record-drag/board/hooks/useRecordBoardDragOperations'; -import { useEndRecordDrag } from '@/object-record/record-drag/shared/hooks/useEndRecordDrag'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { useStartRecordDrag } from '@/object-record/record-drag/shared/hooks/useStartRecordDrag'; -import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; -import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId'; -import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; -import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; -import { MODAL_BACKDROP_CLICK_OUTSIDE_ID } from '@/ui/layout/modal/constants/ModalBackdropClickOutsideId'; -import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID } from '@/ui/layout/page/constants/PageActionContainerClickOutsideId'; -import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; -import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass'; -import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { ViewType } from '@/views/types/ViewType'; -import { LINK_CHIP_CLICK_OUTSIDE_ID } from 'twenty-ui/components'; const StyledContainer = styled.div` display: flex; @@ -78,110 +47,6 @@ export const RecordBoard = () => { const { recordBoardId } = useContext(RecordBoardContext); const boardRef = useRef(null); - const { toggleClickOutside } = useClickOutsideListener( - RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, - ); - - const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); - - const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); - const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId); - - const handleDragSelectionStart = () => { - closeAnyOpenDropdown(); - toggleClickOutside(false); - }; - - const handleDragSelectionEnd = () => { - toggleClickOutside(true); - }; - - const visibleRecordGroupIds = useRecoilComponentFamilyValue( - visibleRecordGroupIdsComponentFamilySelector, - ViewType.Kanban, - ); - - const { resetRecordSelection, setRecordAsSelected } = - useRecordBoardSelection(recordBoardId); - - const currentRecordSorts = useRecoilComponentValue( - currentRecordSortsComponentState, - ); - - const recordBoardSelectedRecordIdsSelector = useRecoilComponentCallbackState( - recordBoardSelectedRecordIdsComponentSelector, - recordBoardId, - ); - - const { startDrag } = useStartRecordDrag('board', recordBoardId); - const { endDrag } = useEndRecordDrag('board', recordBoardId); - const multiDragState = useRecordDragState('board', recordBoardId); - - const { processDragOperation } = useRecordBoardDragOperations(); - - useListenClickOutside({ - excludedClickOutsideIds: [ - ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID, - COMMAND_MENU_CLICK_OUTSIDE_ID, - MODAL_BACKDROP_CLICK_OUTSIDE_ID, - PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID, - RECORD_BOARD_CARD_CLICK_OUTSIDE_ID, - LINK_CHIP_CLICK_OUTSIDE_ID, - ], - listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, - refs: [], - callback: () => { - if (!multiDragState.isDragging) { - resetRecordSelection(); - deactivateBoardCard(); - unfocusBoardCard(); - } - }, - }); - - const { openModal } = useModal(); - - const handleDragStart = useRecoilCallback( - ({ snapshot }) => - (start: DragStart) => { - const currentSelectedRecordIds = getSnapshotValue( - snapshot, - recordBoardSelectedRecordIdsSelector, - ); - - startDrag(start, currentSelectedRecordIds); - }, - [recordBoardSelectedRecordIdsSelector, startDrag], - ); - - const handleDragEnd: OnDragEndResponder = useRecoilCallback( - () => (result) => { - endDrag(); - - if (!result.destination) return; - - if (currentRecordSorts.length > 0) { - openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID); - return; - } - - processDragOperation(result, multiDragState.originalSelection); - }, - [ - processDragOperation, - multiDragState.originalSelection, - endDrag, - currentRecordSorts, - openModal, - ], - ); - - // FixMe: Check if we really need this as it depends on the times it takes to update the view groups - // if (isPersistingViewGroups) { - // // TODO: Add skeleton state - // return null; - // } - return ( { - + - {visibleRecordGroupIds.map((recordGroupId, index) => ( - - ))} + - - - + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardClickOutsideEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardClickOutsideEffect.tsx new file mode 100644 index 0000000000..c6c9c3162d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardClickOutsideEffect.tsx @@ -0,0 +1,51 @@ +import { ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/action-menu/constants/ActionMenuDropdownClickOutsideId'; +import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId'; +import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; +import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; + +import { MODAL_BACKDROP_CLICK_OUTSIDE_ID } from '@/ui/layout/modal/constants/ModalBackdropClickOutsideId'; +import { PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID } from '@/ui/layout/page/constants/PageActionContainerClickOutsideId'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useContext } from 'react'; +import { LINK_CHIP_CLICK_OUTSIDE_ID } from 'twenty-ui/components'; + +export const RecordBoardClickOutsideEffect = () => { + const { recordBoardId } = useContext(RecordBoardContext); + + const isDraggingRecord = useRecoilComponentValue( + isDraggingRecordComponentState, + ); + + const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); + const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId); + + const { resetRecordSelection } = useRecordBoardSelection(recordBoardId); + + useListenClickOutside({ + excludedClickOutsideIds: [ + ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID, + COMMAND_MENU_CLICK_OUTSIDE_ID, + MODAL_BACKDROP_CLICK_OUTSIDE_ID, + PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID, + RECORD_BOARD_CARD_CLICK_OUTSIDE_ID, + LINK_CHIP_CLICK_OUTSIDE_ID, + ], + listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, + refs: [], + callback: () => { + if (!isDraggingRecord) { + resetRecordSelection(); + deactivateBoardCard(); + unfocusBoardCard(); + } + }, + }); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardColumns.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardColumns.tsx new file mode 100644 index 0000000000..fdd9ce7a55 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardColumns.tsx @@ -0,0 +1,21 @@ +import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; +import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { ViewType } from '@/views/types/ViewType'; + +export const RecordBoardColumns = () => { + const visibleRecordGroupIds = useRecoilComponentFamilyValue( + visibleRecordGroupIdsComponentFamilySelector, + ViewType.Kanban, + ); + + return visibleRecordGroupIds.map((recordGroupId, index) => { + return ( + + ); + }); +}; 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 new file mode 100644 index 0000000000..9a0cb7ff74 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragDropContext.tsx @@ -0,0 +1,98 @@ +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +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'; +import { useStartRecordDrag } from '@/object-record/record-drag/hooks/useStartRecordDrag'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; + +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 { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { + DragDropContext, + type DragStart, + type OnDragEndResponder, +} from '@hello-pangea/dnd'; +import { useContext } from 'react'; +import { useRecoilCallback } from 'recoil'; + +export const RecordBoardDragDropContext = ({ + children, +}: React.PropsWithChildren) => { + const { recordBoardId } = useContext(RecordBoardContext); + + const currentRecordSortCallbackState = useRecoilComponentCallbackState( + currentRecordSortsComponentState, + ); + + const recordBoardSelectedRecordIdsSelector = useRecoilComponentCallbackState( + recordBoardSelectedRecordIdsComponentSelector, + recordBoardId, + ); + + const originalDragSelectionCallbackState = useRecoilComponentCallbackState( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + const { endRecordDrag } = useEndRecordDrag(); + // const multiDragState = useRecordDragState('board', recordBoardId); + + const { processBoardCardDrop } = useProcessBoardCardDrop(); + + const { openModal } = useModal(); + + const handleDragStart = useRecoilCallback( + ({ snapshot }) => + (start: DragStart) => { + const currentSelectedRecordIds = getSnapshotValue( + snapshot, + recordBoardSelectedRecordIdsSelector, + ); + + startRecordDrag(start, currentSelectedRecordIds); + }, + [recordBoardSelectedRecordIdsSelector, startRecordDrag], + ); + + const handleDragEnd: OnDragEndResponder = useRecoilCallback( + ({ snapshot }) => + (result) => { + endRecordDrag(); + + if (!result.destination) return; + + const currentRecordSorts = getSnapshotValue( + snapshot, + currentRecordSortCallbackState, + ); + + if (currentRecordSorts.length > 0) { + openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID); + return; + } + + const originalSelection = getSnapshotValue( + snapshot, + originalDragSelectionCallbackState, + ); + + processBoardCardDrop(result, originalSelection); + }, + [ + processBoardCardDrop, + originalDragSelectionCallbackState, + endRecordDrag, + currentRecordSortCallbackState, + openModal, + ], + ); + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragSelect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragSelect.tsx new file mode 100644 index 0000000000..0508c6dd80 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardDragSelect.tsx @@ -0,0 +1,46 @@ +import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; +import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; +import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass'; +import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; +import { useContext, type RefObject } from 'react'; + +export type RecordBoardDragSelectProps = { + boardRef: RefObject; +}; + +export const RecordBoardDragSelect = ({ + boardRef, +}: RecordBoardDragSelectProps) => { + const { recordBoardId } = useContext(RecordBoardContext); + + const { toggleClickOutside } = useClickOutsideListener( + RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, + ); + + const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); + + const handleDragSelectionStart = () => { + closeAnyOpenDropdown(); + toggleClickOutside(false); + }; + + const handleDragSelectionEnd = () => { + toggleClickOutside(true); + }; + + const { setRecordAsSelected } = useRecordBoardSelection(); + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordBoardRecordIds.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordBoardRecordIds.ts deleted file mode 100644 index 2c035bd5f4..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordBoardRecordIds.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; -import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; -import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; -import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { sortRecordsByPosition } from '@/object-record/utils/sortRecordsByPosition'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { ViewType } from '@/views/types/ViewType'; -import { isDefined } from 'twenty-shared/utils'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const useSetRecordBoardRecordIds = (recordBoardId?: string) => { - const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackState( - visibleRecordGroupIdsComponentFamilySelector, - ); - - const recordGroupFieldMetadataState = useRecoilComponentCallbackState( - recordGroupFieldMetadataComponentState, - recordBoardId, - ); - - const recordIndexRecordIdsByGroupFamilyState = - useRecoilComponentCallbackState( - recordIndexRecordIdsByGroupComponentFamilyState, - recordBoardId, - ); - - const setRecordIds = useRecoilCallback( - ({ set, snapshot }) => - (records: ObjectRecord[]) => { - const recordGroupIds = getSnapshotValue( - snapshot, - visibleRecordGroupIdsFamilySelector(ViewType.Kanban), - ); - - for (const recordGroupId of recordGroupIds) { - const recordGroup = getSnapshotValue( - snapshot, - recordGroupDefinitionFamilyState(recordGroupId), - ); - - const existingRecordGroupRowIds = getSnapshotValue( - snapshot, - recordIndexRecordIdsByGroupFamilyState(recordGroupId), - ); - - const recordGroupFieldMetadata = getSnapshotValue( - snapshot, - recordGroupFieldMetadataState, - ); - - if (!isDefined(recordGroupFieldMetadata)) { - return; - } - - const recordGroupRowIds = records - .filter( - (record) => - record[recordGroupFieldMetadata.name] === recordGroup?.value, - ) - .sort(sortRecordsByPosition) - .map((record) => record.id); - - if (!isDeeplyEqual(existingRecordGroupRowIds, recordGroupRowIds)) { - set( - recordIndexRecordIdsByGroupFamilyState(recordGroupId), - recordGroupRowIds, - ); - } - } - }, - [ - visibleRecordGroupIdsFamilySelector, - recordIndexRecordIdsByGroupFamilyState, - recordGroupFieldMetadataState, - ], - ); - - return { - setRecordIds, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordIdsForColumn.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordIdsForColumn.ts index 5f1feea17e..e0154977ca 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordIdsForColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useSetRecordIdsForColumn.ts @@ -1,5 +1,6 @@ import { useRecoilCallback } from 'recoil'; +import { emptyRecordGroupByIdComponentFamilyState } from '@/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; @@ -21,6 +22,10 @@ export const useSetRecordIdsForColumn = (recordBoardId?: string) => { recordBoardId, ); + const emptyRecordGroupByIdCallbackState = useRecoilComponentCallbackState( + emptyRecordGroupByIdComponentFamilyState, + ); + const setRecordIdsForColumn = useRecoilCallback( ({ set, snapshot }) => (currentRecordGroupId: string, records: ObjectRecord[]) => { @@ -56,8 +61,26 @@ export const useSetRecordIdsForColumn = (recordBoardId?: string) => { recordGroupRowIds, ); } + + const isEmptyRecordGroup = getSnapshotValue( + snapshot, + emptyRecordGroupByIdCallbackState(currentRecordGroupId), + ); + + const computedIsEmptyRecordGroup = recordGroupRowIds.length === 0; + + if (computedIsEmptyRecordGroup !== isEmptyRecordGroup) { + set( + emptyRecordGroupByIdCallbackState(currentRecordGroupId), + computedIsEmptyRecordGroup, + ); + } }, - [recordIndexRecordIdsByGroupFamilyState, recordGroupFieldMetadataState], + [ + recordIndexRecordIdsByGroupFamilyState, + recordGroupFieldMetadataState, + emptyRecordGroupByIdCallbackState, + ], ); return { 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 d3af0c4293..021063b39b 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 @@ -5,7 +5,6 @@ import { RecordBoardCardContext } from '@/object-record/record-board/record-boar import { isRecordBoardCardActiveComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState'; import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState'; import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; @@ -13,14 +12,16 @@ import { RecordBoardCardCellEditModePortal } from '@/object-record/record-board/ import { RecordBoardCardCellHoveredPortal } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader'; +import { RecordBoardCardMultiDragStack } from '@/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack'; import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; import { RecordBoardCardComponentInstanceContext } from '@/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext'; import { recordBoardCardIsExpandedComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardIsExpandedComponentState'; import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; import { RecordCard } from '@/object-record/record-card/components/RecordCard'; +import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState'; +import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown'; -import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; @@ -29,7 +30,6 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import styled from '@emotion/styled'; import { useContext } from 'react'; -import { InView, useInView } from 'react-intersection-observer'; import { AnimatedEaseInOut } from 'twenty-ui/utilities'; import { useDebouncedCallback } from 'use-debounce'; @@ -43,18 +43,6 @@ const StyledCardContainer = styled.div<{ isPrimaryMultiDrag?: boolean }>` `} `; -const StyledRecordBoardCardStackCard = styled.div<{ offset: number }>` - position: absolute; - top: ${({ offset }) => (offset === 1 ? 2 : (offset - 1) * 4 + 2)}px; - left: 0; - right: 0; - height: 100%; - background-color: ${({ theme }) => theme.accent.tertiary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - z-index: ${({ offset }) => -offset}; -`; - const StyledBoardCardWrapper = styled.div` padding-bottom: ${({ theme }) => theme.spacing(2)}; width: 100%; @@ -69,17 +57,15 @@ export const RecordBoardCard = () => { RecordBoardComponentInstanceContext, ); - const multiDragState = useRecordDragState('board', recordBoardId); + const isRecordIdPrimaryDragMultiple = useRecoilComponentFamilyValue( + isRecordIdPrimaryDragMultipleComponentFamilyState, + { recordId }, + ); - const isPrimaryMultiDrag = - multiDragState?.isDragging && - recordId === multiDragState.primaryDraggedRecordId && - multiDragState.originalSelection.length > 1; - - const isSecondaryDragged = - multiDragState?.isDragging && - multiDragState.originalSelection.includes(recordId) && - recordId !== multiDragState.primaryDraggedRecordId; + const isRecordIdSecondaryDragMultiple = useRecoilComponentFamilyValue( + isRecordIdSecondaryDragMultipleComponentFamilyState, + { recordId }, + ); const { currentView } = useGetCurrentViewOnly(); @@ -156,12 +142,8 @@ export const RecordBoardCard = () => { } }, 800); - const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement(); - - const { ref: cardRef } = useInView({ - root: scrollWrapperHTMLElement, - rootMargin: '1000px', - }); + const isDraggingThisCard = + isRecordIdPrimaryDragMultiple || isRecordIdSecondaryDragMultiple; return ( { data-click-outside-id={RECORD_BOARD_CARD_CLICK_OUTSIDE_ID} onContextMenu={handleContextMenuOpen} > - - - {isPrimaryMultiDrag && - Array.from({ - length: Math.min( - 5, - multiDragState.originalSelection.length - 1, - ), - }).map((_, index) => ( - - ))} - - + {isRecordIdPrimaryDragMultiple && } + + + - - - - - - - - - + + + + + + ); 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 04a9a6d09b..4fc2296aed 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 @@ -10,6 +10,7 @@ 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 { DragAndDropLibraryLegacyReRenderBreaker } from '@/ui/drag-and-drop/components/DragAndDropReRenderBreaker'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; const StyledDraggableContainer = styled.div` @@ -48,7 +49,7 @@ export const RecordBoardCardDraggableContainer = ({ value={{ recordId, isRecordReadOnly, rowIndex, columnIndex }} > - {(draggableProvided, snapshot) => ( + {(draggableProvided) => ( - {isRecordBoardCardFocusActive && } - - + + {isRecordBoardCardFocusActive && } + + + )} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragCounterChip.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragCounterChip.tsx new file mode 100644 index 0000000000..b008d645a9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragCounterChip.tsx @@ -0,0 +1,27 @@ +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import styled from '@emotion/styled'; +import { NotificationCounter } from 'twenty-ui/navigation'; + +const StyledNotificationCounter = styled(NotificationCounter)` + position: absolute; + top: -7px; + right: -7px; + z-index: 1000; +`; + +export const RecordBoardCardMultiDragCounterChip = () => { + const originalDragSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const selectedCount = originalDragSelection.length ?? 0; + + const shouldShow = selectedCount > 1; + + if (!shouldShow) { + return null; + } + + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragPreview.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragPreview.tsx index 4559c8a5fc..851e957d7f 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragPreview.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragPreview.tsx @@ -1,40 +1,21 @@ -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; +import { RecordBoardCardMultiDragCounterChip } from '@/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragCounterChip'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; -import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; -import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import styled from '@emotion/styled'; +import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; import { useContext } from 'react'; -import { NotificationCounter } from 'twenty-ui/navigation'; -const StyledNotificationCounter = styled(NotificationCounter)` - position: absolute; - top: -7px; - right: -7px; - z-index: 1000; -`; - -type RecordBoardCardMultiDragPreviewProps = { - isDragging: boolean; -}; - -export const RecordBoardCardMultiDragPreview = ({ - isDragging, -}: RecordBoardCardMultiDragPreviewProps) => { +// TODO: use the same concept as PortalHovered components in the app +export const RecordBoardCardMultiDragPreview = () => { const { recordId } = useContext(RecordBoardCardContext); - const recordBoardId = useAvailableComponentInstanceIdOrThrow( - RecordBoardComponentInstanceContext, + + const isRecordIdPrimaryDragMultiple = useRecoilComponentFamilyValue( + isRecordIdPrimaryDragMultipleComponentFamilyState, + { recordId }, ); - const multiDragState = useRecordDragState('board', recordBoardId); - const isCurrentCardSelected = - multiDragState?.originalSelection.includes(recordId) || false; - const selectedCount = multiDragState?.originalSelection.length || 0; - - const shouldShow = isDragging && isCurrentCardSelected && selectedCount > 1; - - if (!shouldShow) { + if (!isRecordIdPrimaryDragMultiple) { return null; } - return ; + return ; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack.tsx new file mode 100644 index 0000000000..c41c94b863 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardMultiDragStack.tsx @@ -0,0 +1,35 @@ +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import styled from '@emotion/styled'; + +const StyledRecordBoardCardStackCard = styled.div<{ offset: number }>` + position: absolute; + top: ${({ offset }) => (offset === 1 ? 2 : (offset - 1) * 4 + 2)}px; + left: 0; + right: 0; + height: 100%; + background-color: ${({ theme }) => theme.accent.tertiary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + z-index: ${({ offset }) => -offset}; +`; + +export const RecordBoardCardMultiDragStack = () => { + const originalDragSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const selectedCount = originalDragSelection.length ?? 0; + + const shouldShow = selectedCount > 1; + + if (!shouldShow) { + return null; + } + + return Array.from({ + length: Math.min(5, originalDragSelection.length - 1), + }).map((_, index) => ( + + )); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx index c6c1c988b9..1bd5ef4343 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx @@ -3,10 +3,15 @@ import { Droppable } from '@hello-pangea/dnd'; import { RecordBoardColumnCardsContainer } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { emptyRecordGroupByIdComponentFamilyState } from '@/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; +import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; +import { DragAndDropLibraryLegacyReRenderBreaker } from '@/ui/drag-and-drop/components/DragAndDropReRenderBreaker'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; const StyledColumn = styled.div` background-color: ${({ theme }) => theme.background.primary}; @@ -34,13 +39,25 @@ export const RecordBoardColumn = ({ const recordGroupDefinition = useRecoilValue( recordGroupDefinitionFamilyState(recordBoardColumnId), ); - const recordIdsByGroup = useRecoilComponentFamilyValue( recordIndexRecordIdsByGroupComponentFamilyState, recordBoardColumnId, ); - if (!recordGroupDefinition) { + const shouldHideEmptyRecordGroups = useRecoilComponentValue( + recordIndexShouldHideEmptyRecordGroupsComponentState, + ); + + const isRecordGroupEmpty = useRecoilComponentFamilyValue( + emptyRecordGroupByIdComponentFamilyState, + recordBoardColumnId, + ); + + if (shouldHideEmptyRecordGroups && isRecordGroupEmpty) { + return null; + } + + if (!isDefined(recordGroupDefinition)) { return null; } @@ -56,10 +73,15 @@ export const RecordBoardColumn = ({ {(droppableProvided) => ( - + + + + {droppableProvided.placeholder} )} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx index 6a43af7944..cdb3204dae 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx @@ -5,6 +5,9 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { RecordCardBodyContainer } from '@/object-record/record-card/components/RecordCardBodyContainer'; import { RecordCardHeaderContainer } from '@/object-record/record-card/components/RecordCardHeaderContainer'; +import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; const StyledSkeletonIconAndText = styled.div` display: flex; gap: ${({ theme }) => theme.spacing(1)}; @@ -18,21 +21,27 @@ const StyledSeparator = styled.div` height: ${({ theme }) => theme.spacing(2)}; `; -export const RecordBoardColumnCardContainerSkeletonLoader = ({ - numberOfFields, - titleSkeletonWidth, - isCompactModeActive, -}: { - numberOfFields: number; - titleSkeletonWidth: number; - isCompactModeActive: boolean; -}) => { +export const RecordBoardColumnCardContainerSkeletonLoader = () => { const theme = useTheme(); + + const { currentView } = useGetCurrentViewOnly(); + + const isCompactModeActive = currentView?.isCompact ?? false; + + const visibleRecordFields = useRecoilComponentValue( + visibleRecordFieldsComponentSelector, + ); + + const numberOfFields = visibleRecordFields.length; + const skeletonItems = Array.from({ length: numberOfFields }).map( (_, index) => ({ id: `skeleton-item-${index}`, }), ); + + const titleSkeletonWidth = isCompactModeActive ? 72 : 54; + return ( { const { columnDefinition } = useContext(RecordBoardColumnContext); const columnId = columnDefinition.id; + const recordIds = useRecoilComponentFamilyValue( + recordIndexRecordIdsByGroupComponentFamilyState, + recordBoardColumnId, + ); + const isRecordIndexBoardColumnLoading = useRecoilValue( isRecordIndexBoardColumnLoadingFamilyState(columnId), ); - const visibleRecordFields = useRecoilComponentValue( - visibleRecordFieldsComponentSelector, - ); - - const numberOfFields = visibleRecordFields.length; - - const { currentView } = useGetCurrentViewOnly(); - - const isCompactModeActive = currentView?.isCompact ?? false; - return ( - {isRecordIndexBoardColumnLoading ? ( - Array.from( - { - length: getNumberOfCardsPerColumnForSkeletonLoading( - columnDefinition.position, + {isRecordIndexBoardColumnLoading + ? Array.from( + { + length: getNumberOfCardsPerColumnForSkeletonLoading( + columnDefinition.position, + ), + }, + (_, index) => ( + + + ), - }, - (_, index) => ( - - - - ), - ) - ) : ( - - )} + ) + : recordIds.map((recordId, index) => ( + + ))} {(draggableProvided) => (
)}
- {droppableProvided?.placeholder} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx deleted file mode 100644 index d0967551df..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import { RecordBoardCardDraggableContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer'; - -type RecordBoardColumnCardsMemoProps = { - recordIds: string[]; -}; - -export const RecordBoardColumnCardsMemo = React.memo( - ({ recordIds }: RecordBoardColumnCardsMemoProps) => { - return recordIds.map((recordId, index) => ( - - )); - }, -); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx index 0a9e2e262b..8893b30247 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx @@ -1,8 +1,11 @@ import { RecordBoardColumnHeader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeader'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { emptyRecordGroupByIdComponentFamilyState } from '@/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; +import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -24,6 +27,19 @@ export const RecordBoardColumnHeaderWrapper = ({ columnId, ); + const shouldHideEmptyRecordGroups = useRecoilComponentValue( + recordIndexShouldHideEmptyRecordGroupsComponentState, + ); + + const isRecordGroupEmpty = useRecoilComponentFamilyValue( + emptyRecordGroupByIdComponentFamilyState, + columnId, + ); + + if (shouldHideEmptyRecordGroups && isRecordGroupEmpty) { + return null; + } + if (!isDefined(recordGroupDefinition)) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonth.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonth.tsx index 3eb7e40be3..5c6bf36078 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonth.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/RecordCalendarMonth.tsx @@ -2,12 +2,10 @@ import { RecordCalendarMonthBody } from '@/object-record/record-calendar/month/c import { RecordCalendarMonthHeader } from '@/object-record/record-calendar/month/components/RecordCalendarMonthHeader'; import { RecordCalendarMonthContextProvider } from '@/object-record/record-calendar/month/contexts/RecordCalendarMonthContext'; import { useRecordCalendarMonthDaysRange } from '@/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange'; -import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext'; import { recordCalendarSelectedDateComponentState } from '@/object-record/record-calendar/states/recordCalendarSelectedDateComponentState'; -import { useHandleDragOneCalendarCard } from '@/object-record/record-drag/calendar/hooks/useHandleDragOneCalendarCard'; -import { useEndRecordDrag } from '@/object-record/record-drag/shared/hooks/useEndRecordDrag'; -import { useStartRecordDrag } from '@/object-record/record-drag/shared/hooks/useStartRecordDrag'; -import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag'; +import { useProcessCalendarCardDrop } from '@/object-record/record-drag/hooks/useProcessCalendarCardDrop'; +import { useStartRecordDrag } from '@/object-record/record-drag/hooks/useStartRecordDrag'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import styled from '@emotion/styled'; import { @@ -24,17 +22,13 @@ const StyledContainer = styled.div` `; export const RecordCalendarMonth = () => { - const recordCalendarId = useAvailableComponentInstanceIdOrThrow( - RecordCalendarComponentInstanceContext, - ); - const recordCalendarSelectedDate = useRecoilComponentValue( recordCalendarSelectedDateComponentState, ); - const { processDragOperation } = useHandleDragOneCalendarCard(); - const { startDrag } = useStartRecordDrag('calendar', recordCalendarId); - const { endDrag } = useEndRecordDrag('calendar', recordCalendarId); + const { processCalendarCardDrop } = useProcessCalendarCardDrop(); + const { startRecordDrag } = useStartRecordDrag(); + const { endRecordDrag } = useEndRecordDrag(); const { firstDayOfMonth, @@ -47,13 +41,15 @@ export const RecordCalendarMonth = () => { } = useRecordCalendarMonthDaysRange(recordCalendarSelectedDate); const handleDragStart = (start: DragStart) => { - startDrag(start, []); + startRecordDrag(start, []); }; const handleDragEnd: OnDragEndResponder = (result) => { - endDrag(); + endRecordDrag(); + if (!result.destination) return; - processDragOperation(result); + + processCalendarCardDrop(result); }; return ( diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx index 761c1e2d14..53c56a6dc0 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardHeader.tsx @@ -2,10 +2,11 @@ import { RecordChip } from '@/object-record/components/RecordChip'; import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer'; import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext'; import { RecordCardHeaderContainer } from '@/object-record/record-card/components/RecordCardHeaderContainer'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; @@ -36,8 +37,7 @@ type RecordCalendarCardHeaderProps = { export const RecordCalendarCardHeader = ({ recordId, }: RecordCalendarCardHeaderProps) => { - const { objectMetadataItem, viewBarInstanceId } = - useRecordCalendarContextOrThrow(); + const { objectMetadataItem } = useRecordCalendarContextOrThrow(); const record = useRecoilValue(recordStoreFamilyState(recordId)); const { openRecordFromIndexView } = useOpenRecordFromIndexView(); @@ -45,7 +45,9 @@ export const RecordCalendarCardHeader = ({ const isCompactModeActive = currentView?.isCompact ?? false; - const dragState = useRecordDragState('calendar', viewBarInstanceId); + const isDraggingRecord = useRecoilComponentValue( + isDraggingRecordComponentState, + ); const [isCurrentCardSelected, setIsCurrentCardSelected] = useRecoilComponentFamilyState( @@ -54,7 +56,7 @@ export const RecordCalendarCardHeader = ({ ); const handleChipClick = () => { - if (dragState.isDragging) { + if (isDraggingRecord) { return; } openRecordFromIndexView({ recordId }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/board/states/originalSelectionComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/board/states/originalSelectionComponentState.ts deleted file mode 100644 index 21d3bf1356..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/board/states/originalSelectionComponentState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export const originalSelectionComponentState = createComponentState({ - key: 'originalSelectionComponentState', - defaultValue: [], - componentInstanceContext: RecordBoardComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useEndRecordDrag.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useEndRecordDrag.test.tsx new file mode 100644 index 0000000000..eb1aeaff25 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useEndRecordDrag.test.tsx @@ -0,0 +1,73 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; + +import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag'; +import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState'; +import { isMultiDragActiveComponentState } from '@/object-record/record-drag/states/isMultiDragActiveComponentState'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/states/primaryDraggedRecordIdComponentState'; +import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; + +describe('useEndRecordDrag', () => { + const Wrapper = getJestMetadataAndApolloMocksWrapper({}); + + it('should clear all board drag states', () => { + const { result } = renderHook( + () => { + const [isMultiDragActive, setIsMultiDragActive] = + useRecoilComponentState(isMultiDragActiveComponentState); + + const [draggedRecordIds, setDraggedRecordIds] = useRecoilComponentState( + draggedRecordIdsComponentState, + ); + + const [primaryDraggedRecordId, setPrimaryDraggedRecordId] = + useRecoilComponentState(primaryDraggedRecordIdComponentState); + + const [originalSelection, setOriginalSelection] = + useRecoilComponentState(originalDragSelectionComponentState); + + const { endRecordDrag } = useEndRecordDrag(); + + return { + endRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + setIsMultiDragActive, + setDraggedRecordIds, + setPrimaryDraggedRecordId, + setOriginalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + act(() => { + result.current.setIsMultiDragActive(true); + result.current.setDraggedRecordIds(['record-1', 'record-2']); + result.current.setPrimaryDraggedRecordId('record-1'); + result.current.setOriginalSelection(['record-1', 'record-2', 'record-3']); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual(['record-1', 'record-2']); + expect(result.current.primaryDraggedRecordId).toBe('record-1'); + expect(result.current.originalSelection).toEqual([ + 'record-1', + 'record-2', + 'record-3', + ]); + + act(() => { + result.current.endRecordDrag(); + }); + + expect(result.current.isMultiDragActive).toBe(false); + expect(result.current.draggedRecordIds).toEqual([]); + expect(result.current.primaryDraggedRecordId).toBeNull(); + expect(result.current.originalSelection).toEqual([]); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useStartRecordDrag.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useStartRecordDrag.test.tsx new file mode 100644 index 0000000000..a2b1d1eb29 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/__tests__/useStartRecordDrag.test.tsx @@ -0,0 +1,293 @@ +import { type DragStart } from '@hello-pangea/dnd'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; + +import { useStartRecordDrag } from '@/object-record/record-drag/hooks/useStartRecordDrag'; +import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState'; +import { isMultiDragActiveComponentState } from '@/object-record/record-drag/states/isMultiDragActiveComponentState'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/states/primaryDraggedRecordIdComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; + +const createDragStart = (draggableId: string, index: number): DragStart => ({ + draggableId, + type: 'record', + source: { + droppableId: 'test-droppable', + index, + }, + mode: 'FLUID', +}); + +describe('useStartRecordDrag', () => { + const Wrapper = getJestMetadataAndApolloMocksWrapper({}); + + it('should set single drag state when dragged record is not in selection', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-1', 0); + const selectedRecordIds = ['record-2', 'record-3']; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual(['record-1']); + expect(result.current.primaryDraggedRecordId).toBe('record-1'); + expect(result.current.originalSelection).toEqual(['record-1']); + }); + + it('should set single drag state when only one record is selected', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-1', 0); + const selectedRecordIds = ['record-1']; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual(['record-1']); + expect(result.current.primaryDraggedRecordId).toBe('record-1'); + expect(result.current.originalSelection).toEqual(['record-1']); + }); + + it('should set multi drag state when multiple records are selected', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-2', 1); + const selectedRecordIds = ['record-1', 'record-2', 'record-3']; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual([ + 'record-1', + 'record-2', + 'record-3', + ]); + expect(result.current.primaryDraggedRecordId).toBe('record-2'); + expect(result.current.originalSelection).toEqual([ + 'record-1', + 'record-2', + 'record-3', + ]); + }); + + it('should handle empty selection', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-1', 0); + const selectedRecordIds: string[] = []; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual(['record-1']); + expect(result.current.primaryDraggedRecordId).toBe('record-1'); + expect(result.current.originalSelection).toEqual(['record-1']); + }); + + it('should set single drag state when dragged record is not in selection', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-1', 0); + const selectedRecordIds = ['record-2', 'record-3']; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual(['record-1']); + expect(result.current.primaryDraggedRecordId).toBe('record-1'); + expect(result.current.originalSelection).toEqual(['record-1']); + }); + + it('should set multi drag state when multiple records are selected', () => { + const { result } = renderHook( + () => { + const isMultiDragActive = useRecoilComponentValue( + isMultiDragActiveComponentState, + ); + const draggedRecordIds = useRecoilComponentValue( + draggedRecordIdsComponentState, + ); + const primaryDraggedRecordId = useRecoilComponentValue( + primaryDraggedRecordIdComponentState, + ); + const originalSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const { startRecordDrag } = useStartRecordDrag(); + + return { + startRecordDrag, + isMultiDragActive, + draggedRecordIds, + primaryDraggedRecordId, + originalSelection, + }; + }, + { wrapper: Wrapper }, + ); + + const dragStart = createDragStart('record-2', 1); + const selectedRecordIds = ['record-1', 'record-2', 'record-3']; + + act(() => { + result.current.startRecordDrag(dragStart, selectedRecordIds); + }); + + expect(result.current.isMultiDragActive).toBe(true); + expect(result.current.draggedRecordIds).toEqual([ + 'record-1', + 'record-2', + 'record-3', + ]); + expect(result.current.primaryDraggedRecordId).toBe('record-2'); + expect(result.current.originalSelection).toEqual([ + 'record-1', + 'record-2', + 'record-3', + ]); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/hooks/useEndRecordDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useEndRecordDrag.ts new file mode 100644 index 0000000000..710497921c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useEndRecordDrag.ts @@ -0,0 +1,110 @@ +import { useRecoilCallback } from 'recoil'; + +import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; + +import { isMultiDragActiveComponentState } from '@/object-record/record-drag/states/isMultiDragActiveComponentState'; + +import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; +import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState'; +import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/states/primaryDraggedRecordIdComponentState'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { isDefined } from 'twenty-shared/utils'; + +export const useEndRecordDrag = (contextStoreInstanceId?: string) => { + const isMultiDragActiveCallbackState = useRecoilComponentCallbackState( + isMultiDragActiveComponentState, + contextStoreInstanceId, + ); + + const draggedRecordIdsCallbackState = useRecoilComponentCallbackState( + draggedRecordIdsComponentState, + contextStoreInstanceId, + ); + + const primaryDraggedRecordIdCallbackState = useRecoilComponentCallbackState( + primaryDraggedRecordIdComponentState, + contextStoreInstanceId, + ); + + const originalSelectionCallbackState = useRecoilComponentCallbackState( + originalDragSelectionComponentState, + contextStoreInstanceId, + ); + + const isRecordIdPrimaryDragMultipleCallbackState = + useRecoilComponentCallbackState( + isRecordIdPrimaryDragMultipleComponentFamilyState, + contextStoreInstanceId, + ); + + const isRecordIdSecondaryDragMultipleCallbackState = + useRecoilComponentCallbackState( + isRecordIdSecondaryDragMultipleComponentFamilyState, + contextStoreInstanceId, + ); + + const isDraggingRecordCallbackState = useRecoilComponentCallbackState( + isDraggingRecordComponentState, + contextStoreInstanceId, + ); + + const endRecordDrag = useRecoilCallback( + ({ snapshot, set }) => + () => { + set(isDraggingRecordCallbackState, false); + + const currentlyDraggedRecordIds = getSnapshotValue( + snapshot, + draggedRecordIdsCallbackState, + ); + + const primaryDraggedRecordId = getSnapshotValue( + snapshot, + primaryDraggedRecordIdCallbackState, + ); + + if (currentlyDraggedRecordIds.length > 0) { + const secondaryDraggedIds = currentlyDraggedRecordIds.filter( + (recordIdToFilter) => recordIdToFilter !== primaryDraggedRecordId, + ); + + for (const secondaryDraggedId of secondaryDraggedIds) { + set( + isRecordIdSecondaryDragMultipleCallbackState({ + recordId: secondaryDraggedId, + }), + false, + ); + } + } + + if (isDefined(primaryDraggedRecordId)) { + set( + isRecordIdPrimaryDragMultipleCallbackState({ + recordId: primaryDraggedRecordId, + }), + false, + ); + } + + set(isMultiDragActiveCallbackState, false); + set(draggedRecordIdsCallbackState, []); + set(primaryDraggedRecordIdCallbackState, null); + set(originalSelectionCallbackState, []); + }, + [ + isMultiDragActiveCallbackState, + draggedRecordIdsCallbackState, + primaryDraggedRecordIdCallbackState, + originalSelectionCallbackState, + isRecordIdPrimaryDragMultipleCallbackState, + isRecordIdSecondaryDragMultipleCallbackState, + isDraggingRecordCallbackState, + ], + ); + + return { endRecordDrag }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/board/hooks/useRecordBoardDragOperations.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts similarity index 79% rename from packages/twenty-front/src/modules/object-record/record-drag/board/hooks/useRecordBoardDragOperations.ts rename to packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts index 6d93b2bd5a..3808934ce3 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/board/hooks/useRecordBoardDragOperations.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessBoardCardDrop.ts @@ -3,11 +3,12 @@ import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; -import { processGroupDragOperation } from '@/object-record/record-drag/shared/utils/processGroupDragOperation'; + +import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -export const useRecordBoardDragOperations = () => { +export const useProcessBoardCardDrop = () => { const { updateOneRecord, selectFieldMetadataItem } = useContext(RecordBoardContext); @@ -16,13 +17,13 @@ export const useRecordBoardDragOperations = () => { recordIndexRecordIdsByGroupComponentFamilyState, ); - const processDragOperation = useRecoilCallback( + const processBoardCardDrop = useRecoilCallback( ({ snapshot }) => - (result: DropResult, selectedRecordIds: string[]) => { + (boardCardDropResult: DropResult, selectedRecordIds: string[]) => { if (!selectFieldMetadataItem) return; - processGroupDragOperation({ - result, + processGroupDrop({ + groupDropResult: boardCardDropResult, snapshot, selectedRecordIds, recordIdsByGroupFamilyState: recordIndexRecordIdsByGroupFamilyState, @@ -45,6 +46,6 @@ export const useRecordBoardDragOperations = () => { ); return { - processDragOperation, + processBoardCardDrop, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/calendar/hooks/useHandleDragOneCalendarCard.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessCalendarCardDrop.ts similarity index 87% rename from packages/twenty-front/src/modules/object-record/record-drag/calendar/hooks/useHandleDragOneCalendarCard.ts rename to packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessCalendarCardDrop.ts index 1d26b1d619..25977872ae 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/calendar/hooks/useHandleDragOneCalendarCard.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessCalendarCardDrop.ts @@ -4,7 +4,8 @@ import { useRecoilCallback } from 'recoil'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useRecordCalendarContextOrThrow } from '@/object-record/record-calendar/contexts/RecordCalendarContext'; import { calendarDayRecordIdsComponentFamilySelector } from '@/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector'; -import { extractRecordPositions } from '@/object-record/record-drag/shared/utils/extractRecordPositions'; + +import { extractRecordPositions } from '@/object-record/record-drag/utils/extractRecordPositions'; import { isFieldDateTime } from '@/object-record/record-field/ui/types/guards/isFieldDateTime'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { computeNewPositionOfDraggedRecord } from '@/object-record/utils/computeNewPositionOfDraggedRecord'; @@ -21,7 +22,7 @@ import { } from 'date-fns'; import { isDefined } from 'twenty-shared/utils'; -export const useHandleDragOneCalendarCard = () => { +export const useProcessCalendarCardDrop = () => { const { objectMetadataItem } = useRecordCalendarContextOrThrow(); const { currentView } = useGetCurrentViewOnly(); const { updateOneRecord } = useUpdateOneRecord({ @@ -32,15 +33,18 @@ export const useHandleDragOneCalendarCard = () => { calendarDayRecordIdsComponentFamilySelector, ); - const processDragOperation = useRecoilCallback( + const processCalendarCardDrop = useRecoilCallback( ({ snapshot }) => - async (result: DropResult) => { - if (!result.destination || !currentView?.calendarFieldMetadataId) + async (calendarCardDropResult: DropResult) => { + if ( + !calendarCardDropResult.destination || + !currentView?.calendarFieldMetadataId + ) return; - const { draggableId: recordId } = result; - const destinationDate = result.destination.droppableId; - const destinationIndex = result.destination.index; + const { draggableId: recordId } = calendarCardDropResult; + const destinationDate = calendarCardDropResult.destination.droppableId; + const destinationIndex = calendarCardDropResult.destination.index; const record = snapshot .getLoadable(recordStoreFamilyState(recordId)) @@ -126,6 +130,6 @@ export const useHandleDragOneCalendarCard = () => { ); return { - processDragOperation, + processCalendarCardDrop, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableGroupDragOperations.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithGroupRecordDrop.ts similarity index 74% rename from packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableGroupDragOperations.ts rename to packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithGroupRecordDrop.ts index 1c0c11feb9..422fc414eb 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableGroupDragOperations.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithGroupRecordDrop.ts @@ -3,8 +3,9 @@ import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { processGroupDragOperation } from '@/object-record/record-drag/shared/utils/processGroupDragOperation'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; @@ -15,7 +16,7 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -export const useRecordTableGroupDragOperations = () => { +export const useProcessTableWithGroupRecordDrop = () => { const { objectNameSingular, objectMetadataItem, recordTableId } = useRecordTableContextOrThrow(); @@ -38,9 +39,15 @@ export const useRecordTableGroupDragOperations = () => { recordTableId, ); - const multiDragState = useRecordDragState('table', recordTableId); + const isDraggingRecordCallbackState = useRecoilComponentCallbackState( + isDraggingRecordComponentState, + ); - const processDragOperation = useRecoilCallback( + const originalDragSelectionCallbackState = useRecoilComponentCallbackState( + originalDragSelectionComponentState, + ); + + const processTableWithGroupRecordDrop = useRecoilCallback( ({ snapshot }) => (result: DropResult) => { if (!result.destination) return; @@ -63,8 +70,18 @@ export const useRecordTableGroupDragOperations = () => { throw new Error('Field metadata is not defined'); } - const selectedRecordIds = multiDragState.isDragging - ? multiDragState.originalSelection + const originalDragSelection = getSnapshotValue( + snapshot, + originalDragSelectionCallbackState, + ); + + const isDraggingRecord = getSnapshotValue( + snapshot, + isDraggingRecordCallbackState, + ); + + const selectedRecordIds = isDraggingRecord + ? originalDragSelection : getSnapshotValue(snapshot, selectedRowIdsSelector); const currentRecordSorts = snapshot @@ -76,8 +93,8 @@ export const useRecordTableGroupDragOperations = () => { return; } - processGroupDragOperation({ - result, + processGroupDrop({ + groupDropResult: result, snapshot, selectedRecordIds, recordIdsByGroupFamilyState: recordIdsByGroupFamilyState, @@ -99,10 +116,10 @@ export const useRecordTableGroupDragOperations = () => { updateOneRow, openModal, selectedRowIdsSelector, - multiDragState.originalSelection, - multiDragState.isDragging, + originalDragSelectionCallbackState, + isDraggingRecordCallbackState, ], ); - return { processDragOperation }; + return { processTableWithGroupRecordDrop }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableWithoutGroupDragOperations.ts b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithoutGroupRecordDrop.ts similarity index 81% rename from packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableWithoutGroupDragOperations.ts rename to packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithoutGroupRecordDrop.ts index 9e9f8f0243..d94dc4ec50 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/hooks/useRecordTableWithoutGroupDragOperations.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useProcessTableWithoutGroupRecordDrop.ts @@ -1,30 +1,29 @@ import { type DropResult } from '@hello-pangea/dnd'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; -import { getDragOperationType } from '@/object-record/record-drag/shared/utils/getDragOperationType'; + +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { getDragOperationType } from '@/object-record/record-drag/utils/getDragOperationType'; +import { processMultiDrag } from '@/object-record/record-drag/utils/processMultiDrag'; +import { processSingleDrag } from '@/object-record/record-drag/utils/processSingleDrag'; import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId'; -import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { isDefined } from 'twenty-shared/utils'; - -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { processMultiDrag } from '@/object-record/record-drag/shared/utils/processMultiDrag'; - -import { processSingleDrag } from '@/object-record/record-drag/shared/utils/processSingleDrag'; import { allRecordIdsWithoutGroupsComponentSelector } from '@/object-record/record-index/states/selectors/allRecordIdsWithoutGroupsComponentSelector'; +import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector'; import { useResetVirtualizationBecauseDataChanged } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizationBecauseDataChanged'; import { useTriggerFetchPages } from '@/object-record/record-table/virtualization/hooks/useTriggerFetchPages'; import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; -export const useRecordTableWithoutGroupDragOperations = () => { - const { objectNameSingular, recordTableId } = useRecordTableContextOrThrow(); +export const useProcessTableWithoutGroupRecordDrop = () => { + const { objectNameSingular } = useRecordTableContextOrThrow(); const { updateOneRecord: updateOneRow } = useUpdateOneRecord({ objectNameSingular, @@ -35,7 +34,10 @@ export const useRecordTableWithoutGroupDragOperations = () => { const selectedRowIdsSelector = useRecoilComponentCallbackState( selectedRowIdsComponentSelector, - recordTableId, + ); + + const originalDragSelectionCallbackState = useRecoilComponentCallbackState( + originalDragSelectionComponentState, ); const currentRecordSorts = useRecoilComponentValue( @@ -43,17 +45,16 @@ export const useRecordTableWithoutGroupDragOperations = () => { ); const { openModal } = useModal(); - const multiDragState = useRecordDragState('table', recordTableId); const { resetVirtualization } = useResetVirtualizationBecauseDataChanged(objectNameSingular); const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages(); - const processDragOperationWithoutGroup = useRecoilCallback( + const processTableWithoutGroupRecordDrop = useRecoilCallback( ({ snapshot }) => - async (result: DropResult) => { - if (!result.destination) return; + async (tableRecordDropResult: DropResult) => { + if (!tableRecordDropResult.destination) return; if (currentRecordSorts.length > 0) { openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID); @@ -65,7 +66,7 @@ export const useRecordTableWithoutGroupDragOperations = () => { allRecordIdsWithoutGroupCallbackSelector, ); - const draggedRecordId = result.draggableId; + const draggedRecordId = tableRecordDropResult.draggableId; const selectedRecordIds = getSnapshotValue( snapshot, selectedRowIdsSelector, @@ -90,7 +91,7 @@ export const useRecordTableWithoutGroupDragOperations = () => { if (dragOperationType === 'single') { const targetRecordId = allSparseRecordIds.at( - result.destination.index, + tableRecordDropResult.destination.index, ); if (!isDefined(targetRecordId)) { @@ -117,7 +118,7 @@ export const useRecordTableWithoutGroupDragOperations = () => { }); } else { const targetRecordId = allSparseRecordIds.at( - result.destination.index, + tableRecordDropResult.destination.index, ); if (!isDefined(targetRecordId)) { @@ -126,10 +127,15 @@ export const useRecordTableWithoutGroupDragOperations = () => { ); } + const originalDragSelection = getSnapshotValue( + snapshot, + originalDragSelectionCallbackState, + ); + const multiDragResult = processMultiDrag({ draggedRecordId, targetRecordId: targetRecordId ?? '', - selectedRecordIds: multiDragState.originalSelection, + selectedRecordIds: originalDragSelection, recordsWithPosition: contiguousRecordsWithPosition, }); @@ -152,12 +158,12 @@ export const useRecordTableWithoutGroupDragOperations = () => { updateOneRow, openModal, currentRecordSorts, - multiDragState.originalSelection, + originalDragSelectionCallbackState, allRecordIdsWithoutGroupCallbackSelector, resetVirtualization, triggerFetchPagesWithoutDebounce, ], ); - return { processDragOperationWithoutGroup }; + return { processTableWithoutGroupRecordDrop }; }; 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 new file mode 100644 index 0000000000..7506b73720 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/hooks/useStartRecordDrag.ts @@ -0,0 +1,110 @@ +import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState'; +import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState'; +import { isMultiDragActiveComponentState } from '@/object-record/record-drag/states/isMultiDragActiveComponentState'; +import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState'; +import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState'; +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; + +import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/states/primaryDraggedRecordIdComponentState'; +import { getDragOperationType } from '@/object-record/record-drag/utils/getDragOperationType'; +import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { type DragStart } from '@hello-pangea/dnd'; +import { useRecoilCallback } from 'recoil'; + +export const useStartRecordDrag = (contextStoreInstanceId?: string) => { + const isMultiDragActiveCallbackState = useRecoilComponentCallbackState( + isMultiDragActiveComponentState, + contextStoreInstanceId, + ); + + const draggedRecordIdsCallbackState = useRecoilComponentCallbackState( + draggedRecordIdsComponentState, + contextStoreInstanceId, + ); + + const isRecordIdPrimaryDragMultipleCallbackState = + useRecoilComponentCallbackState( + isRecordIdPrimaryDragMultipleComponentFamilyState, + contextStoreInstanceId, + ); + + const isRecordIdSecondaryDragMultipleCallbackState = + useRecoilComponentCallbackState( + isRecordIdSecondaryDragMultipleComponentFamilyState, + contextStoreInstanceId, + ); + + const primaryDraggedRecordIdCallbackState = useRecoilComponentCallbackState( + primaryDraggedRecordIdComponentState, + contextStoreInstanceId, + ); + + const originalSelectionCallbackState = useRecoilComponentCallbackState( + originalDragSelectionComponentState, + contextStoreInstanceId, + ); + + const isDraggingRecordCallbackState = useRecoilComponentCallbackState( + isDraggingRecordComponentState, + contextStoreInstanceId, + ); + + const startRecordDrag = useRecoilCallback( + ({ set }) => + (start: DragStart, selectedRecordIds: string[]) => { + set(isDraggingRecordCallbackState, true); + + const draggedRecordId = start.draggableId; + + const dragOperationType = getDragOperationType({ + draggedRecordId, + selectedRecordIds, + }); + + if (dragOperationType === 'multi') { + set(isMultiDragActiveCallbackState, true); + set(draggedRecordIdsCallbackState, selectedRecordIds); + set(primaryDraggedRecordIdCallbackState, draggedRecordId); + set(originalSelectionCallbackState, selectedRecordIds); + + set( + isRecordIdPrimaryDragMultipleCallbackState({ + recordId: draggedRecordId, + }), + true, + ); + + const secondaryDraggedIds = selectedRecordIds.filter( + (recordIdToFilter) => recordIdToFilter !== draggedRecordId, + ); + + for (const secondaryDraggedId of secondaryDraggedIds) { + set( + isRecordIdSecondaryDragMultipleCallbackState({ + recordId: secondaryDraggedId, + }), + true, + ); + } + } else { + set(isMultiDragActiveCallbackState, true); + set(draggedRecordIdsCallbackState, [draggedRecordId]); + set(primaryDraggedRecordIdCallbackState, draggedRecordId); + set(originalSelectionCallbackState, [draggedRecordId]); + } + }, + [ + isMultiDragActiveCallbackState, + draggedRecordIdsCallbackState, + primaryDraggedRecordIdCallbackState, + originalSelectionCallbackState, + isDraggingRecordCallbackState, + isRecordIdSecondaryDragMultipleCallbackState, + isRecordIdPrimaryDragMultipleCallbackState, + ], + ); + + return { + startRecordDrag, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useEndRecordDrag.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useEndRecordDrag.test.tsx deleted file mode 100644 index 30e365523d..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useEndRecordDrag.test.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; -import { useEndRecordDrag } from '@/object-record/record-drag/shared/hooks/useEndRecordDrag'; -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -describe('useEndRecordDrag', () => { - const Wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} - ); - - describe('Board context', () => { - it('should clear all board drag states', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionComponentState, - instanceId, - ); - - const setIsMultiDragActive = useSetRecoilState( - isMultiDragActiveComponentState.atomFamily({ - instanceId, - }), - ); - const setDraggedRecordIds = useSetRecoilState( - draggedRecordIdsComponentState.atomFamily({ - instanceId, - }), - ); - const setPrimaryDraggedRecordId = useSetRecoilState( - primaryDraggedRecordIdComponentState.atomFamily({ - instanceId, - }), - ); - const setOriginalSelection = useSetRecoilState( - originalSelectionComponentState.atomFamily({ - instanceId, - }), - ); - - const { endDrag } = useEndRecordDrag('board', instanceId); - - return { - endDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - setIsMultiDragActive, - setDraggedRecordIds, - setPrimaryDraggedRecordId, - setOriginalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - act(() => { - result.current.setIsMultiDragActive(true); - result.current.setDraggedRecordIds(['record-1', 'record-2']); - result.current.setPrimaryDraggedRecordId('record-1'); - result.current.setOriginalSelection([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1', 'record-2']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - - act(() => { - result.current.endDrag(); - }); - - expect(result.current.isMultiDragActive).toBe(false); - expect(result.current.draggedRecordIds).toEqual([]); - expect(result.current.primaryDraggedRecordId).toBeNull(); - expect(result.current.originalSelection).toEqual([]); - }); - }); - - describe('Table context', () => { - it('should clear all table drag states', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveTableComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsTableComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdTableComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionTableComponentState, - instanceId, - ); - - const setIsMultiDragActive = useSetRecoilState( - isMultiDragActiveTableComponentState.atomFamily({ - instanceId, - }), - ); - const setDraggedRecordIds = useSetRecoilState( - draggedRecordIdsTableComponentState.atomFamily({ - instanceId, - }), - ); - const setPrimaryDraggedRecordId = useSetRecoilState( - primaryDraggedRecordIdTableComponentState.atomFamily({ - instanceId, - }), - ); - const setOriginalSelection = useSetRecoilState( - originalSelectionTableComponentState.atomFamily({ - instanceId, - }), - ); - - const { endDrag } = useEndRecordDrag('table', instanceId); - - return { - endDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - setIsMultiDragActive, - setDraggedRecordIds, - setPrimaryDraggedRecordId, - setOriginalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - act(() => { - result.current.setIsMultiDragActive(true); - result.current.setDraggedRecordIds(['record-1', 'record-2']); - result.current.setPrimaryDraggedRecordId('record-1'); - result.current.setOriginalSelection([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1', 'record-2']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - - act(() => { - result.current.endDrag(); - }); - - expect(result.current.isMultiDragActive).toBe(false); - expect(result.current.draggedRecordIds).toEqual([]); - expect(result.current.primaryDraggedRecordId).toBeNull(); - expect(result.current.originalSelection).toEqual([]); - }); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useMultiDragState.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useMultiDragState.test.tsx deleted file mode 100644 index 8d1a10461d..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useMultiDragState.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { type DragStart } from '@hello-pangea/dnd'; -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; - -import { useMultiDragState } from '@/object-record/record-drag/shared/hooks/useMultiDragState'; - -const createDragStart = (draggableId: string, index: number): DragStart => ({ - draggableId, - type: 'record', - source: { - droppableId: 'test-droppable', - index, - }, - mode: 'FLUID', -}); - -describe('useMultiDragState', () => { - it('should have initial state with no drag', () => { - const { result } = renderHook(() => useMultiDragState()); - - expect(result.current.multiDragState.isDragging).toBe(false); - expect(result.current.multiDragState.draggedRecordIds).toEqual([]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBeNull(); - expect(result.current.multiDragState.originalSelection).toEqual([]); - }); - - it('should handle single drag when record is not in selection', () => { - const { result } = renderHook(() => useMultiDragState()); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.multiDragState.isDragging).toBe(true); - expect(result.current.multiDragState.draggedRecordIds).toEqual([ - 'record-1', - ]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBe( - 'record-1', - ); - expect(result.current.multiDragState.originalSelection).toEqual([ - 'record-1', - ]); - }); - - it('should handle single drag when only one record is selected', () => { - const { result } = renderHook(() => useMultiDragState()); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-1']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.multiDragState.isDragging).toBe(true); - expect(result.current.multiDragState.draggedRecordIds).toEqual([ - 'record-1', - ]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBe( - 'record-1', - ); - expect(result.current.multiDragState.originalSelection).toEqual([ - 'record-1', - ]); - }); - - it('should handle multi drag when multiple records are selected', () => { - const { result } = renderHook(() => useMultiDragState()); - - const dragStart = createDragStart('record-2', 1); - const selectedRecordIds = ['record-1', 'record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.multiDragState.isDragging).toBe(true); - expect(result.current.multiDragState.draggedRecordIds).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBe( - 'record-2', - ); - expect(result.current.multiDragState.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - it('should clear state when endDrag is called', () => { - const { result } = renderHook(() => useMultiDragState()); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-1', 'record-2']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.multiDragState.isDragging).toBe(true); - - act(() => { - result.current.endDrag(); - }); - - expect(result.current.multiDragState.isDragging).toBe(false); - expect(result.current.multiDragState.draggedRecordIds).toEqual([]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBeNull(); - expect(result.current.multiDragState.originalSelection).toEqual([]); - }); - - it('should handle empty selection', () => { - const { result } = renderHook(() => useMultiDragState()); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds: string[] = []; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.multiDragState.isDragging).toBe(true); - expect(result.current.multiDragState.draggedRecordIds).toEqual([ - 'record-1', - ]); - expect(result.current.multiDragState.primaryDraggedRecordId).toBe( - 'record-1', - ); - expect(result.current.multiDragState.originalSelection).toEqual([ - 'record-1', - ]); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useRecordDragState.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useRecordDragState.test.tsx deleted file mode 100644 index a193ec4eaa..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useRecordDragState.test.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; - -describe('useRecordDragState', () => { - const Wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} - ); - - describe('Board context', () => { - it('should return current board drag state', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const dragState = useRecordDragState('board', instanceId); - - const setIsMultiDragActive = useSetRecoilState( - isMultiDragActiveComponentState.atomFamily({ - instanceId, - }), - ); - const setDraggedRecordIds = useSetRecoilState( - draggedRecordIdsComponentState.atomFamily({ - instanceId, - }), - ); - const setPrimaryDraggedRecordId = useSetRecoilState( - primaryDraggedRecordIdComponentState.atomFamily({ - instanceId, - }), - ); - const setOriginalSelection = useSetRecoilState( - originalSelectionComponentState.atomFamily({ - instanceId, - }), - ); - - return { - dragState, - setIsMultiDragActive, - setDraggedRecordIds, - setPrimaryDraggedRecordId, - setOriginalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - expect(result.current.dragState.isDragging).toBe(false); - expect(result.current.dragState.draggedRecordIds).toEqual([]); - expect(result.current.dragState.primaryDraggedRecordId).toBeNull(); - expect(result.current.dragState.originalSelection).toEqual([]); - - act(() => { - result.current.setIsMultiDragActive(true); - result.current.setDraggedRecordIds(['record-1', 'record-2']); - result.current.setPrimaryDraggedRecordId('record-1'); - result.current.setOriginalSelection([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - expect(result.current.dragState.isDragging).toBe(true); - expect(result.current.dragState.draggedRecordIds).toEqual([ - 'record-1', - 'record-2', - ]); - expect(result.current.dragState.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.dragState.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - }); - - describe('Table context', () => { - it('should return current table drag state', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const dragState = useRecordDragState('table', instanceId); - - const setIsMultiDragActive = useSetRecoilState( - isMultiDragActiveTableComponentState.atomFamily({ - instanceId, - }), - ); - const setDraggedRecordIds = useSetRecoilState( - draggedRecordIdsTableComponentState.atomFamily({ - instanceId, - }), - ); - const setPrimaryDraggedRecordId = useSetRecoilState( - primaryDraggedRecordIdTableComponentState.atomFamily({ - instanceId, - }), - ); - const setOriginalSelection = useSetRecoilState( - originalSelectionTableComponentState.atomFamily({ - instanceId, - }), - ); - - return { - dragState, - setIsMultiDragActive, - setDraggedRecordIds, - setPrimaryDraggedRecordId, - setOriginalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - expect(result.current.dragState.isDragging).toBe(false); - expect(result.current.dragState.draggedRecordIds).toEqual([]); - expect(result.current.dragState.primaryDraggedRecordId).toBeNull(); - expect(result.current.dragState.originalSelection).toEqual([]); - - act(() => { - result.current.setIsMultiDragActive(true); - result.current.setDraggedRecordIds(['record-1', 'record-2']); - result.current.setPrimaryDraggedRecordId('record-1'); - result.current.setOriginalSelection([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - expect(result.current.dragState.isDragging).toBe(true); - expect(result.current.dragState.draggedRecordIds).toEqual([ - 'record-1', - 'record-2', - ]); - expect(result.current.dragState.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.dragState.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useStartRecordDrag.test.tsx b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useStartRecordDrag.test.tsx deleted file mode 100644 index 8c9d10e36f..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/__tests__/useStartRecordDrag.test.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import { type DragStart } from '@hello-pangea/dnd'; -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { RecoilRoot } from 'recoil'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; -import { useStartRecordDrag } from '@/object-record/record-drag/shared/hooks/useStartRecordDrag'; -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -const createDragStart = (draggableId: string, index: number): DragStart => ({ - draggableId, - type: 'record', - source: { - droppableId: 'test-droppable', - index, - }, - mode: 'FLUID', -}); - -describe('useStartRecordDrag', () => { - const Wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} - ); - - describe('Board context', () => { - it('should set single drag state when dragged record is not in selection', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('board', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual(['record-1']); - }); - - it('should set single drag state when only one record is selected', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('board', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-1']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual(['record-1']); - }); - - it('should set multi drag state when multiple records are selected', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('board', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-2', 1); - const selectedRecordIds = ['record-1', 'record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - expect(result.current.primaryDraggedRecordId).toBe('record-2'); - expect(result.current.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - - it('should handle empty selection', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('board', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds: string[] = []; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual(['record-1']); - }); - }); - - describe('Table context', () => { - it('should set single drag state when dragged record is not in selection', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveTableComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsTableComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdTableComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionTableComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('table', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-1', 0); - const selectedRecordIds = ['record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual(['record-1']); - expect(result.current.primaryDraggedRecordId).toBe('record-1'); - expect(result.current.originalSelection).toEqual(['record-1']); - }); - - it('should set multi drag state when multiple records are selected', () => { - const instanceId = 'test-instance'; - - const { result } = renderHook( - () => { - const isMultiDragActive = useRecoilComponentValue( - isMultiDragActiveTableComponentState, - instanceId, - ); - const draggedRecordIds = useRecoilComponentValue( - draggedRecordIdsTableComponentState, - instanceId, - ); - const primaryDraggedRecordId = useRecoilComponentValue( - primaryDraggedRecordIdTableComponentState, - instanceId, - ); - const originalSelection = useRecoilComponentValue( - originalSelectionTableComponentState, - instanceId, - ); - - const { startDrag } = useStartRecordDrag('table', instanceId); - - return { - startDrag, - isMultiDragActive, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; - }, - { wrapper: Wrapper }, - ); - - const dragStart = createDragStart('record-2', 1); - const selectedRecordIds = ['record-1', 'record-2', 'record-3']; - - act(() => { - result.current.startDrag(dragStart, selectedRecordIds); - }); - - expect(result.current.isMultiDragActive).toBe(true); - expect(result.current.draggedRecordIds).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - expect(result.current.primaryDraggedRecordId).toBe('record-2'); - expect(result.current.originalSelection).toEqual([ - 'record-1', - 'record-2', - 'record-3', - ]); - }); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useEndRecordDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useEndRecordDrag.ts deleted file mode 100644 index 208a5a7ecc..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useEndRecordDrag.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { type RecordDragContext } from '@/object-record/record-drag/shared/types/RecordDragContext'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; - -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; - -export const useEndRecordDrag = ( - context: RecordDragContext, - instanceId?: string, -) => { - const isMultiDragActiveCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? isMultiDragActiveComponentState - : isMultiDragActiveTableComponentState, - instanceId, - ); - - const draggedRecordIdsCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? draggedRecordIdsComponentState - : draggedRecordIdsTableComponentState, - instanceId, - ); - - const primaryDraggedRecordIdCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? primaryDraggedRecordIdComponentState - : primaryDraggedRecordIdTableComponentState, - instanceId, - ); - - const originalSelectionCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? originalSelectionComponentState - : originalSelectionTableComponentState, - instanceId, - ); - - const endDrag = useRecoilCallback( - ({ set }) => - () => { - set(isMultiDragActiveCallbackState, false); - set(draggedRecordIdsCallbackState, []); - set(primaryDraggedRecordIdCallbackState, null); - set(originalSelectionCallbackState, []); - }, - [ - isMultiDragActiveCallbackState, - draggedRecordIdsCallbackState, - primaryDraggedRecordIdCallbackState, - originalSelectionCallbackState, - ], - ); - - return { endDrag }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useMultiDragState.ts b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useMultiDragState.ts deleted file mode 100644 index 03c1cdd13c..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useMultiDragState.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { type MultiDragState } from '@/object-record/record-drag/shared/types/MultiDragState'; -import { getDragOperationType } from '@/object-record/record-drag/shared/utils/getDragOperationType'; -import { type DragStart } from '@hello-pangea/dnd'; -import { useState } from 'react'; - -export const useMultiDragState = () => { - const [multiDragState, setMultiDragState] = useState({ - isDragging: false, - draggedRecordIds: [], - primaryDraggedRecordId: null, - originalSelection: [], - }); - - const startDrag = (start: DragStart, selectedRecordIds: string[]) => { - const draggedRecordId = start.draggableId; - - const operationType = getDragOperationType({ - draggedRecordId, - selectedRecordIds, - }); - - if (operationType === 'multi') { - setMultiDragState({ - isDragging: true, - draggedRecordIds: selectedRecordIds, - primaryDraggedRecordId: draggedRecordId, - originalSelection: selectedRecordIds, - }); - } else { - setMultiDragState({ - isDragging: true, - draggedRecordIds: [draggedRecordId], - primaryDraggedRecordId: draggedRecordId, - originalSelection: [draggedRecordId], - }); - } - }; - - const endDrag = () => { - setMultiDragState({ - isDragging: false, - draggedRecordIds: [], - primaryDraggedRecordId: null, - originalSelection: [], - }); - }; - - return { - multiDragState, - startDrag, - endDrag, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useRecordDragState.ts b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useRecordDragState.ts deleted file mode 100644 index fc10708b6e..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useRecordDragState.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { type RecordDragContext } from '@/object-record/record-drag/shared/types/RecordDragContext'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; - -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; - -export const useRecordDragState = ( - context: RecordDragContext, - instanceId?: string, -) => { - const isDragging = useRecoilComponentValue( - context === 'board' - ? isMultiDragActiveComponentState - : isMultiDragActiveTableComponentState, - instanceId, - ); - - const draggedRecordIds = useRecoilComponentValue( - context === 'board' - ? draggedRecordIdsComponentState - : draggedRecordIdsTableComponentState, - instanceId, - ); - - const primaryDraggedRecordId = useRecoilComponentValue( - context === 'board' - ? primaryDraggedRecordIdComponentState - : primaryDraggedRecordIdTableComponentState, - instanceId, - ); - - const originalSelection = useRecoilComponentValue( - context === 'board' - ? originalSelectionComponentState - : originalSelectionTableComponentState, - instanceId, - ); - - return { - isDragging, - draggedRecordIds, - primaryDraggedRecordId, - originalSelection, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useStartRecordDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useStartRecordDrag.ts deleted file mode 100644 index eebd3d4abc..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/hooks/useStartRecordDrag.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { type DragStart } from '@hello-pangea/dnd'; -import { useRecoilCallback } from 'recoil'; - -import { type RecordDragContext } from '@/object-record/record-drag/shared/types/RecordDragContext'; -import { getDragOperationType } from '@/object-record/record-drag/shared/utils/getDragOperationType'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; - -import { draggedRecordIdsComponentState } from '@/object-record/record-drag/board/states/draggedRecordIdsComponentState'; -import { isMultiDragActiveComponentState } from '@/object-record/record-drag/board/states/isMultiDragActiveComponentState'; -import { originalSelectionComponentState } from '@/object-record/record-drag/board/states/originalSelectionComponentState'; -import { primaryDraggedRecordIdComponentState } from '@/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState'; - -import { draggedRecordIdsTableComponentState } from '@/object-record/record-drag/table/states/draggedRecordIdsTableComponentState'; -import { isMultiDragActiveTableComponentState } from '@/object-record/record-drag/table/states/isMultiDragActiveTableComponentState'; -import { originalSelectionTableComponentState } from '@/object-record/record-drag/table/states/originalSelectionTableComponentState'; -import { primaryDraggedRecordIdTableComponentState } from '@/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState'; - -export const useStartRecordDrag = ( - context: RecordDragContext, - instanceId?: string, -) => { - const isMultiDragActiveCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? isMultiDragActiveComponentState - : isMultiDragActiveTableComponentState, - instanceId, - ); - - const draggedRecordIdsCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? draggedRecordIdsComponentState - : draggedRecordIdsTableComponentState, - instanceId, - ); - - const primaryDraggedRecordIdCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? primaryDraggedRecordIdComponentState - : primaryDraggedRecordIdTableComponentState, - instanceId, - ); - - const originalSelectionCallbackState = useRecoilComponentCallbackState( - context === 'board' - ? originalSelectionComponentState - : originalSelectionTableComponentState, - instanceId, - ); - - const startDrag = useRecoilCallback( - ({ set }) => - (start: DragStart, selectedRecordIds: string[]) => { - const draggedRecordId = start.draggableId; - - const dragOperationType = getDragOperationType({ - draggedRecordId, - selectedRecordIds, - }); - - if (dragOperationType === 'multi') { - set(isMultiDragActiveCallbackState, true); - set(draggedRecordIdsCallbackState, selectedRecordIds); - set(primaryDraggedRecordIdCallbackState, draggedRecordId); - set(originalSelectionCallbackState, selectedRecordIds); - } else { - set(isMultiDragActiveCallbackState, true); - set(draggedRecordIdsCallbackState, [draggedRecordId]); - set(primaryDraggedRecordIdCallbackState, draggedRecordId); - set(originalSelectionCallbackState, [draggedRecordId]); - } - }, - [ - isMultiDragActiveCallbackState, - draggedRecordIdsCallbackState, - primaryDraggedRecordIdCallbackState, - originalSelectionCallbackState, - ], - ); - - return { startDrag }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragState.ts b/packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragState.ts deleted file mode 100644 index b98a22386c..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragState.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type MultiDragState = { - isDragging: boolean; - draggedRecordIds: string[]; - primaryDraggedRecordId: string | null; - originalSelection: string[]; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/board/states/draggedRecordIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/draggedRecordIdsComponentState.ts similarity index 55% rename from packages/twenty-front/src/modules/object-record/record-drag/board/states/draggedRecordIdsComponentState.ts rename to packages/twenty-front/src/modules/object-record/record-drag/states/draggedRecordIdsComponentState.ts index a14972d2bb..1271f24fd4 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/board/states/draggedRecordIdsComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/draggedRecordIdsComponentState.ts @@ -1,8 +1,8 @@ -import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; export const draggedRecordIdsComponentState = createComponentState({ key: 'draggedRecordIdsComponentState', defaultValue: [], - componentInstanceContext: RecordBoardComponentInstanceContext, + componentInstanceContext: ContextStoreComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/states/isDraggingRecordComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/isDraggingRecordComponentState.ts new file mode 100644 index 0000000000..a148ed084f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/isDraggingRecordComponentState.ts @@ -0,0 +1,8 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const isDraggingRecordComponentState = createComponentState({ + key: 'isDraggingRecordComponentState', + defaultValue: false, + componentInstanceContext: ContextStoreComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/board/states/isMultiDragActiveComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/isMultiDragActiveComponentState.ts similarity index 55% rename from packages/twenty-front/src/modules/object-record/record-drag/board/states/isMultiDragActiveComponentState.ts rename to packages/twenty-front/src/modules/object-record/record-drag/states/isMultiDragActiveComponentState.ts index a3622333d7..7a07ae233c 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/board/states/isMultiDragActiveComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/isMultiDragActiveComponentState.ts @@ -1,8 +1,8 @@ -import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; export const isMultiDragActiveComponentState = createComponentState({ key: 'isMultiDragActiveComponentState', defaultValue: false, - componentInstanceContext: RecordBoardComponentInstanceContext, + componentInstanceContext: ContextStoreComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState.ts new file mode 100644 index 0000000000..a31530d205 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState.ts @@ -0,0 +1,9 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; + +export const isRecordIdPrimaryDragMultipleComponentFamilyState = + createComponentFamilyState({ + key: 'isRecordIdPrimaryDragMultipleComponentFamilyState', + defaultValue: false, + componentInstanceContext: ContextStoreComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState.ts new file mode 100644 index 0000000000..eee3d560df --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState.ts @@ -0,0 +1,9 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; + +export const isRecordIdSecondaryDragMultipleComponentFamilyState = + createComponentFamilyState({ + key: 'isRecordIdSecondaryDragMultipleComponentFamilyState', + defaultValue: false, + componentInstanceContext: ContextStoreComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/states/originalDragSelectionComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/originalDragSelectionComponentState.ts new file mode 100644 index 0000000000..e1854577ff --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/originalDragSelectionComponentState.ts @@ -0,0 +1,10 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const originalDragSelectionComponentState = createComponentState< + string[] +>({ + key: 'originalDragSelectionComponentState', + defaultValue: [], + componentInstanceContext: ContextStoreComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/states/primaryDraggedRecordIdComponentState.ts similarity index 57% rename from packages/twenty-front/src/modules/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState.ts rename to packages/twenty-front/src/modules/object-record/record-drag/states/primaryDraggedRecordIdComponentState.ts index 170244aaed..00443ff98b 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/board/states/primaryDraggedRecordIdComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/states/primaryDraggedRecordIdComponentState.ts @@ -1,4 +1,4 @@ -import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; export const primaryDraggedRecordIdComponentState = createComponentState< @@ -6,5 +6,5 @@ export const primaryDraggedRecordIdComponentState = createComponentState< >({ key: 'primaryDraggedRecordIdComponentState', defaultValue: null, - componentInstanceContext: RecordBoardComponentInstanceContext, + componentInstanceContext: ContextStoreComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/states/draggedRecordIdsTableComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/table/states/draggedRecordIdsTableComponentState.ts deleted file mode 100644 index e7e9f4269f..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/states/draggedRecordIdsTableComponentState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export const draggedRecordIdsTableComponentState = createComponentState< - string[] ->({ - key: 'draggedRecordIdsTableComponentState', - defaultValue: [], - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/states/isMultiDragActiveTableComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/table/states/isMultiDragActiveTableComponentState.ts deleted file mode 100644 index add2e68a67..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/states/isMultiDragActiveTableComponentState.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export const isMultiDragActiveTableComponentState = - createComponentState({ - key: 'isMultiDragActiveTableComponentState', - defaultValue: false, - componentInstanceContext: RecordTableComponentInstanceContext, - }); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/states/originalSelectionTableComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/table/states/originalSelectionTableComponentState.ts deleted file mode 100644 index c9a4aa6851..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/states/originalSelectionTableComponentState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export const originalSelectionTableComponentState = createComponentState< - string[] ->({ - key: 'originalSelectionTableComponentState', - defaultValue: [], - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState.ts b/packages/twenty-front/src/modules/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState.ts deleted file mode 100644 index 202b0e4f96..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-drag/table/states/primaryDraggedRecordIdTableComponentState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export const primaryDraggedRecordIdTableComponentState = createComponentState< - string | null ->({ - key: 'primaryDraggedRecordIdTableComponentState', - defaultValue: null, - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/types/DragOperationType.ts b/packages/twenty-front/src/modules/object-record/record-drag/types/DragOperationType.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/types/DragOperationType.ts rename to packages/twenty-front/src/modules/object-record/record-drag/types/DragOperationType.ts diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragResult.ts b/packages/twenty-front/src/modules/object-record/record-drag/types/MultiDragResult.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/types/MultiDragResult.ts rename to packages/twenty-front/src/modules/object-record/record-drag/types/MultiDragResult.ts diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/types/RecordDragContext.ts b/packages/twenty-front/src/modules/object-record/record-drag/types/RecordDragContext.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/types/RecordDragContext.ts rename to packages/twenty-front/src/modules/object-record/record-drag/types/RecordDragContext.ts diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/extractRecordPositions.test.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/extractRecordPositions.test.ts similarity index 99% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/extractRecordPositions.test.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/extractRecordPositions.test.ts index 63bcea6b3d..212a750e59 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/extractRecordPositions.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/extractRecordPositions.test.ts @@ -1,6 +1,6 @@ +import { extractRecordPositions } from '@/object-record/record-drag/utils/extractRecordPositions'; import { type Snapshot } from 'recoil'; -import { extractRecordPositions } from '@/object-record/record-drag/shared/utils/extractRecordPositions'; import { isDefined } from 'twenty-shared/utils'; describe('extractRecordPositions', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/getDragOperationType.test.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/getDragOperationType.test.ts similarity index 97% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/getDragOperationType.test.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/getDragOperationType.test.ts index 1792d7b435..603f33374e 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/getDragOperationType.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/getDragOperationType.test.ts @@ -1,4 +1,4 @@ -import { getDragOperationType } from '@/object-record/record-drag/shared/utils/getDragOperationType'; +import { getDragOperationType } from '@/object-record/record-drag/utils/getDragOperationType'; describe('getDragOperationType', () => { it('should return single when dragged record is not selected', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processMultiDrag.test.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processMultiDrag.test.ts similarity index 95% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processMultiDrag.test.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processMultiDrag.test.ts index 2360909d43..dd9d756edb 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processMultiDrag.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processMultiDrag.test.ts @@ -1,4 +1,4 @@ -import { processMultiDrag } from '@/object-record/record-drag/shared/utils/processMultiDrag'; +import { processMultiDrag } from '@/object-record/record-drag/utils/processMultiDrag'; import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord'; describe('processMultiDrag', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processSingleDrag.test.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processSingleDrag.test.ts similarity index 95% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processSingleDrag.test.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processSingleDrag.test.ts index d38720e657..689cdb5cc9 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/__tests__/processSingleDrag.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/__tests__/processSingleDrag.test.ts @@ -1,4 +1,4 @@ -import { processSingleDrag } from '@/object-record/record-drag/shared/utils/processSingleDrag'; +import { processSingleDrag } from '@/object-record/record-drag/utils/processSingleDrag'; import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord'; describe('processSingleDrag', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/extractRecordPositions.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/extractRecordPositions.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/extractRecordPositions.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/extractRecordPositions.ts diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/getDragOperationType.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/getDragOperationType.ts similarity index 82% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/getDragOperationType.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/getDragOperationType.ts index ecae9bcc13..46a2bb02ed 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/getDragOperationType.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/getDragOperationType.ts @@ -1,4 +1,4 @@ -import { type DragOperationType } from '../types/DragOperationType'; +import { type DragOperationType } from '@/object-record/record-drag/types/DragOperationType'; type DragOperationContext = { draggedRecordId: string; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processGroupDragOperation.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts similarity index 85% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processGroupDragOperation.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts index e22bc25e0d..d8cae423f7 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processGroupDragOperation.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/processGroupDrop.ts @@ -5,14 +5,14 @@ import { isDefined } from 'twenty-shared/utils'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { processSingleDrag } from '@/object-record/record-drag/shared/utils/processSingleDrag'; +import { processSingleDrag } from '@/object-record/record-drag/utils/processSingleDrag'; import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { extractRecordPositions } from './extractRecordPositions'; import { getDragOperationType } from './getDragOperationType'; import { processMultiDrag } from './processMultiDrag'; -type ProcessGroupDragOperationParams = { - result: DropResult; +type ProcessGroupDropParams = { + groupDropResult: DropResult; snapshot: Snapshot; selectedRecordIds: string[]; recordIdsByGroupFamilyState: any; @@ -22,18 +22,18 @@ type ProcessGroupDragOperationParams = { ) => void; }; -export const processGroupDragOperation = ({ - result, +export const processGroupDrop = ({ + groupDropResult, snapshot, selectedRecordIds, recordIdsByGroupFamilyState, onUpdateRecord, -}: ProcessGroupDragOperationParams) => { - if (!result.destination) { +}: ProcessGroupDropParams) => { + if (!groupDropResult.destination) { return; } - const destinationGroupId = result.destination.droppableId; + const destinationGroupId = groupDropResult.destination.droppableId; const recordGroup = getSnapshotValue( snapshot, @@ -49,7 +49,7 @@ export const processGroupDragOperation = ({ recordIdsByGroupFamilyState(destinationGroupId), ) as string[]; - const draggedRecordId = result.draggableId; + const draggedRecordId = groupDropResult.draggableId; const dragOperationType = getDragOperationType({ draggedRecordId, @@ -87,13 +87,13 @@ export const processGroupDragOperation = ({ snapshot, ); - const destinationIndex = result.destination.index; + const destinationIndex = groupDropResult.destination.index; const isDroppedAfterList = destinationIndex >= recordsWithPosition.length; const targetRecord = isDroppedAfterList ? recordsWithPosition.at(-1) - : recordsWithPosition.at(result.destination.index); + : recordsWithPosition.at(groupDropResult.destination.index); if (!isDefined(targetRecord)) { throw new Error( diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processMultiDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/processMultiDrag.ts similarity index 96% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processMultiDrag.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/processMultiDrag.ts index d09e4ca56f..693aeffe43 100644 --- a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processMultiDrag.ts +++ b/packages/twenty-front/src/modules/object-record/record-drag/utils/processMultiDrag.ts @@ -1,4 +1,4 @@ -import { type MultiDragResult } from '@/object-record/record-drag/shared/types/MultiDragResult'; +import { type MultiDragResult } from '@/object-record/record-drag/types/MultiDragResult'; import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord'; import { computeNewPositionsOfDraggedRecords } from '@/object-record/utils/computeNewPositionsOfDraggedRecords'; diff --git a/packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processSingleDrag.ts b/packages/twenty-front/src/modules/object-record/record-drag/utils/processSingleDrag.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-drag/shared/utils/processSingleDrag.ts rename to packages/twenty-front/src/modules/object-record/record-drag/utils/processSingleDrag.ts diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts index 725156087a..516e58933a 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts @@ -54,9 +54,7 @@ export const useRecordGroupActions = ({ ); const { handleVisibilityChange: handleRecordGroupVisibilityChange } = - useRecordGroupVisibility({ - viewType, - }); + useRecordGroupVisibility(); const setNavigationMemorizedUrl = useSetRecoilState( navigationMemorizedUrlState, diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupVisibility.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupVisibility.ts index cbaca36ffe..226246751b 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupVisibility.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupVisibility.ts @@ -1,22 +1,15 @@ import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; -import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState'; +import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups'; -import { type ViewType } from '@/views/types/ViewType'; import { recordGroupDefinitionToViewGroup } from '@/views/utils/recordGroupDefinitionToViewGroup'; import { useRecoilCallback } from 'recoil'; -type UseRecordGroupVisibilityParams = { - viewType: ViewType; -}; - -export const useRecordGroupVisibility = ({ - viewType, -}: UseRecordGroupVisibilityParams) => { - const objectOptionsDropdownRecordGroupHideFamilyState = +export const useRecordGroupVisibility = () => { + const recordIndexShouldHideEmptyRecordGroupsCallbackState = useRecoilComponentCallbackState( - recordIndexRecordGroupHideComponentFamilyState, + recordIndexShouldHideEmptyRecordGroupsComponentState, ); const { saveViewGroup } = useSaveCurrentViewGroups(); @@ -38,11 +31,11 @@ export const useRecordGroupVisibility = ({ ({ set }) => async () => { set( - objectOptionsDropdownRecordGroupHideFamilyState(viewType), + recordIndexShouldHideEmptyRecordGroupsCallbackState, (currentHideState) => !currentHideState, ); }, - [viewType, objectOptionsDropdownRecordGroupHideFamilyState], + [recordIndexShouldHideEmptyRecordGroupsCallbackState], ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState.ts new file mode 100644 index 0000000000..d7a1c9906e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-group/states/emptyRecordGroupByIdComponentFamilyState.ts @@ -0,0 +1,9 @@ +import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; +import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; + +export const emptyRecordGroupByIdComponentFamilyState = + createComponentFamilyState({ + key: 'emptyRecordGroupByIdComponentFamilyState', + defaultValue: false, + componentInstanceContext: ViewComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector.ts index aa24cc2eb6..90f72745f5 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector.ts @@ -3,13 +3,12 @@ import { recordGroupIdsComponentState } from '@/object-record/record-group/state import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort'; import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert'; -import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState'; import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; -import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { type ViewType } from '@/views/types/ViewType'; + import { isDefined } from 'twenty-shared/utils'; export const visibleRecordGroupIdsComponentFamilySelector = @@ -17,24 +16,19 @@ export const visibleRecordGroupIdsComponentFamilySelector = key: 'visibleRecordGroupIdsComponentFamilySelector', componentInstanceContext: ViewComponentInstanceContext, get: - ({ instanceId, familyKey }) => + ({ instanceId }) => ({ get }) => { const recordGroupSort = get( recordIndexRecordGroupSortComponentState.atomFamily({ instanceId, }), ); + const recordGroupIds = get( recordGroupIdsComponentState.atomFamily({ instanceId, }), ); - const hideEmptyRecordGroup = get( - recordIndexRecordGroupHideComponentFamilyState.atomFamily({ - instanceId, - familyKey, - }), - ); const result: RecordGroupDefinition[] = []; @@ -57,21 +51,11 @@ export const visibleRecordGroupIdsComponentFamilySelector = const recordGroupDefinition = get( recordGroupDefinitionFamilyState(recordGroupId), ); - const recordIds = get( - recordIndexRecordIdsByGroupComponentFamilyState.atomFamily({ - instanceId, - familyKey: recordGroupId, - }), - ); if (!isDefined(recordGroupDefinition)) { continue; } - if (hideEmptyRecordGroup && recordIds.length === 0) { - continue; - } - if (!recordGroupDefinition.isVisible) { continue; } 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 8e8847da6d..5c00c0ee94 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 @@ -14,6 +14,7 @@ import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-ind import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; + type RecordIndexBoardContainerProps = { recordBoardId: string; viewBarId: string; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState.ts deleted file mode 100644 index 88d3b2ae02..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; -import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { ViewType } from '@/views/types/ViewType'; - -export const recordIndexRecordGroupHideComponentFamilyState = - createComponentFamilyState({ - key: 'recordIndexRecordGroupHideComponentFamilyState', - defaultValue: ({ familyKey }) => { - switch (familyKey) { - case ViewType.Kanban: - return false; - case ViewType.Table: - return false; - default: - return false; - } - }, - componentInstanceContext: ViewComponentInstanceContext, - }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState.ts new file mode 100644 index 0000000000..d5ca3c1f62 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState.ts @@ -0,0 +1,9 @@ +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; +import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; + +export const recordIndexShouldHideEmptyRecordGroupsComponentState = + createComponentState({ + key: 'recordIndexShouldHideEmptyRecordGroupsComponentState', + defaultValue: false, + componentInstanceContext: ViewComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyNoRecordGroupDragDropContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyNoRecordGroupDragDropContextProvider.tsx index 279aa9570e..ee24d5228f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyNoRecordGroupDragDropContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyNoRecordGroupDragDropContextProvider.tsx @@ -10,9 +10,9 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { useEndRecordDrag } from '@/object-record/record-drag/shared/hooks/useEndRecordDrag'; -import { useStartRecordDrag } from '@/object-record/record-drag/shared/hooks/useStartRecordDrag'; -import { useRecordTableWithoutGroupDragOperations } from '@/object-record/record-drag/table/hooks/useRecordTableWithoutGroupDragOperations'; +import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag'; +import { useProcessTableWithoutGroupRecordDrop } from '@/object-record/record-drag/hooks/useProcessTableWithoutGroupRecordDrop'; +import { useStartRecordDrag } from '@/object-record/record-drag/hooks/useStartRecordDrag'; import { selectedRowIdsComponentSelector } from '../../states/selectors/selectedRowIdsComponentSelector'; export const RecordTableBodyNoRecordGroupDragDropContextProvider = ({ @@ -27,10 +27,10 @@ export const RecordTableBodyNoRecordGroupDragDropContextProvider = ({ recordTableId, ); - const { startDrag } = useStartRecordDrag('table', recordTableId); - const { endDrag } = useEndRecordDrag('table', recordTableId); - const { processDragOperationWithoutGroup } = - useRecordTableWithoutGroupDragOperations(); + const { startRecordDrag } = useStartRecordDrag(); + const { endRecordDrag } = useEndRecordDrag(); + const { processTableWithoutGroupRecordDrop } = + useProcessTableWithoutGroupRecordDrop(); const handleDragStart = useRecoilCallback( ({ snapshot }) => @@ -40,17 +40,17 @@ export const RecordTableBodyNoRecordGroupDragDropContextProvider = ({ selectedRowIdsSelector, ); - startDrag(start, currentSelectedRecordIds); + startRecordDrag(start, currentSelectedRecordIds); }, - [selectedRowIdsSelector, startDrag], + [selectedRowIdsSelector, startRecordDrag], ); const handleDragEnd = useRecoilCallback( () => (result: DropResult) => { - processDragOperationWithoutGroup(result); - endDrag(); + processTableWithoutGroupRecordDrop(result); + endRecordDrag(); }, - [endDrag, processDragOperationWithoutGroup], + [endRecordDrag, processTableWithoutGroupRecordDrop], ); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider.tsx index 9c460d1ae2..9ce344bfee 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider.tsx @@ -1,3 +1,10 @@ +import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag'; +import { useProcessTableWithGroupRecordDrop } from '@/object-record/record-drag/hooks/useProcessTableWithGroupRecordDrop'; +import { useStartRecordDrag } from '@/object-record/record-drag/hooks/useStartRecordDrag'; +import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector'; +import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { DragDropContext, type DragStart, @@ -6,14 +13,6 @@ import { import { type ReactNode } from 'react'; import { useRecoilCallback } from 'recoil'; -import { useEndRecordDrag } from '@/object-record/record-drag/shared/hooks/useEndRecordDrag'; -import { useStartRecordDrag } from '@/object-record/record-drag/shared/hooks/useStartRecordDrag'; -import { useRecordTableGroupDragOperations } from '@/object-record/record-drag/table/hooks/useRecordTableGroupDragOperations'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; - export const RecordTableBodyRecordGroupDragDropContextProvider = ({ children, }: { @@ -26,9 +25,11 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({ recordTableId, ); - const { startDrag } = useStartRecordDrag('table', recordTableId); - const { endDrag } = useEndRecordDrag('table', recordTableId); - const { processDragOperation } = useRecordTableGroupDragOperations(); + const { startRecordDrag } = useStartRecordDrag(); + const { endRecordDrag } = useEndRecordDrag(); + + const { processTableWithGroupRecordDrop } = + useProcessTableWithGroupRecordDrop(); const handleDragStart = useRecoilCallback( ({ snapshot }) => @@ -38,17 +39,18 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({ selectedRowIdsSelector, ); - startDrag(start, currentSelectedRecordIds); + startRecordDrag(start, currentSelectedRecordIds); }, - [selectedRowIdsSelector, startDrag], + [selectedRowIdsSelector, startRecordDrag], ); const handleDragEnd = useRecoilCallback( () => (result: DropResult) => { - processDragOperation(result); - endDrag(); + processTableWithGroupRecordDrop(result); + + endRecordDrag(); }, - [endDrag, processDragOperation], + [endRecordDrag, processTableWithGroupRecordDrop], ); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx index b7d64e39a1..2eff949f82 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyVirtualizedDraggableClone.tsx @@ -1,4 +1,3 @@ -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { HorizontalScrollBoxShadowCSS } from '@/object-record/record-table/components/RecordTableStyleWrapper'; import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth'; @@ -23,6 +22,7 @@ import { RecordTableFieldsCells } from '@/object-record/record-table/record-tabl import { RecordTableRowMultiDragPreview } from '@/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview'; import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; +import { useIsTableRowSecondaryDragged } from '@/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged'; import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName'; import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName'; import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState'; @@ -98,16 +98,16 @@ const StyledRowDraggableCloneCSSBridge = styled.div<{ } for (let i = 0; i < visibleRecordFields.length; i++) { - returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { - width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); - min-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); - max-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { + width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + min-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + max-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); } \n`; const isLabelIdentifierColumn = i === 0; if (isLabelIdentifierColumn) { - returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { + returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { @media (max-width: ${MOBILE_VIEWPORT}px) { width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; max-width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; @@ -145,10 +145,6 @@ export const RecordTableBodyVirtualizedDraggableClone = ({ }) => { const realIndex = rubric.source.index; - const { recordTableId } = useRecordTableContextOrThrow(); - - const multiDragState = useRecordDragState('table', recordTableId); - const theme = useTheme(); const recordId = useRecoilComponentFamilyValue( @@ -160,15 +156,12 @@ export const RecordTableBodyVirtualizedDraggableClone = ({ const { visibleRecordFields } = useRecordTableContextOrThrow(); + const { isSecondaryDragged } = useIsTableRowSecondaryDragged(recordId); + if (!isDefined(recordId)) { return null; } - const isSecondaryDragged = - multiDragState?.isDragging && - multiDragState.originalSelection.includes(recordId) && - recordId !== multiDragState.primaryDraggedRecordId; - return ( - + diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTr.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTr.tsx index 5e77634ec9..dde7f2eb30 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTr.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTr.tsx @@ -2,11 +2,10 @@ import { useTheme } from '@emotion/react'; import { Draggable } from '@hello-pangea/dnd'; import { type ReactNode } from 'react'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext'; import { RecordTableRowMultiDragPreview } from '@/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview'; import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; +import { useIsTableRowSecondaryDragged } from '@/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged'; type RecordTableDraggableTrProps = { className?: string; @@ -28,13 +27,8 @@ export const RecordTableDraggableTr = ({ children, }: RecordTableDraggableTrProps) => { const theme = useTheme(); - const { recordTableId } = useRecordTableContextOrThrow(); - const multiDragState = useRecordDragState('table', recordTableId); - const isSecondaryDragged = - multiDragState?.isDragging && - multiDragState.originalSelection.includes(recordId) && - recordId !== multiDragState.primaryDraggedRecordId; + const { isSecondaryDragged } = useIsTableRowSecondaryDragged(recordId); return ( {children} - + diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTrFirstRowOfGroup.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTrFirstRowOfGroup.tsx index 14ebfe1054..f103d38901 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTrFirstRowOfGroup.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDraggableTrFirstRowOfGroup.tsx @@ -2,12 +2,11 @@ import { useTheme } from '@emotion/react'; import { Draggable } from '@hello-pangea/dnd'; import { type ReactNode } from 'react'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext'; import { RecordTableRowMultiDragPreview } from '@/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview'; import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; +import { useIsTableRowSecondaryDragged } from '@/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged'; import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -31,13 +30,8 @@ export const RecordTableDraggableTrFirstRowOfGroup = ({ children, }: RecordTableDraggableTrFirstRowOfGroupProps) => { const theme = useTheme(); - const { recordTableId } = useRecordTableContextOrThrow(); - const multiDragState = useRecordDragState('table', recordTableId); - const isSecondaryDragged = - multiDragState?.isDragging && - multiDragState.originalSelection.includes(recordId) && - recordId !== multiDragState.primaryDraggedRecordId; + const { isSecondaryDragged } = useIsTableRowSecondaryDragged(recordId); const isScrolledVertically = useRecoilComponentValue( isRecordTableScrolledVerticallyComponentState, @@ -83,9 +77,7 @@ export const RecordTableDraggableTrFirstRowOfGroup = ({ }} > {children} - + diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragCounterChip.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragCounterChip.tsx new file mode 100644 index 0000000000..544964e854 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragCounterChip.tsx @@ -0,0 +1,27 @@ +import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import styled from '@emotion/styled'; +import { NotificationCounter } from 'twenty-ui/navigation'; + +const StyledNotificationCounter = styled(NotificationCounter)` + position: absolute; + top: -7px; + left: -7px; + z-index: 1000; +`; + +export const RecordTableRowMultiDragCounterChip = () => { + const originalDragSelection = useRecoilComponentValue( + originalDragSelectionComponentState, + ); + + const selectedCount = originalDragSelection.length ?? 0; + + const shouldShow = selectedCount > 1; + + if (!shouldShow) { + return null; + } + + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview.tsx index 6f8779df0b..db6e097737 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowMultiDragPreview.tsx @@ -1,37 +1,19 @@ -import styled from '@emotion/styled'; -import { NotificationCounter } from 'twenty-ui/navigation'; - -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { isRecordIdPrimaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdPrimaryDragMultipleComponentFamilyState'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState'; +import { RecordTableRowMultiDragCounterChip } from '@/object-record/record-table/record-table-row/components/RecordTableRowMultiDragCounterChip'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; -const StyledNotificationCounter = styled(NotificationCounter)` - position: absolute; - top: -7px; - left: -7px; - z-index: 1000; -`; - -type RecordTableRowMultiDragPreviewProps = { - isDragging: boolean; -}; - -export const RecordTableRowMultiDragPreview = ({ - isDragging, -}: RecordTableRowMultiDragPreviewProps) => { +export const RecordTableRowMultiDragPreview = () => { const { recordId } = useRecordTableRowContextOrThrow(); - const { recordTableId } = useRecordTableContextOrThrow(); - const multiDragState = useRecordDragState('table', recordTableId); - const isCurrentRowSelected = - multiDragState?.originalSelection.includes(recordId) || false; - const selectedCount = multiDragState?.originalSelection.length ?? 0; + const isRecordIdPrimaryDragMultiple = useRecoilComponentFamilyValue( + isRecordIdPrimaryDragMultipleComponentFamilyState, + { recordId }, + ); - const shouldShow = isDragging && isCurrentRowSelected && selectedCount > 1; - - if (!shouldShow) { + if (!isRecordIdPrimaryDragMultiple) { return null; } - return ; + return ; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged.ts new file mode 100644 index 0000000000..96f1039942 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged.ts @@ -0,0 +1,13 @@ +import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; + +export const useIsTableRowSecondaryDragged = (recordId: string | null) => { + const isSecondaryDragged = useRecoilComponentFamilyValue( + isRecordIdSecondaryDragMultipleComponentFamilyState, + { recordId: recordId ?? '' }, + ); + + return { + isSecondaryDragged, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/drag-and-drop/components/DragAndDropReRenderBreaker.tsx b/packages/twenty-front/src/modules/ui/drag-and-drop/components/DragAndDropReRenderBreaker.tsx new file mode 100644 index 0000000000..c6b7ff1975 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/drag-and-drop/components/DragAndDropReRenderBreaker.tsx @@ -0,0 +1,20 @@ +import { memo } from 'react'; + +/** + * THIS IS REQUIRED BY THE DND LYBRARY ONLY + * DO NOT USE THIS PATTERN ELSEWHERE IN THE APPLICATION + * + * @deprecated This should be replaced by a more recent dnd library that does not force the re-render of its dragged components, which is a bad practice in React. + */ +export const DragAndDropLibraryLegacyReRenderBreaker = memo( + ({ + children, + }: React.PropsWithChildren<{ + memoizationId: string; + }>) => { + return <>{children}; + }, + (prev, next) => { + return prev.memoizationId === next.memoizationId; + }, +);