From 21dbd6441a4b2b1f7c2209fc59633905b9eaa8e5 Mon Sep 17 00:00:00 2001 From: kikoleitao <92337535+kikoleitao@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:22:51 +0100 Subject: [PATCH] feat: implement row re-ordering via drag and drop (#4846) (#5580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Context Currently, the Twenty platform incorporates "positions" for rows on the backend, which are functional within the Kanban view. However, this advantageous feature has yet to be leveraged within list views. # Feature Proposal ## Implement Row-Reordering via Drag-and-Drop on Frontend (#4846) - This PR addresses the implementation of row reordering via Drag-and-Drop on frontend. The objective is to enrich the list view functionality by introducing a grip that dynamically appears upon hovering over the left space preceding the checkbox container. This grip empowers users to effortlessly reposition rows within the list. #### Proposal Highlights: - **Enhanced User Interaction**: Introduce a draggable grip to facilitate intuitive row reordering, enhancing user experience and productivity. - **Preservation of Design Aesthetics**: By excluding the grip from the first row and maintaining the left gap, we uphold design integrity while providing enhanced functionality. - **Consistency with Existing Features**: Align with existing drag-and-drop functionalities within the platform, such as Favorites re-ordering or Fields re-ordering in table options, ensuring a seamless user experience. ## Implementation Strategy ### Grip Implementation: - Add an extra column to the table (header + body) to accommodate the grip cell, which displays the IconListViewGrip when its container is hovered over. - Ensure the preceding left-space is maintained by setting the corresponding width for this column and removing padding from the table container (while maintaining padding in other page elements and the Kanban view for coherence). ### Row Drag and Drop: - Implement row drag-and-drop functionality using draggableList and draggableItem, based on the existing logic in the KanbanView for row repositioning. - Create a draggableTableBody and apply it to the current RecordTableBody (including modal open triggering - if dragging while sorting exists). - Apply the draggableItem logic to RecordTableRow. ### Sorting Modal Implementation: - Reuse the ConfirmationModel for the removeSortingModal. - Create a new state to address the modal. - Implement sorting removal logic in the corresponding modal file. ## Outcome - The left-side margin is preserved. - The grip appears upon hovering. - Dragging a row gives it and maintains an aesthetic appearance. - Dropping a row updates its position, and the table gets a new configuration. - If sorting is present, dropping a row activates a modal. Clicking on the "Remove Sorting" button will deactivate any sorting (clicking on "Cancel" will close the modal), and the table will revert to its default configuration by position, allowing manual row reordering. Row repositioning will not occur if sorting is not removed. - The record table maintains its overall consistency. - There are no conflicts with DragSelect functionality. https://github.com/twentyhq/twenty/assets/92337535/73de96cc-4aac-41a9-b4ec-2b8d1c928d04 --------- Co-authored-by: Vasco Paisana Co-authored-by: Lucas Bordeau Co-authored-by: FĂ©lix Malfait --- .../components/RecordIndexContainer.tsx | 83 +++++++------ .../components/RecordIndexTableContainer.tsx | 2 + .../record-table/components/GripCell.tsx | 29 +++++ .../record-table/components/RecordTable.tsx | 80 +++++++----- .../components/RecordTableBody.tsx | 31 +++-- .../components/RecordTableHeader.tsx | 1 + .../components/RecordTableRow.tsx | 117 ++++++++++++++---- .../components/RemoveSortingModal.tsx | 44 +++++++ .../hooks/useComputeNewRowPosition.ts | 90 ++++++++++++++ .../states/isRemoveSortingModalOpenState.ts | 6 + .../ui/input/components/IconListViewGrip.tsx | 11 ++ .../ui/input/components/list-view-grip.svg | 5 + .../components/DraggableTableBody.tsx | 87 +++++++++++++ 13 files changed, 481 insertions(+), 105 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/useComputeNewRowPosition.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/IconListViewGrip.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/list-view-grip.svg create mode 100644 packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index e74b9e9be4..28b6aa9a6a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -34,6 +34,9 @@ const StyledContainer = styled.div` height: 100%; width: 100%; overflow: auto; +`; + +const StyledContainerWithPadding = styled.div` padding-left: ${({ theme }) => theme.table.horizontalCellPadding}; `; @@ -109,45 +112,48 @@ export const RecordIndexContainer = ({ - - } - onCurrentViewChange={(view) => { - if (!view) { - return; + + } + onCurrentViewChange={(view) => { + if (!view) { + return; + } - onViewFieldsChange(view.viewFields); - setTableFilters( - mapViewFiltersToFilters(view.viewFilters, filterDefinitions), - ); - setRecordIndexFilters( - mapViewFiltersToFilters(view.viewFilters, filterDefinitions), - ); - setTableSorts( - mapViewSortsToSorts(view.viewSorts, sortDefinitions), - ); - setRecordIndexSorts( - mapViewSortsToSorts(view.viewSorts, sortDefinitions), - ); - setRecordIndexViewType(view.type); - setRecordIndexViewKanbanFieldMetadataIdState( - view.kanbanFieldMetadataId, - ); - setRecordIndexIsCompactModeActive(view.isCompact); - }} - /> - + onViewFieldsChange(view.viewFields); + setTableFilters( + mapViewFiltersToFilters(view.viewFilters, filterDefinitions), + ); + setRecordIndexFilters( + mapViewFiltersToFilters(view.viewFilters, filterDefinitions), + ); + setTableSorts( + mapViewSortsToSorts(view.viewSorts, sortDefinitions), + ); + setRecordIndexSorts( + mapViewSortsToSorts(view.viewSorts, sortDefinitions), + ); + setRecordIndexViewType(view.type); + setRecordIndexViewKanbanFieldMetadataIdState( + view.kanbanFieldMetadataId, + ); + setRecordIndexIsCompactModeActive(view.isCompact); + }} + /> + + + {recordIndexViewType === ViewType.Table && ( <> )} + {recordIndexViewType === ViewType.Kanban && ( - <> + - + )} diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx index 51a5caf58f..31be052ff4 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx @@ -2,6 +2,7 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext'; import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; +import { RemoveSortingModal } from '@/object-record/record-table/components/RemoveSortingModal'; import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu'; type RecordIndexTableContainerProps = { @@ -38,6 +39,7 @@ export const RecordIndexTableContainer = ({ createRecord={createRecord} /> + ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx new file mode 100644 index 0000000000..b9900026db --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; + +import { IconListViewGrip } from '@/ui/input/components/IconListViewGrip'; + +const StyledContainer = styled.div` + cursor: grab; + width: 16px; + height: 32px; + z-index: 200; + display: flex; + &:hover .icon { + opacity: 1; + } +`; + +const StyledIconWrapper = styled.div<{ isDragging: boolean }>` + opacity: ${({ isDragging }) => (isDragging ? 1 : 0)}; + transition: opacity 0.1s; +`; + +export const GripCell = ({ isDragging }: { isDragging: boolean }) => { + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 92280dfd1f..2afb86e987 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -43,15 +43,14 @@ const StyledTable = styled.table<{ border-right-color: transparent; } :first-of-type { - border-left-color: transparent; - border-right-color: transparent; + border-top-color: transparent; + border-bottom-color: transparent; } } td { border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; color: ${({ theme }) => theme.font.color.primary}; - padding: 0; border-right: 1px solid ${({ theme }) => theme.border.color.light}; text-align: left; @@ -60,8 +59,8 @@ const StyledTable = styled.table<{ border-right-color: transparent; } :first-of-type { - border-left-color: transparent; - border-right-color: transparent; + border-top-color: transparent; + border-bottom-color: transparent; } } @@ -70,35 +69,58 @@ const StyledTable = styled.table<{ border-right: 1px solid ${({ theme }) => theme.border.color.light}; } - thead th:nth-of-type(-n + 2), - tbody td:nth-of-type(-n + 2) { + thead th { position: sticky; - z-index: 2; - border-right: none; + top: 0; + z-index: 9; + } + + thead th:nth-of-type(1), + thead th:nth-of-type(2), + thead th:nth-of-type(3) { + z-index: 12; + background-color: ${({ theme }) => theme.background.primary}; + } + + thead th:nth-of-type(1) { + width: 9px; + left: 0; + border-right-color: ${({ theme }) => theme.background.primary}; + } + + thead th:nth-of-type(2) { + left: 9px; + border-right-color: ${({ theme }) => theme.background.primary}; + } + + thead th:nth-of-type(3) { + left: 39px; + } + + tbody td:nth-of-type(1), + tbody td:nth-of-type(2), + tbody td:nth-of-type(3) { + position: sticky; + z-index: 1; } tbody td:nth-of-type(1) { left: 0; + z-index: 7; } - // Label identifier column - thead th:nth-of-type(1), - thead th:nth-of-type(2) { - left: 0; - top: 0; + tbody td:nth-of-type(2) { + left: 9px; + z-index: 5; + } + + tbody td:nth-of-type(3) { + left: 39px; z-index: 6; } - thead th:nth-of-type(n + 3) { - top: 0; - z-index: 5; - position: sticky; - } - - thead th:nth-of-type(2), - tbody td:nth-of-type(2) { - left: calc(${({ theme }) => theme.table.checkboxColumnWidth} - 2px); - + thead th:nth-of-type(3), + tbody td:nth-of-type(3) { ${({ freezeFirstColumns }) => freezeFirstColumns && css` @@ -125,11 +147,6 @@ const StyledTable = styled.table<{ `} } } - - thead th:nth-of-type(3), - tbody td:nth-of-type(3) { - border-left: 1px solid ${({ theme }) => theme.border.color.light}; - } `; type RecordTableProps = { @@ -229,7 +246,10 @@ export const RecordTable = ({ - + )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx index 3947067f48..640bfffee5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx @@ -1,16 +1,18 @@ import { useRecoilValue } from 'recoil'; import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader'; -import { RecordTablePendingRow } from '@/object-record/record-table/components/RecordTablePendingRow'; import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { DraggableTableBody } from '@/ui/layout/draggable-list/components/DraggableTableBody'; type RecordTableBodyProps = { objectNameSingular: string; + recordTableId: string; }; export const RecordTableBody = ({ objectNameSingular, + recordTableId, }: RecordTableBodyProps) => { const { tableRowIdsState } = useRecordTableStates(); @@ -18,16 +20,23 @@ export const RecordTableBody = ({ return ( <> - - - {tableRowIds.map((recordId, rowIndex) => ( - - ))} - + + {tableRowIds.map((recordId, rowIndex) => { + return ( + + ); + })} + + } + /> ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx index edc262e725..9fe828dd97 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx @@ -70,6 +70,7 @@ export const RecordTableHeader = ({ return ( + theme.background.primary}; + position: relative; + user-select: none; +`; + +const StyledTr = styled.tr<{ isDragging: boolean }>` + border: 1px solid transparent; + transition: border-left-color 0.2s ease-in-out; + + td:nth-of-type(-n + 2) { + background-color: ${({ theme }) => theme.background.primary}; + border-right-color: ${({ theme }) => theme.background.primary}; + } + + ${({ isDragging }) => + isDragging && + ` + td:nth-of-type(1) { + background-color: transparent; + border-color: transparent; + } + + td:nth-of-type(2) { + background-color: transparent; + border-color: transparent; + } + + td:nth-of-type(3) { + background-color: transparent; + border-color: transparent; + } + + `} `; export const RecordTableRow = ({ @@ -45,6 +79,8 @@ export const RecordTableRow = ({ rootMargin: '1000px', }); + const theme = useTheme(); + return ( - - - - - {inView - ? visibleTableColumns.map((column, columnIndex) => ( - - - - )) - : visibleTableColumns.map((column) => ( - - ))} - - + + + {(draggableProvided, draggableSnapshot) => ( + { + elementRef(node); + draggableProvided.innerRef(node); + }} + // eslint-disable-next-line react/jsx-props-no-spreading + {...draggableProvided.draggableProps} + style={{ + ...draggableProvided.draggableProps.style, + background: draggableSnapshot.isDragging + ? theme.background.transparent.light + : 'none', + borderColor: draggableSnapshot.isDragging + ? `${theme.border.color.medium}` + : 'transparent', + }} + isDragging={draggableSnapshot.isDragging} + data-testid={`row-id-${recordId}`} + data-selectable-id={recordId} + > + + + + + {!draggableSnapshot.isDragging && } + + {inView || draggableSnapshot.isDragging + ? visibleTableColumns.map((column, columnIndex) => ( + + {draggableSnapshot.isDragging && columnIndex > 0 ? null : ( + + )} + + )) + : visibleTableColumns.map((column) => ( + + ))} + + + )} + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx new file mode 100644 index 0000000000..9bfb93b604 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx @@ -0,0 +1,44 @@ +import { useRecoilState } from 'recoil'; + +import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; + +export const RemoveSortingModal = ({ + recordTableId, +}: { + recordTableId: string; +}) => { + const { currentViewWithCombinedFiltersAndSorts } = + useGetCurrentView(recordTableId); + + const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || []; + const fieldMetadataIds = viewSorts.map( + (viewSort) => viewSort.fieldMetadataId, + ); + const isRemoveSortingModalOpen = useRecoilState( + isRemoveSortingModalOpenState, + ); + + const { removeCombinedViewSort } = useCombinedViewSorts(recordTableId); + + const handleRemoveClick = () => { + fieldMetadataIds.forEach((id) => { + removeCombinedViewSort(id); + }); + }; + + return ( + <> + This is required to enable manual row reordering.} + onConfirmClick={() => handleRemoveClick()} + deleteButtonText={'Remove Sorting'} + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useComputeNewRowPosition.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useComputeNewRowPosition.ts new file mode 100644 index 0000000000..0ef47ec49f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useComputeNewRowPosition.ts @@ -0,0 +1,90 @@ +import { DropResult } from '@hello-pangea/dnd'; +import { useRecoilCallback } from 'recoil'; + +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { isDefined } from '~/utils/isDefined'; + +export const useComputeNewRowPosition = () => { + return useRecoilCallback( + ({ snapshot }) => + (result: DropResult, tableRowIds: string[]) => { + if (!isDefined(result.destination)) { + return; + } + + const draggedRecordId = result.draggableId; + const destinationIndex = result.destination.index; + const sourceIndex = result.source.index; + + const recordBeforeId = tableRowIds[destinationIndex - 1]; + const recordDestinationId = tableRowIds[destinationIndex]; + const recordAfterDestinationId = tableRowIds[destinationIndex + 1]; + + const recordBefore = recordBeforeId + ? snapshot + .getLoadable(recordStoreFamilyState(recordBeforeId)) + .getValue() + : null; + const recordDestination = recordDestinationId + ? snapshot + .getLoadable(recordStoreFamilyState(recordDestinationId)) + .getValue() + : null; + const recordAfterDestination = recordAfterDestinationId + ? snapshot + .getLoadable(recordStoreFamilyState(recordAfterDestinationId)) + .getValue() + : null; + + const computeNewPosition = (destIndex: number, sourceIndex: number) => { + const moveToFirstPosition = destIndex === 0; + const moveToLastPosition = destIndex === tableRowIds.length - 1; + const moveAfterSource = destIndex > sourceIndex; + + const firstRecord = tableRowIds[0] + ? snapshot + .getLoadable(recordStoreFamilyState(tableRowIds[0])) + .getValue() + : null; + + const lastRecord = tableRowIds[tableRowIds.length - 1] + ? snapshot + .getLoadable( + recordStoreFamilyState(tableRowIds[tableRowIds.length - 1]), + ) + .getValue() + : null; + + const firstRecordPosition = firstRecord?.position ?? 0; + + if (moveToFirstPosition) { + if (firstRecordPosition <= 0) { + return firstRecordPosition - 1; + } else { + return firstRecordPosition / 2; + } + } else if (moveToLastPosition) { + return lastRecord?.position + 1; + } else if (moveAfterSource) { + const recordAfterDestinationPosition = + recordAfterDestination?.position ?? 0; + const recordDestinationPosition = recordDestination?.position ?? 0; + + return ( + (recordAfterDestinationPosition + recordDestinationPosition) / 2 + ); + } else { + return ( + recordDestination?.position - + (recordDestination?.position - recordBefore?.position) / 2 + ); + } + }; + + const newPosition = computeNewPosition(destinationIndex, sourceIndex); + + return { draggedRecordId, newPosition }; + }, + [], + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts new file mode 100644 index 0000000000..9f8627f2a4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isRemoveSortingModalOpenState = createState({ + key: 'isRemoveSortingModalOpenState', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/ui/input/components/IconListViewGrip.tsx b/packages/twenty-front/src/modules/ui/input/components/IconListViewGrip.tsx new file mode 100644 index 0000000000..8c6c425d4f --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/IconListViewGrip.tsx @@ -0,0 +1,11 @@ +import IconListViewGripRaw from '@/ui/input/components/list-view-grip.svg?react'; +import { IconComponentProps } from '@ui/display/icon/types/IconComponent'; + +type IconListViewGripProps = Pick; + +export const IconListViewGrip = (props: IconListViewGripProps) => { + const width = props.size ?? 8; + const height = props.size ?? 32; + + return ; +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/list-view-grip.svg b/packages/twenty-front/src/modules/ui/input/components/list-view-grip.svg new file mode 100644 index 0000000000..b8a82f167f --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/list-view-grip.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx b/packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx new file mode 100644 index 0000000000..c1513d828b --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react'; +import styled from '@emotion/styled'; +import { DragDropContext, Droppable, DropResult } from '@hello-pangea/dnd'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { v4 } from 'uuid'; + +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { RecordTablePendingRow } from '@/object-record/record-table/components/RecordTablePendingRow'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { useComputeNewRowPosition } from '@/object-record/record-table/hooks/useComputeNewRowPosition'; +import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; +import { isDefined } from '~/utils/isDefined'; + +type DraggableTableBodyProps = { + draggableItems: React.ReactNode; + objectNameSingular: string; + recordTableId: string; +}; + +const StyledTbody = styled.tbody` + overflow: hidden; +`; + +export const DraggableTableBody = ({ + objectNameSingular, + draggableItems, + recordTableId, +}: DraggableTableBodyProps) => { + const [v4Persistable] = useState(v4()); + + const { tableRowIdsState } = useRecordTableStates(); + + const tableRowIds = useRecoilValue(tableRowIdsState); + + const { updateOneRecord: updateOneRow } = useUpdateOneRecord({ + objectNameSingular, + }); + + const { currentViewWithCombinedFiltersAndSorts } = + useGetCurrentView(recordTableId); + + const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || []; + + const setIsRemoveSortingModalOpenState = useSetRecoilState( + isRemoveSortingModalOpenState, + ); + const computeNewRowPosition = useComputeNewRowPosition(); + + const handleDragEnd = (result: DropResult) => { + if (viewSorts.length > 0) { + setIsRemoveSortingModalOpenState(true); + return; + } + + const computeResult = computeNewRowPosition(result, tableRowIds); + + if (!isDefined(computeResult)) { + return; + } + + updateOneRow({ + idToUpdate: computeResult.draggedRecordId, + updateOneRecordInput: { + position: computeResult.newPosition, + }, + }); + }; + + return ( + + + {(provided) => ( + + + {draggableItems} + {provided.placeholder} + + )} + + + ); +};