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:
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -56,8 +56,8 @@ export const RecordTableRecordGroupRows = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<RecordTableRecordGroupSectionLoadMore />
|
|
||||||
<RecordTablePendingRecordGroupRow />
|
<RecordTablePendingRecordGroupRow />
|
||||||
|
<RecordTableRecordGroupSectionLoadMore />
|
||||||
<RecordTableRecordGroupSectionAddNew />
|
<RecordTableRecordGroupSectionAddNew />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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}`}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user