Fix sorts on table and board (#10311)

This PR fixes sorts on table and board.

The sorts were not taken into account due to the recent view refactor
that had the side effect of removing the synchronization between
recordSortsState and changing the state.

The fix was to use combined view sorts instead, which are currently the
source of truth for sorts.

Also added a confirmation modal for manual sorting on board.
This commit is contained in:
Lucas Bordeau
2025-02-19 10:57:13 +01:00
committed by GitHub
parent 645065abba
commit 33178fa8b2
8 changed files with 65 additions and 24 deletions

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DragDropContext, 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 import { DragDropContext, 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
import { useContext, useRef } from 'react'; import { useContext, useRef } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { useActionMenu } from '@/action-menu/hooks/useActionMenu'; import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
@ -21,6 +21,7 @@ import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/re
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -31,6 +32,7 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useScrollRestoration } from '~/hooks/useScrollRestoration'; import { useScrollRestoration } from '~/hooks/useScrollRestoration';
@ -142,11 +144,26 @@ export const RecordBoard = () => {
ActionBarHotkeyScope.ActionBar, ActionBarHotkeyScope.ActionBar,
); );
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(recordBoardId);
const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState,
);
const handleDragEnd: OnDragEndResponder = useRecoilCallback( const handleDragEnd: OnDragEndResponder = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(result) => { (result) => {
if (!result.destination) return; if (!result.destination) return;
const viewSorts =
currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
if (viewSorts.length > 0) {
setIsRemoveSortingModalOpen(true);
return;
}
const draggedRecordId = result.draggableId; const draggedRecordId = result.draggableId;
const sourceRecordGroupId = result.source.droppableId; const sourceRecordGroupId = result.source.droppableId;
const destinationRecordGroupId = result.destination.droppableId; const destinationRecordGroupId = result.destination.droppableId;
@ -201,6 +218,8 @@ export const RecordBoard = () => {
recordIndexRecordIdsByGroupFamilyState, recordIndexRecordIdsByGroupFamilyState,
selectFieldMetadataItem, selectFieldMetadataItem,
updateOneRecord, updateOneRecord,
setIsRemoveSortingModalOpen,
currentViewWithCombinedFiltersAndSorts,
], ],
); );

View File

@ -6,6 +6,7 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard'; import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
type RecordIndexBoardContainerProps = { type RecordIndexBoardContainerProps = {
@ -50,6 +51,7 @@ export const RecordIndexBoardContainer = ({
}} }}
> >
<RecordBoard /> <RecordBoard />
<RecordIndexRemoveSortingModal recordIndexId={recordBoardId} />
</RecordBoardContext.Provider> </RecordBoardContext.Provider>
); );
}; };

View File

@ -6,22 +6,21 @@ import { useDeleteCombinedViewSorts } from '@/views/hooks/useDeleteCombinedViewS
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
export const RecordIndexRemoveSortingModal = ({ export const RecordIndexRemoveSortingModal = ({
recordTableId, recordIndexId,
}: { }: {
recordTableId: string; recordIndexId: string;
}) => { }) => {
const { currentViewWithCombinedFiltersAndSorts } = const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(recordTableId); useGetCurrentView(recordIndexId);
const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || []; const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
const fieldMetadataIds = viewSorts.map( const fieldMetadataIds = viewSorts.map(
(viewSort) => viewSort.fieldMetadataId, (viewSort) => viewSort.fieldMetadataId,
); );
const isRemoveSortingModalOpen = useRecoilState( const [isRemoveSortingModalOpen, setIsRemoveSortingModalOpen] =
isRemoveSortingModalOpenState, useRecoilState(isRemoveSortingModalOpenState);
);
const { deleteCombinedViewSort } = useDeleteCombinedViewSorts(recordTableId); const { deleteCombinedViewSort } = useDeleteCombinedViewSorts(recordIndexId);
const handleRemoveClick = () => { const handleRemoveClick = () => {
fieldMetadataIds.forEach((id) => { fieldMetadataIds.forEach((id) => {
@ -32,8 +31,8 @@ export const RecordIndexRemoveSortingModal = ({
return ( return (
<> <>
<ConfirmationModal <ConfirmationModal
isOpen={isRemoveSortingModalOpen[0]} isOpen={isRemoveSortingModalOpen}
setIsOpen={isRemoveSortingModalOpen[1]} setIsOpen={setIsRemoveSortingModalOpen}
title={'Remove sorting?'} title={'Remove sorting?'}
subtitle={<>This is required to enable manual row reordering.</>} subtitle={<>This is required to enable manual row reordering.</>}
onConfirmClick={() => handleRemoveClick()} onConfirmClick={() => handleRemoveClick()}

View File

@ -34,7 +34,7 @@ export const RecordIndexTableContainer = ({
viewBarId={viewBarId} viewBarId={viewBarId}
updateRecordMutation={updateEntity} updateRecordMutation={updateEntity}
/> />
<RecordIndexRemoveSortingModal recordTableId={recordTableId} /> <RecordIndexRemoveSortingModal recordIndexId={recordTableId} />
</> </>
); );
}; };

View File

@ -5,9 +5,11 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition'; import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
export const useFindManyRecordIndexTableParams = ( export const useFindManyRecordIndexTableParams = (
objectNameSingular: string, objectNameSingular: string,
@ -28,11 +30,18 @@ export const useFindManyRecordIndexTableParams = (
recordTableId, recordTableId,
); );
const tableSorts = useRecoilComponentValueV2( const { currentViewWithCombinedFiltersAndSorts } =
tableSortsComponentState, useGetCurrentView(recordTableId);
const availableSortDefinitions = useRecoilComponentValueV2(
availableSortDefinitionsComponentState,
recordTableId, recordTableId,
); );
const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts ?? [];
const sorts = mapViewSortsToSorts(viewSorts, availableSortDefinitions);
const currentRecordFilters = useRecoilComponentValueV2( const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState, currentRecordFiltersComponentState,
); );
@ -46,7 +55,7 @@ export const useFindManyRecordIndexTableParams = (
tableViewFilterGroups, tableViewFilterGroups,
); );
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts); const orderBy = turnSortsIntoOrderBy(objectMetadataItem, sorts);
return { return {
objectNameSingular, objectNameSingular,

View File

@ -10,10 +10,12 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields'; import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
type UseLoadRecordIndexBoardProps = { type UseLoadRecordIndexBoardProps = {
@ -45,7 +47,16 @@ export const useLoadRecordIndexBoardColumn = ({
const currentRecordFilters = useRecoilComponentValueV2( const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState, currentRecordFiltersComponentState,
); );
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const viewsorts = currentViewWithCombinedFiltersAndSorts?.viewSorts ?? [];
const sortDefinitions = useRecoilComponentValueV2(
availableSortDefinitionsComponentState,
);
const sorts = mapViewSortsToSorts(viewsorts, sortDefinitions);
const { filterValueDependencies } = useFilterValueDependencies(); const { filterValueDependencies } = useFilterValueDependencies();
@ -55,7 +66,8 @@ export const useLoadRecordIndexBoardColumn = ({
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
recordIndexViewFilterGroups, recordIndexViewFilterGroups,
); );
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, recordIndexSorts);
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, sorts);
const recordGqlFields = useRecordBoardRecordGqlFields({ const recordGqlFields = useRecordBoardRecordGqlFields({
objectMetadataItem, objectMetadataItem,

View File

@ -33,7 +33,7 @@ export const RecordTableBodyDragDropContextProvider = ({
const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || []; const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
const setIsRemoveSortingModalOpenState = useSetRecoilState( const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState, isRemoveSortingModalOpenState,
); );
@ -41,7 +41,7 @@ export const RecordTableBodyDragDropContextProvider = ({
({ snapshot }) => ({ snapshot }) =>
(result: DropResult) => { (result: DropResult) => {
if (viewSorts.length > 0) { if (viewSorts.length > 0) {
setIsRemoveSortingModalOpenState(true); setIsRemoveSortingModalOpen(true);
return; return;
} }
@ -101,7 +101,7 @@ export const RecordTableBodyDragDropContextProvider = ({
}, },
[ [
recordIndexAllRecordIdsSelector, recordIndexAllRecordIdsSelector,
setIsRemoveSortingModalOpenState, setIsRemoveSortingModalOpen,
updateOneRow, updateOneRow,
viewSorts.length, viewSorts.length,
], ],

View File

@ -26,7 +26,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
objectNameSingular, objectNameSingular,
}); });
const setIsRemoveSortingModalOpenState = useSetRecoilState( const setIsRemoveSortingModalOpen = useSetRecoilState(
isRemoveSortingModalOpenState, isRemoveSortingModalOpenState,
); );
@ -69,7 +69,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
} }
if (indexSorts.length > 0) { if (indexSorts.length > 0) {
setIsRemoveSortingModalOpenState(true); setIsRemoveSortingModalOpen(true);
return; return;
} }
@ -129,7 +129,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
objectMetadataItem.fields, objectMetadataItem.fields,
recordIdsByGroupFamilyState, recordIdsByGroupFamilyState,
updateOneRow, updateOneRow,
setIsRemoveSortingModalOpenState, setIsRemoveSortingModalOpen,
], ],
); );