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.
This commit is contained in:
Jérémy M
2024-12-16 14:57:16 +01:00
committed by GitHub
parent 5a27491bb2
commit 9e9c1bdff1
6 changed files with 90 additions and 15 deletions

View File

@ -40,6 +40,7 @@
"@tiptap/extension-text-style": "^2.8.0", "@tiptap/extension-text-style": "^2.8.0",
"@tiptap/react": "^2.8.0", "@tiptap/react": "^2.8.0",
"@xyflow/react": "^12.0.4", "@xyflow/react": "^12.0.4",
"buffer": "^6.0.3",
"transliteration": "^2.3.5" "transliteration": "^2.3.5"
} }
} }

View File

@ -1,5 +1,6 @@
import { ApolloCache, StoreObject } from '@apollo/client'; import { ApolloCache, StoreObject } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { Buffer } from 'buffer';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@ -75,10 +76,21 @@ export const triggerCreateRecordsOptimisticEffect = ({
rootQueryCachedObjectRecordConnection, rootQueryCachedObjectRecordConnection,
); );
const rootQueryCachedPageInfo = readField<{
startCursor?: string;
endCursor?: string;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
}>('pageInfo', rootQueryCachedObjectRecordConnection);
const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges
? [...rootQueryCachedRecordEdges] ? [...rootQueryCachedRecordEdges]
: []; : [];
const nextQueryCachedPageInfo = isDefined(rootQueryCachedPageInfo)
? { ...rootQueryCachedPageInfo }
: {};
const hasAddedRecords = recordsToCreate const hasAddedRecords = recordsToCreate
.map((recordToCreate) => { .map((recordToCreate) => {
if (isNonEmptyString(recordToCreate.id)) { if (isNonEmptyString(recordToCreate.id)) {
@ -116,11 +128,61 @@ export const triggerCreateRecordsOptimisticEffect = ({
); );
if (recordToCreateReference && !recordAlreadyInCache) { 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), __typename: getEdgeTypename(objectMetadataItem.nameSingular),
node: recordToCreateReference, 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; return true;
} }
@ -140,6 +202,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
totalCount: isDefined(rootQueryCachedRecordTotalCount) totalCount: isDefined(rootQueryCachedRecordTotalCount)
? rootQueryCachedRecordTotalCount + 1 ? rootQueryCachedRecordTotalCount + 1
: undefined, : undefined,
pageInfo: nextQueryCachedPageInfo,
}; };
}, },
}, },

View File

@ -56,8 +56,8 @@ export const RecordTableRecordGroupRows = () => {
/> />
); );
})} })}
<RecordTableRecordGroupSectionLoadMore />
<RecordTablePendingRecordGroupRow /> <RecordTablePendingRecordGroupRow />
<RecordTableRecordGroupSectionLoadMore />
<RecordTableRecordGroupSectionAddNew /> <RecordTableRecordGroupSectionAddNew />
</> </>
); );

View File

@ -4,6 +4,8 @@ import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/get
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; 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 { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
@ -26,6 +28,11 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
recordTablePendingRecordIdByGroupComponentFamilyState, recordTablePendingRecordIdByGroupComponentFamilyState,
); );
const recordIndexRecordIdsByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordIndexRecordIdsByGroupComponentFamilyState,
);
const upsertTableRecordInGroup = useRecoilCallback( const upsertTableRecordInGroup = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(persistField: () => void, recordId: string, fieldName: string) => { (persistField: () => void, recordId: string, fieldName: string) => {
@ -54,11 +61,23 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
recordGroupDefinitionFamilyState(recordGroupId), recordGroupDefinitionFamilyState(recordGroupId),
); );
const recordGroupIds = getSnapshotValue(
snapshot,
recordIndexRecordIdsByGroupFamilyState(recordGroupId),
);
const recordGroupFieldMetadataItem = objectMetadataItem.fields.find( const recordGroupFieldMetadataItem = objectMetadataItem.fields.find(
(fieldMetadata) => (fieldMetadata) =>
fieldMetadata.id === recordGroupDefinition?.fieldMetadataId, fieldMetadata.id === recordGroupDefinition?.fieldMetadataId,
); );
const lastId = recordGroupIds?.[recordGroupIds.length - 1];
const objectRecord = getSnapshotValue(
snapshot,
recordStoreFamilyState(lastId),
);
if ( if (
isDefined(recordTablePendingRecordId) && isDefined(recordTablePendingRecordId) &&
isDefined(recordGroupDefinition) && isDefined(recordGroupDefinition) &&
@ -69,7 +88,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
id: recordTablePendingRecordId, id: recordTablePendingRecordId,
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue, [labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
[recordGroupFieldMetadataItem.name]: recordGroupDefinition.value, [recordGroupFieldMetadataItem.name]: recordGroupDefinition.value,
position: 'first', position: (objectRecord?.position ?? 0) + 0.0001,
}); });
} else if (!recordTablePendingRecordId) { } else if (!recordTablePendingRecordId) {
persistField(); persistField();
@ -79,6 +98,7 @@ export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
createOneRecord, createOneRecord,
objectMetadataItem, objectMetadataItem,
recordGroupId, recordGroupId,
recordIndexRecordIdsByGroupFamilyState,
recordTablePendingRecordIdByGroupFamilyState, recordTablePendingRecordIdByGroupFamilyState,
], ],
); );

View File

@ -3,22 +3,14 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
export const RecordTableRecordGroupSectionAddNew = () => { export const RecordTableRecordGroupSectionAddNew = () => {
const { recordTableId } = useRecordTableContextOrThrow(); const { recordTableId } = useRecordTableContextOrThrow();
const currentRecordGroupId = useCurrentRecordGroupId(); const currentRecordGroupId = useCurrentRecordGroupId();
const pendingRecordId = useRecoilComponentFamilyValueV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
currentRecordGroupId,
);
const recordIds = useRecoilComponentValueV2( const recordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector, recordIndexAllRecordIdsComponentSelector,
); );
@ -30,8 +22,6 @@ export const RecordTableRecordGroupSectionAddNew = () => {
createNewTableRecordInGroup(currentRecordGroupId); createNewTableRecordInGroup(currentRecordGroupId);
}; };
if (isDefined(pendingRecordId)) return null;
return ( return (
<RecordTableActionRow <RecordTableActionRow
draggableId={`add-new-record-${currentRecordGroupId}`} draggableId={`add-new-record-${currentRecordGroupId}`}

View File

@ -43950,6 +43950,7 @@ __metadata:
"@tiptap/extension-text-style": "npm:^2.8.0" "@tiptap/extension-text-style": "npm:^2.8.0"
"@tiptap/react": "npm:^2.8.0" "@tiptap/react": "npm:^2.8.0"
"@xyflow/react": "npm:^12.0.4" "@xyflow/react": "npm:^12.0.4"
buffer: "npm:^6.0.3"
transliteration: "npm:^2.3.5" transliteration: "npm:^2.3.5"
languageName: unknown languageName: unknown
linkType: soft linkType: soft