Refactor field definition and column definition manipulation with record field (#13992)

This PR introduces the usage of record fields in the manipulation of
table columns and board fields.

Since all of the actual system relies on states like tableColumns,
recordIndexFieldDefinitions and the likes, it was required to implement
temporary utils that modifies those states in parallel of the new
generic currentRecordFields, to keep a working product.

With this PR though, currentRecordFields becomes the single source of
truth that gets saved to DB, and the remaining work is just to make the
switch with this new state on all components that are plugged to
tableColumns and the like.

This will be done in another PR.
This commit is contained in:
Lucas Bordeau
2025-08-20 12:03:12 +02:00
committed by GitHub
parent 3f6866da20
commit 00478152e0
31 changed files with 867 additions and 459 deletions
@@ -0,0 +1,65 @@
import {
computeNewPositionOfRecordWithPosition,
type RecordWithPosition,
} from '@/object-record/utils/computeNewPositionOfRecordWithPosition';
const mockRecordsWithPosition: RecordWithPosition[] = [
{
id: 'A',
position: 0,
},
{
id: 'B',
position: 1,
},
{
id: 'C',
position: 2,
},
{
id: 'D',
position: 3,
},
];
describe('computeNewPositionOfRecordWithPosition', () => {
it('should compute first position', () => {
const newPosition = computeNewPositionOfRecordWithPosition({
arrayOfRecordsWithPosition: mockRecordsWithPosition,
idOfItemToMove: 'B',
idOfTargetItem: 'A',
});
expect(newPosition).toEqual(-1);
});
it('should compute last position', () => {
const newPosition = computeNewPositionOfRecordWithPosition({
arrayOfRecordsWithPosition: mockRecordsWithPosition,
idOfItemToMove: 'B',
idOfTargetItem: 'D',
});
expect(newPosition).toEqual(4);
});
it('should compute intermediary position after target item', () => {
const newPosition = computeNewPositionOfRecordWithPosition({
arrayOfRecordsWithPosition: mockRecordsWithPosition,
idOfItemToMove: 'A',
idOfTargetItem: 'B',
});
expect(newPosition).toEqual(1.5);
});
it('should compute intermediary position before target item', () => {
const newPosition = computeNewPositionOfRecordWithPosition({
arrayOfRecordsWithPosition: mockRecordsWithPosition,
idOfItemToMove: 'A',
idOfTargetItem: 'C',
});
expect(newPosition).toEqual(2.5);
});
});
@@ -0,0 +1,86 @@
import { isDefined } from 'twenty-shared/utils';
import { sortByProperty } from '~/utils/array/sortByProperty';
export type RecordWithPosition = {
id: string;
position: number;
};
export const computeNewPositionOfRecordWithPosition = ({
arrayOfRecordsWithPosition,
idOfItemToMove,
idOfTargetItem,
}: {
arrayOfRecordsWithPosition: RecordWithPosition[];
idOfItemToMove: string;
idOfTargetItem: string;
}) => {
const itemToMove = arrayOfRecordsWithPosition.find(
(recordToFind) => recordToFind.id === idOfItemToMove,
);
const targetItem = arrayOfRecordsWithPosition.find(
(recordToFind) => recordToFind.id === idOfTargetItem,
);
if (!isDefined(itemToMove)) {
throw new Error(`Cannot find item to move for id : ${idOfItemToMove}`);
}
if (!isDefined(targetItem)) {
throw new Error(`Cannot find item to move for id : ${idOfTargetItem}`);
}
if (itemToMove.id === targetItem.id) {
return itemToMove.position;
}
const targetPosition = targetItem.position;
const sortedRecordsByAscendingPosition = arrayOfRecordsWithPosition.toSorted(
sortByProperty('position'),
);
const indexOfItemToMove = sortedRecordsByAscendingPosition.findIndex(
(recordToFind) => recordToFind.id === idOfItemToMove,
);
const indexOfTargetItem = sortedRecordsByAscendingPosition.findIndex(
(recordToFind) => recordToFind.id === idOfTargetItem,
);
const lastIndex = sortedRecordsByAscendingPosition.length - 1;
const shouldGoToFirstPosition =
indexOfItemToMove > 0 && indexOfTargetItem === 0;
const shouldGoToLastPosition =
indexOfItemToMove < lastIndex && indexOfTargetItem === lastIndex;
if (shouldGoToFirstPosition) {
return targetPosition - 1;
} else if (shouldGoToLastPosition) {
return targetPosition + 1;
} else {
const shouldGoAfterTargetItem = indexOfItemToMove < indexOfTargetItem;
if (shouldGoAfterTargetItem) {
const itemAfterTargetItem =
sortedRecordsByAscendingPosition[indexOfTargetItem + 1];
const intermediaryPosition =
targetItem.position +
(itemAfterTargetItem.position - targetItem.position) / 2;
return intermediaryPosition;
} else {
const itemBeforeTargetItem =
sortedRecordsByAscendingPosition[indexOfTargetItem - 1];
const intermediaryPosition =
targetItem.position -
(targetItem.position - itemBeforeTargetItem.position) / 2;
return intermediaryPosition;
}
}
};