feat: record group fetch more (#8868)
Fix #8756 This PR is adding a load more button when we're grouping by a field in table view. Only 8 records are printed at first, and a click on `Load more` is needed to show more records. <img width="1347" alt="Screenshot 2024-12-04 at 11 54 15 AM" src="https://github.com/user-attachments/assets/4ad6ad4f-8de9-424d-b7b6-5f82f28c53df"> <img width="1352" alt="Screenshot 2024-12-04 at 11 54 23 AM" src="https://github.com/user-attachments/assets/2a94b4ac-7285-4ba2-9cff-d2f653e36302"> --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -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 (
|
||||
<HookMockWrapper>
|
||||
<ObjectNamePluralSetter>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<RecordTableComponentInstance
|
||||
recordTableId={recordTableId}
|
||||
onColumnsChange={onColumnsChange}
|
||||
>
|
||||
<RecordGroupContext.Provider value={{ recordGroupId: 'default' }}>
|
||||
{children}
|
||||
</RecordGroupContext.Provider>
|
||||
</RecordTableComponentInstance>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</ObjectNamePluralSetter>
|
||||
</HookMockWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -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 (
|
||||
<HookMockWrapper>
|
||||
<ObjectNamePluralSetter>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<RecordTableComponentInstance
|
||||
recordTableId={recordTableId}
|
||||
onColumnsChange={onColumnsChange}
|
||||
>
|
||||
<RecordGroupContext.Provider value={{ recordGroupId: 'default' }}>
|
||||
{children}
|
||||
</RecordGroupContext.Provider>
|
||||
</RecordTableComponentInstance>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</ObjectNamePluralSetter>
|
||||
</HookMockWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
|
||||
@ -67,7 +67,7 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
onError: handleFindManyRecordsError,
|
||||
});
|
||||
|
||||
const { fetchMoreRecords, totalCount, records } =
|
||||
const { fetchMoreRecords, totalCount, records, hasNextPage } =
|
||||
useFetchMoreRecordsWithPagination<T>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
@ -108,8 +108,9 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
loading,
|
||||
error,
|
||||
fetchMore,
|
||||
fetchMoreRecordsWithPagination: fetchMoreRecords,
|
||||
fetchMoreRecords,
|
||||
queryStateIdentifier: queryIdentifier,
|
||||
findManyRecords: findManyRecordsLazy,
|
||||
hasNextPage,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
// export const recordGroupSortedInsert = <T>(
|
||||
// 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]);
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
export const sortedInsert = <T>(
|
||||
export const recordGroupSortedInsert = <T>(
|
||||
array: T[],
|
||||
item: T,
|
||||
comparator: (a: T, b: T) => number,
|
||||
@ -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 ?? '',
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 } : {}),
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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<boolean, RecordGroupDefinition['id']>({
|
||||
key: 'recordIndexHasFetchedAllRecordsByGroupComponentState',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
@ -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 = () => {
|
||||
<RecordTableBodyDroppable recordGroupId={recordGroupId}>
|
||||
<RecordTableRecordGroupSection />
|
||||
<RecordTableRecordGroupRows />
|
||||
<RecordTableRecordGroupSectionLoadMore />
|
||||
</RecordTableBodyDroppable>
|
||||
</RecordGroupContext.Provider>
|
||||
))}
|
||||
|
||||
@ -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<DraggableProvidedDragHandleProps> | 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}
|
||||
>
|
||||
|
||||
@ -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<HTMLTableRowElement>) => void;
|
||||
};
|
||||
|
||||
export const RecordTableActionRow = ({
|
||||
LeftIcon,
|
||||
text,
|
||||
onClick,
|
||||
}: RecordTableActionRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const visibleColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTrContainer onClick={onClick}>
|
||||
<td aria-hidden />
|
||||
<StyledIconContainer>
|
||||
<LeftIcon size={theme.icon.size.sm} color={theme.font.color.tertiary} />
|
||||
</StyledIconContainer>
|
||||
<StyledRecordTableTdTextContainer colSpan={visibleColumns.length}>
|
||||
<StyledText>{text}</StyledText>
|
||||
</StyledRecordTableTdTextContainer>
|
||||
<StyledEmptyTd />
|
||||
<StyledEmptyTd />
|
||||
</StyledTrContainer>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<StyledTrContainer onClick={handleDropdownToggle}>
|
||||
<td aria-hidden></td>
|
||||
<td aria-hidden />
|
||||
<StyledChevronContainer>
|
||||
<motion.span
|
||||
animate={{ rotate: isRecordGroupTableSectionToggled ? 180 : 0 }}
|
||||
@ -108,8 +108,8 @@ export const RecordTableRecordGroupSection = () => {
|
||||
/>
|
||||
<StyledTotalRow>{recordIdsByGroup.length}</StyledTotalRow>
|
||||
</StyledRecordGroupSection>
|
||||
<StyledEmptyTd></StyledEmptyTd>
|
||||
<StyledEmptyTd></StyledEmptyTd>
|
||||
<StyledEmptyTd />
|
||||
<StyledEmptyTd />
|
||||
</StyledTrContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 (
|
||||
<RecordTableActionRow
|
||||
LeftIcon={IconArrowDown}
|
||||
text="Load more"
|
||||
onClick={handleLoadMore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user