From 9e9c1bdff1df0fb65e31b67010c03d10f0399c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20M?= Date: Mon, 16 Dec 2024 14:57:16 +0100 Subject: [PATCH] feat: record group insert at bottom when created (#9053) Fix #9050 When we add a new record in a record group this one should be added at them bottom. --- packages/twenty-front/package.json | 1 + .../triggerCreateRecordsOptimisticEffect.ts | 69 ++++++++++++++++++- .../components/RecordTableRecordGroupRows.tsx | 2 +- .../internal/useUpsertTableRecordInGroup.ts | 22 +++++- .../RecordTableRecordGroupSectionAddNew.tsx | 10 --- yarn.lock | 1 + 6 files changed, 90 insertions(+), 15 deletions(-) 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 (