diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx
new file mode 100644
index 000000000..a6232fefa
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx
@@ -0,0 +1,467 @@
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { ReactNode, act } from 'react';
+
+import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
+import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
+import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
+import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
+import { MockedResponse } from '@apollo/client/testing';
+import gql from 'graphql-tag';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { getPeopleMock } from '~/testing/mock-data/people';
+
+const recordTableId = 'people';
+const objectNameSingular = 'person';
+const onColumnsChange = jest.fn();
+
+const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => {
+ return <>{children}>;
+};
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindManyPeople(
+ $filter: PersonFilterInput
+ $orderBy: [PersonOrderByInput]
+ $lastCursor: String
+ $limit: Int
+ ) {
+ people(
+ filter: $filter
+ orderBy: $orderBy
+ first: $limit
+ after: $lastCursor
+ ) {
+ edges {
+ node {
+ __typename
+ avatarUrl
+ deletedAt
+ id
+ name {
+ firstName
+ lastName
+ }
+ noteTargets {
+ edges {
+ node {
+ __typename
+ company {
+ __typename
+ accountOwnerId
+ address {
+ addressStreet1
+ addressStreet2
+ addressCity
+ addressState
+ addressCountry
+ addressPostcode
+ addressLat
+ addressLng
+ }
+ annualRecurringRevenue {
+ amountMicros
+ currencyCode
+ }
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ domainName {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ employees
+ id
+ idealCustomerProfile
+ introVideo {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ linkedinLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ name
+ position
+ tagline
+ updatedAt
+ visaSponsorship
+ workPolicy
+ xLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ }
+ companyId
+ createdAt
+ deletedAt
+ id
+ note {
+ __typename
+ body
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ id
+ position
+ title
+ updatedAt
+ }
+ noteId
+ opportunity {
+ __typename
+ amount {
+ amountMicros
+ currencyCode
+ }
+ closeDate
+ companyId
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ id
+ name
+ pointOfContactId
+ position
+ stage
+ updatedAt
+ }
+ opportunityId
+ person {
+ __typename
+ avatarUrl
+ city
+ companyId
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ emails {
+ primaryEmail
+ additionalEmails
+ }
+ id
+ intro
+ jobTitle
+ linkedinLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ name {
+ firstName
+ lastName
+ }
+ performanceRating
+ phones {
+ primaryPhoneNumber
+ primaryPhoneCountryCode
+ additionalPhones
+ }
+ position
+ updatedAt
+ whatsapp {
+ primaryPhoneNumber
+ primaryPhoneCountryCode
+ additionalPhones
+ }
+ workPreference
+ xLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ }
+ personId
+ rocket {
+ __typename
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ id
+ name
+ position
+ updatedAt
+ }
+ rocketId
+ updatedAt
+ }
+ }
+ }
+ position
+ taskTargets {
+ edges {
+ node {
+ __typename
+ company {
+ __typename
+ accountOwnerId
+ address {
+ addressStreet1
+ addressStreet2
+ addressCity
+ addressState
+ addressCountry
+ addressPostcode
+ addressLat
+ addressLng
+ }
+ annualRecurringRevenue {
+ amountMicros
+ currencyCode
+ }
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ domainName {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ employees
+ id
+ idealCustomerProfile
+ introVideo {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ linkedinLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ name
+ position
+ tagline
+ updatedAt
+ visaSponsorship
+ workPolicy
+ xLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ }
+ companyId
+ createdAt
+ deletedAt
+ id
+ opportunity {
+ __typename
+ amount {
+ amountMicros
+ currencyCode
+ }
+ closeDate
+ companyId
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ id
+ name
+ pointOfContactId
+ position
+ stage
+ updatedAt
+ }
+ opportunityId
+ person {
+ __typename
+ avatarUrl
+ city
+ companyId
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ emails {
+ primaryEmail
+ additionalEmails
+ }
+ id
+ intro
+ jobTitle
+ linkedinLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ name {
+ firstName
+ lastName
+ }
+ performanceRating
+ phones {
+ primaryPhoneNumber
+ primaryPhoneCountryCode
+ additionalPhones
+ }
+ position
+ updatedAt
+ whatsapp {
+ primaryPhoneNumber
+ primaryPhoneCountryCode
+ additionalPhones
+ }
+ workPreference
+ xLink {
+ primaryLinkUrl
+ primaryLinkLabel
+ secondaryLinks
+ }
+ }
+ personId
+ rocket {
+ __typename
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ id
+ name
+ position
+ updatedAt
+ }
+ rocketId
+ task {
+ __typename
+ assigneeId
+ body
+ createdAt
+ createdBy {
+ source
+ workspaceMemberId
+ name
+ }
+ deletedAt
+ dueAt
+ id
+ position
+ status
+ title
+ updatedAt
+ }
+ taskId
+ updatedAt
+ }
+ }
+ }
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ totalCount
+ }
+ }
+ `,
+ variables: {
+ filter: {},
+ orderBy: [{ position: 'AscNullsFirst' }],
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ people: getPeopleMock(),
+ },
+ })),
+ },
+];
+
+const HookMockWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: mocks,
+});
+
+const Wrapper = ({ children }: { children: ReactNode }) => {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+};
+
+describe('useLazyLoadRecordIndexTable', () => {
+ it('should fetch', async () => {
+ const { result } = renderHook(
+ () => {
+ const { findManyRecords, ...result } =
+ useLazyLoadRecordIndexTable(objectNameSingular);
+
+ return {
+ findManyRecords,
+ ...result,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ expect(result.current.loading).toBe(false);
+
+ act(() => {
+ result.current.findManyRecords();
+ });
+
+ expect(Array.isArray(result.current.records)).toBe(true);
+ expect(result.current.records.length).toBe(13);
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx
deleted file mode 100644
index 7008bec92..000000000
--- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { expect } from '@storybook/test';
-import { renderHook } from '@testing-library/react';
-import { ReactNode } from 'react';
-
-import { mocks } from '@/auth/hooks/__mocks__/useAuth';
-import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
-import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
-import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
-import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
-import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
-
-const recordTableId = 'people';
-const objectNameSingular = 'person';
-const onColumnsChange = jest.fn();
-
-const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => {
- return <>{children}>;
-};
-
-const HookMockWrapper = getJestMetadataAndApolloMocksWrapper({
- apolloMocks: mocks,
-});
-
-const Wrapper = ({ children }: { children: ReactNode }) => {
- return (
-
-
-
-
-
- {children}
-
-
-
-
-
- );
-};
-
-describe('useObjectRecordTable', () => {
- it('should skip fetch if currentWorkspace is undefined', async () => {
- const { result } = renderHook(
- () => useLoadRecordIndexTable(objectNameSingular),
- {
- wrapper: Wrapper,
- },
- );
-
- expect(result.current.loading).toBe(false);
- expect(Array.isArray(result.current.records)).toBe(true);
- expect(result.current.records.length).toBe(13);
- });
-});
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFetchMoreRecordsWithPagination.ts b/packages/twenty-front/src/modules/object-record/hooks/useFetchMoreRecordsWithPagination.ts
index 5f19c4edc..977333c50 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/useFetchMoreRecordsWithPagination.ts
+++ b/packages/twenty-front/src/modules/object-record/hooks/useFetchMoreRecordsWithPagination.ts
@@ -142,7 +142,7 @@ export const useFetchMoreRecordsWithPagination = <
const pageInfo =
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo;
- if (isDefined(data?.[objectMetadataItem.namePlural])) {
+ if (isDefined(pageInfo)) {
set(
cursorFamilyState(queryIdentifier),
pageInfo.endCursor ?? '',
@@ -201,7 +201,6 @@ export const useFetchMoreRecordsWithPagination = <
fetchMore,
filter,
orderBy,
- data,
onCompleted,
handleFindManyRecordsError,
queryIdentifier,
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts
index caf315296..56a226510 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts
+++ b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts
@@ -67,7 +67,7 @@ export const useLazyFindManyRecords = ({
onError: handleFindManyRecordsError,
});
- const { fetchMoreRecords, totalCount, records } =
+ const { fetchMoreRecords, totalCount, records, hasNextPage } =
useFetchMoreRecordsWithPagination({
objectNameSingular,
filter,
@@ -108,8 +108,9 @@ export const useLazyFindManyRecords = ({
loading,
error,
fetchMore,
- fetchMoreRecordsWithPagination: fetchMoreRecords,
+ fetchMoreRecords,
queryStateIdentifier: queryIdentifier,
findManyRecords: findManyRecordsLazy,
+ hasNextPage,
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector.ts
index 848cebbad..9ff30316c 100644
--- a/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector.ts
+++ b/packages/twenty-front/src/modules/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector.ts
@@ -2,7 +2,7 @@ import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/s
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
-import { sortedInsert } from '@/object-record/record-group/utils/sortedInsert';
+import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
@@ -54,7 +54,7 @@ export const visibleRecordGroupIdsComponentSelector = createComponentSelectorV2<
isDefined(recordGroupDefinition) &&
recordGroupDefinition.isVisible
) {
- sortedInsert(result, recordGroupDefinition, comparator);
+ recordGroupSortedInsert(result, recordGroupDefinition, comparator);
}
}
diff --git a/packages/twenty-front/src/modules/object-record/record-group/utils/__tests__/recordGroupSortedInsert.test.ts b/packages/twenty-front/src/modules/object-record/record-group/utils/__tests__/recordGroupSortedInsert.test.ts
new file mode 100644
index 000000000..4b955fd68
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-group/utils/__tests__/recordGroupSortedInsert.test.ts
@@ -0,0 +1,66 @@
+// export const recordGroupSortedInsert = (
+// array: T[],
+// item: T,
+// comparator: (a: T, b: T) => number,
+// ) => {
+// let low = 0;
+// let high = array.length;
+
+import { expect } from '@storybook/test';
+
+// while (low < high) {
+// const mid = Math.floor((low + high) / 2);
+
+// if (comparator(item, array[mid]) < 0) {
+// high = mid;
+// } else {
+// low = mid + 1;
+// }
+// }
+
+// array.splice(low, 0, item);
+// };
+
+import { recordGroupSortedInsert } from '../recordGroupSortedInsert';
+
+describe('recordGroupSortedInsert', () => {
+ it('should insert an item into an empty array', () => {
+ const array: number[] = [];
+ const item = 1;
+ const comparator = (a: number, b: number) => a - b;
+
+ recordGroupSortedInsert(array, item, comparator);
+
+ expect(array).toEqual([1]);
+ });
+
+ it('should insert an item at the beginning of the array', () => {
+ const array = [2, 3, 4];
+ const item = 1;
+ const comparator = (a: number, b: number) => a - b;
+
+ recordGroupSortedInsert(array, item, comparator);
+
+ expect(array).toEqual([1, 2, 3, 4]);
+ });
+
+ it('should insert an item at the end of the array', () => {
+ const array = [1, 2, 3];
+ const item = 4;
+ const comparator = (a: number, b: number) => a - b;
+
+ recordGroupSortedInsert(array, item, comparator);
+
+ expect(array).toEqual([1, 2, 3, 4]);
+ });
+
+ it('should insert an item in the middle of the array', () => {
+ const array = [1, 3, 4];
+ const item = 2;
+ const comparator = (a: number, b: number) => a - b;
+
+ recordGroupSortedInsert(array, item, comparator);
+
+ expect(array).toEqual([1, 2, 3, 4]);
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/record-group/utils/sortedInsert.ts b/packages/twenty-front/src/modules/object-record/record-group/utils/recordGroupSortedInsert.ts
similarity index 88%
rename from packages/twenty-front/src/modules/object-record/record-group/utils/sortedInsert.ts
rename to packages/twenty-front/src/modules/object-record/record-group/utils/recordGroupSortedInsert.ts
index 5a7dd1027..578dcf640 100644
--- a/packages/twenty-front/src/modules/object-record/record-group/utils/sortedInsert.ts
+++ b/packages/twenty-front/src/modules/object-record/record-group/utils/recordGroupSortedInsert.ts
@@ -1,4 +1,4 @@
-export const sortedInsert = (
+export const recordGroupSortedInsert = (
array: T[],
item: T,
comparator: (a: T, b: T) => number,
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx
index d6c6f94dc..2c8106f6d 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx
@@ -6,7 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
-import { useFindManyParams } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
+import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useContext, useEffect } from 'react';
@@ -31,7 +31,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
objectNameSingular,
});
- const findManyRecordsParams = useFindManyParams(
+ const findManyRecordsParams = useFindManyRecordIndexTableParams(
objectMetadataItem?.nameSingular ?? '',
objectMetadataItem?.namePlural ?? '',
);
diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts
index 523762226..bc256a8bc 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts
@@ -13,7 +13,7 @@ import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRec
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
-import { useFindManyParams } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
+import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType';
@@ -94,29 +94,24 @@ export const useExportFetchRecords = ({
objectMetadataItem,
);
- const findManyRecordsParams = useFindManyParams(
+ const findManyRecordsParams = useFindManyRecordIndexTableParams(
objectMetadataItem.nameSingular,
recordIndexId,
);
- const {
- findManyRecords,
- totalCount,
- records,
- fetchMoreRecordsWithPagination,
- loading,
- } = useLazyFindManyRecords({
- ...findManyRecordsParams,
- filter: queryFilter,
- limit: pageSize,
- });
+ const { findManyRecords, totalCount, records, fetchMoreRecords, loading } =
+ useLazyFindManyRecords({
+ ...findManyRecordsParams,
+ filter: queryFilter,
+ limit: pageSize,
+ });
useEffect(() => {
const fetchNextPage = async () => {
setInflight(true);
setPreviousRecordCount(records.length);
- await fetchMoreRecordsWithPagination();
+ await fetchMoreRecords();
setPageCount((state) => state + 1);
setProgress({
@@ -166,7 +161,7 @@ export const useExportFetchRecords = ({
}
}, [
delayMs,
- fetchMoreRecordsWithPagination,
+ fetchMoreRecords,
inflight,
isDownloading,
pageCount,
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts
similarity index 57%
rename from packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts
rename to packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts
index ccfec9af3..e77e993ab 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts
@@ -1,25 +1,15 @@
-import { useRecoilValue } from 'recoil';
-
-import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
-import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
-import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hooks/useRecordTableRecordGqlFields';
-import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
-import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { isNull } from '@sniptt/guards';
import { useMemo } from 'react';
import { isDefined } from 'twenty-ui';
-import { WorkspaceActivationStatus } from '~/generated/graphql';
-export const useFindManyParams = (
+export const useFindManyRecordIndexTableParams = (
objectNameSingular: string,
recordTableId?: string,
) => {
@@ -84,51 +74,7 @@ export const useFindManyParams = (
...recordGroupFilter,
},
orderBy,
- };
-};
-
-export const useLoadRecordIndexTable = (objectNameSingular: string) => {
- const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular,
- });
-
- const { setRecordTableData, setIsRecordTableInitialLoading } =
- useRecordTable();
- const currentWorkspace = useRecoilValue(currentWorkspaceState);
- const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
- const params = useFindManyParams(objectNameSingular);
-
- const recordGqlFields = useRecordTableRecordGqlFields({ objectMetadataItem });
-
- const {
- records,
- loading,
- totalCount,
- fetchMoreRecords,
- queryStateIdentifier,
- hasNextPage,
- } = useFindManyRecords({
- ...params,
- recordGqlFields,
- onCompleted: () => {
- setIsRecordTableInitialLoading(false);
- },
- onError: () => {
- setIsRecordTableInitialLoading(false);
- },
- skip: isNull(currentWorkspaceMember),
- });
-
- return {
- records:
- currentWorkspace?.activationStatus === WorkspaceActivationStatus.Active
- ? records
- : SIGN_IN_BACKGROUND_MOCK_COMPANIES,
- totalCount: totalCount,
- loading,
- fetchMoreRecords,
- queryStateIdentifier,
- setRecordTableData,
- hasNextPage,
+ // If we have a current record group definition, we only want to fetch 8 records by page
+ ...(currentRecordGroupDefinition ? { limit: 8 } : {}),
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLazyLoadRecordIndexTable.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLazyLoadRecordIndexTable.ts
new file mode 100644
index 000000000..5b212528f
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLazyLoadRecordIndexTable.ts
@@ -0,0 +1,58 @@
+import { useRecoilValue } from 'recoil';
+
+import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
+import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
+import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hooks/useRecordTableRecordGqlFields';
+import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
+import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
+import { WorkspaceActivationStatus } from '~/generated/graphql';
+
+export const useLazyLoadRecordIndexTable = (objectNameSingular: string) => {
+ const { objectMetadataItem } = useObjectMetadataItem({
+ objectNameSingular,
+ });
+
+ const { setRecordTableData, setIsRecordTableInitialLoading } =
+ useRecordTable();
+
+ const currentWorkspace = useRecoilValue(currentWorkspaceState);
+
+ const params = useFindManyRecordIndexTableParams(objectNameSingular);
+
+ const recordGqlFields = useRecordTableRecordGqlFields({ objectMetadataItem });
+
+ const {
+ findManyRecords,
+ records,
+ loading,
+ totalCount,
+ fetchMoreRecords,
+ queryStateIdentifier,
+ hasNextPage,
+ } = useLazyFindManyRecords({
+ ...params,
+ recordGqlFields,
+ onCompleted: () => {
+ setIsRecordTableInitialLoading(false);
+ },
+ onError: () => {
+ setIsRecordTableInitialLoading(false);
+ },
+ });
+
+ return {
+ findManyRecords,
+ records:
+ currentWorkspace?.activationStatus === WorkspaceActivationStatus.Active
+ ? records
+ : SIGN_IN_BACKGROUND_MOCK_COMPANIES,
+ totalCount: totalCount,
+ loading,
+ fetchMoreRecords,
+ queryStateIdentifier,
+ setRecordTableData,
+ hasNextPage,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState.ts
new file mode 100644
index 000000000..11add135e
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState.ts
@@ -0,0 +1,10 @@
+import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
+import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
+import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
+
+export const recordIndexHasFetchedAllRecordsByGroupComponentState =
+ createComponentFamilyStateV2({
+ key: 'recordIndexHasFetchedAllRecordsByGroupComponentState',
+ componentInstanceContext: ViewComponentInstanceContext,
+ defaultValue: false,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
index 89fc0e8f3..f2c2fb0f6 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
@@ -2,26 +2,30 @@ import { useContext, useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useDebouncedCallback } from 'use-debounce';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
-import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
+import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
+import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { isNonEmptyString } from '@sniptt/guards';
+import { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
-import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
-import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
export const RecordTableNoRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext);
+ const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
const [hasInitializedScroll, setHasInitializedScroll] = useState(false);
const {
+ findManyRecords,
fetchMoreRecords,
records,
totalCount,
@@ -29,7 +33,7 @@ export const RecordTableNoRecordGroupBodyEffect = () => {
loading,
queryStateIdentifier,
hasNextPage,
- } = useLoadRecordIndexTable(objectNameSingular);
+ } = useLazyLoadRecordIndexTable(objectNameSingular);
const isFetchingMoreObjects = useRecoilValue(
isFetchingMoreRecordsFamilyState(queryStateIdentifier),
@@ -132,5 +136,13 @@ export const RecordTableNoRecordGroupBodyEffect = () => {
setEncounteredUnrecoverableError,
]);
+ useEffect(() => {
+ if (isNull(currentWorkspaceMember)) {
+ return;
+ }
+
+ findManyRecords();
+ }, [currentWorkspaceMember, findManyRecords]);
+
return <>>;
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx
index d768a6700..90569f73e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx
@@ -1,29 +1,39 @@
import { useContext, useEffect, useState } from 'react';
-import { useRecoilState } from 'recoil';
+import { useRecoilState, useRecoilValue } from 'recoil';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
-import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
+import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
+import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
-import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
-import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { isNonEmptyString } from '@sniptt/guards';
+import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
+import { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const RecordTableRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext);
+ const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
const recordGroupId = useCurrentRecordGroupId();
const [hasInitializedScroll, setHasInitializedScroll] = useState(false);
- const { records, totalCount, setRecordTableData, loading, hasNextPage } =
- useLoadRecordIndexTable(objectNameSingular);
+ const {
+ findManyRecords,
+ records,
+ totalCount,
+ setRecordTableData,
+ loading,
+ hasNextPage,
+ } = useLazyLoadRecordIndexTable(objectNameSingular);
- const setHasRecordTableFetchedAllRecordsComponents =
- useSetRecoilComponentStateV2(
- hasRecordTableFetchedAllRecordsComponentStateV2,
+ const setHasRecordFetchedAllRecordsComponents =
+ useSetRecoilComponentFamilyStateV2(
+ recordIndexHasFetchedAllRecordsByGroupComponentState,
+ recordGroupId,
);
const [lastShowPageRecordId] = useRecoilState(lastShowPageRecordIdState);
@@ -65,8 +75,16 @@ export const RecordTableRecordGroupBodyEffect = () => {
useEffect(() => {
const allRecordsHaveBeenFetched = !hasNextPage;
- setHasRecordTableFetchedAllRecordsComponents(allRecordsHaveBeenFetched);
- }, [hasNextPage, setHasRecordTableFetchedAllRecordsComponents]);
+ setHasRecordFetchedAllRecordsComponents(allRecordsHaveBeenFetched);
+ }, [hasNextPage, setHasRecordFetchedAllRecordsComponents]);
+
+ useEffect(() => {
+ if (isNull(currentWorkspaceMember)) {
+ return;
+ }
+
+ findManyRecords();
+ }, [currentWorkspaceMember, findManyRecords]);
return <>>;
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx
index e057d2cbd..6cfa2c722 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx
@@ -7,6 +7,7 @@ import { RecordTableBodyLoading } from '@/object-record/record-table/record-tabl
import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow';
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
+import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -40,6 +41,7 @@ export const RecordTableRecordGroupsBody = () => {
+
))}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx
index 65b5dd390..a5460f16d 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx
@@ -61,6 +61,7 @@ export const RecordTableTd = ({
hasRightBorder = true,
hasBottomBorder = true,
width,
+ colSpan,
...dragHandleProps
}: {
className?: string;
@@ -74,6 +75,7 @@ export const RecordTableTd = ({
hasBottomBorder?: boolean;
left?: number;
width?: number;
+ colSpan?: number;
} & (Partial | null)) => {
const { theme } = useContext(ThemeContext);
@@ -97,6 +99,7 @@ export const RecordTableTd = ({
hasRightBorder={hasRightBorder}
hasBottomBorder={hasBottomBorder}
width={width}
+ colSpan={colSpan}
// eslint-disable-next-line react/jsx-props-no-spreading
{...dragHandleProps}
>
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx
new file mode 100644
index 000000000..94975fe78
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx
@@ -0,0 +1,68 @@
+import styled from '@emotion/styled';
+
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
+import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useTheme } from '@emotion/react';
+import { IconComponent } from 'twenty-ui';
+
+const StyledTrContainer = styled.tr`
+ cursor: pointer;
+`;
+
+const StyledIconContainer = styled(RecordTableTd)`
+ border-right: none;
+ color: ${({ theme }) => theme.font.color.secondary};
+ text-align: center;
+ vertical-align: middle;
+ padding-top: 3px;
+`;
+
+const StyledRecordTableTdTextContainer = styled(RecordTableTd)`
+ border-right: none;
+ height: 32px;
+`;
+
+const StyledText = styled.span`
+ color: ${({ theme }) => theme.font.color.tertiary};
+ margin-left: ${({ theme }) => theme.spacing(2)};
+ font-size: ${({ theme }) => theme.font.size.md};
+ text-align: center;
+ vertical-align: middle;
+`;
+
+const StyledEmptyTd = styled.td`
+ border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
+`;
+
+type RecordTableActionRowProps = {
+ LeftIcon: IconComponent;
+ text: string;
+ onClick?: (event?: React.MouseEvent) => void;
+};
+
+export const RecordTableActionRow = ({
+ LeftIcon,
+ text,
+ onClick,
+}: RecordTableActionRowProps) => {
+ const theme = useTheme();
+
+ const visibleColumns = useRecoilComponentValueV2(
+ visibleTableColumnsComponentSelector,
+ );
+
+ return (
+
+ |
+
+
+
+
+ {text}
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx
index 4e3058011..708fa50db 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx
@@ -8,6 +8,7 @@ import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useC
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
@@ -19,8 +20,8 @@ const StyledTrContainer = styled.tr`
cursor: pointer;
`;
-const StyledChevronContainer = styled.td`
- border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
+const StyledChevronContainer = styled(RecordTableTd)`
+ border-right: none;
color: ${({ theme }) => theme.font.color.secondary};
text-align: center;
vertical-align: middle;
@@ -33,10 +34,9 @@ const StyledTotalRow = styled.span`
vertical-align: middle;
`;
-const StyledRecordGroupSection = styled.td`
- border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
- padding-bottom: 6px;
- padding-top: 6px;
+const StyledRecordGroupSection = styled(RecordTableTd)`
+ border-right: none;
+ height: 32px;
`;
const StyledEmptyTd = styled.td`
@@ -79,7 +79,7 @@ export const RecordTableRecordGroupSection = () => {
return (
- |
+ |
{
/>
{recordIdsByGroup.length}
-
-
+
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore.tsx
new file mode 100644
index 000000000..0418a8a46
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore.tsx
@@ -0,0 +1,37 @@
+import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
+import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
+import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
+import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
+import { useContext } from 'react';
+import { IconArrowDown } from 'twenty-ui';
+
+export const RecordTableRecordGroupSectionLoadMore = () => {
+ const { objectNameSingular } = useContext(RecordTableContext);
+
+ const currentRecordGroupId = useCurrentRecordGroupId();
+
+ const { fetchMoreRecords } = useLazyLoadRecordIndexTable(objectNameSingular);
+
+ const hasFetchedAllRecords = useRecoilComponentFamilyValueV2(
+ recordIndexHasFetchedAllRecordsByGroupComponentState,
+ currentRecordGroupId,
+ );
+
+ const handleLoadMore = () => {
+ fetchMoreRecords();
+ };
+
+ if (hasFetchedAllRecords) {
+ return null;
+ }
+
+ return (
+
+ );
+};