diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json
index dd610f0d6..e16eecd72 100644
--- a/packages/twenty-front/package.json
+++ b/packages/twenty-front/package.json
@@ -40,6 +40,7 @@
"@tiptap/extension-text-style": "^2.8.0",
"@tiptap/react": "^2.8.0",
"@xyflow/react": "^12.0.4",
+ "buffer": "^6.0.3",
"transliteration": "^2.3.5"
}
}
diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts
index 057f93006..6bea94d6f 100644
--- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts
+++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts
@@ -1,5 +1,6 @@
import { ApolloCache, StoreObject } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards';
+import { Buffer } from 'buffer';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -75,10 +76,21 @@ export const triggerCreateRecordsOptimisticEffect = ({
rootQueryCachedObjectRecordConnection,
);
+ const rootQueryCachedPageInfo = readField<{
+ startCursor?: string;
+ endCursor?: string;
+ hasNextPage?: boolean;
+ hasPreviousPage?: boolean;
+ }>('pageInfo', rootQueryCachedObjectRecordConnection);
+
const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges
? [...rootQueryCachedRecordEdges]
: [];
+ const nextQueryCachedPageInfo = isDefined(rootQueryCachedPageInfo)
+ ? { ...rootQueryCachedPageInfo }
+ : {};
+
const hasAddedRecords = recordsToCreate
.map((recordToCreate) => {
if (isNonEmptyString(recordToCreate.id)) {
@@ -116,11 +128,61 @@ export const triggerCreateRecordsOptimisticEffect = ({
);
if (recordToCreateReference && !recordAlreadyInCache) {
- nextRootQueryCachedRecordEdges.unshift({
+ const cursor = Buffer.from(
+ JSON.stringify({
+ position: recordToCreate.position,
+ id: recordToCreate.id,
+ }),
+ 'utf-8',
+ ).toString('base64');
+
+ const edge = {
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
node: recordToCreateReference,
- cursor: '',
- });
+ cursor,
+ };
+
+ if (
+ !isDefined(recordToCreate.position) ||
+ recordToCreate.position === 'first'
+ ) {
+ nextRootQueryCachedRecordEdges.unshift(edge);
+ nextQueryCachedPageInfo.startCursor = cursor;
+ } else if (recordToCreate.position === 'last') {
+ nextRootQueryCachedRecordEdges.push(edge);
+ nextQueryCachedPageInfo.endCursor = cursor;
+ } else if (typeof recordToCreate.position === 'number') {
+ let index = Math.round(
+ nextRootQueryCachedRecordEdges.length *
+ recordToCreate.position,
+ );
+
+ if (recordToCreate.position < 0) {
+ index = Math.max(
+ 0,
+ nextRootQueryCachedRecordEdges.length +
+ Math.round(recordToCreate.position),
+ );
+ } else if (recordToCreate.position > 1) {
+ index = nextRootQueryCachedRecordEdges.length;
+ }
+
+ index = Math.max(
+ 0,
+ Math.min(index, nextRootQueryCachedRecordEdges.length),
+ );
+
+ nextRootQueryCachedRecordEdges.splice(index, 0, edge);
+
+ if (index === 0) {
+ nextQueryCachedPageInfo.startCursor = cursor;
+ } else if (
+ index ===
+ nextRootQueryCachedRecordEdges.length - 1
+ ) {
+ nextQueryCachedPageInfo.endCursor = cursor;
+ }
+ }
return true;
}
@@ -140,6 +202,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
totalCount: isDefined(rootQueryCachedRecordTotalCount)
? rootQueryCachedRecordTotalCount + 1
: undefined,
+ pageInfo: nextQueryCachedPageInfo,
};
},
},
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupRows.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupRows.tsx
index c944fd92a..8c897368b 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupRows.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupRows.tsx
@@ -56,8 +56,8 @@ export const RecordTableRecordGroupRows = () => {
/>
);
})}
-
+
>
);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts
index cabcf7e6e..47ae746da 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts
@@ -4,6 +4,8 @@ import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/get
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
+import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
@@ -26,6 +28,11 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
recordTablePendingRecordIdByGroupComponentFamilyState,
);
+ const recordIndexRecordIdsByGroupFamilyState =
+ useRecoilComponentCallbackStateV2(
+ recordIndexRecordIdsByGroupComponentFamilyState,
+ );
+
const upsertTableRecordInGroup = useRecoilCallback(
({ snapshot }) =>
(persistField: () => void, recordId: string, fieldName: string) => {
@@ -54,11 +61,23 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
recordGroupDefinitionFamilyState(recordGroupId),
);
+ const recordGroupIds = getSnapshotValue(
+ snapshot,
+ recordIndexRecordIdsByGroupFamilyState(recordGroupId),
+ );
+
const recordGroupFieldMetadataItem = objectMetadataItem.fields.find(
(fieldMetadata) =>
fieldMetadata.id === recordGroupDefinition?.fieldMetadataId,
);
+ const lastId = recordGroupIds?.[recordGroupIds.length - 1];
+
+ const objectRecord = getSnapshotValue(
+ snapshot,
+ recordStoreFamilyState(lastId),
+ );
+
if (
isDefined(recordTablePendingRecordId) &&
isDefined(recordGroupDefinition) &&
@@ -69,7 +88,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
id: recordTablePendingRecordId,
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
[recordGroupFieldMetadataItem.name]: recordGroupDefinition.value,
- position: 'first',
+ position: (objectRecord?.position ?? 0) + 0.0001,
});
} else if (!recordTablePendingRecordId) {
persistField();
@@ -79,6 +98,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
createOneRecord,
objectMetadataItem,
recordGroupId,
+ recordIndexRecordIdsByGroupFamilyState,
recordTablePendingRecordIdByGroupFamilyState,
],
);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx
index 1d05147c0..929472098 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx
@@ -3,22 +3,14 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
-import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
-import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconPlus } from 'twenty-ui';
-import { isDefined } from '~/utils/isDefined';
export const RecordTableRecordGroupSectionAddNew = () => {
const { recordTableId } = useRecordTableContextOrThrow();
const currentRecordGroupId = useCurrentRecordGroupId();
- const pendingRecordId = useRecoilComponentFamilyValueV2(
- recordTablePendingRecordIdByGroupComponentFamilyState,
- currentRecordGroupId,
- );
-
const recordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector,
);
@@ -30,8 +22,6 @@ export const RecordTableRecordGroupSectionAddNew = () => {
createNewTableRecordInGroup(currentRecordGroupId);
};
- if (isDefined(pendingRecordId)) return null;
-
return (