From 9da9d1e3bdb58ce072ebd623c54d9aba970d2475 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 29 Jan 2024 15:28:28 +0100 Subject: [PATCH] Build infinite scroll for email threads (#3666) * Use recoil state for page info * Remove memoization * Remove right drawer fetch more loader --------- Co-authored-by: Thomas Trompette --- packages/twenty-emails/package.json | 2 +- .../emails/components/EmailThreads.tsx | 84 +++++++++++++++---- .../components/RightDrawerEmailThread.tsx | 18 ++-- .../RightDrawerEmailThreadFetchMoreLoader.tsx | 26 ------ .../emails/state/emailThreadsPageState.ts | 11 +++ 5 files changed, 89 insertions(+), 52 deletions(-) delete mode 100644 packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThreadFetchMoreLoader.tsx create mode 100644 packages/twenty-front/src/modules/activities/emails/state/emailThreadsPageState.ts diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index 7aaac25a7..49fbeb059 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -36,4 +36,4 @@ "nx": { "projectType": "library" } -} \ No newline at end of file +} diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx index ffe468a54..97f85bf63 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -1,11 +1,16 @@ +import { useState } from 'react'; import { useQuery } from '@apollo/client'; import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; -import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/messaging.constants'; import { useEmailThread } from '@/activities/emails/hooks/useEmailThread'; import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId'; import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId'; +import { + emailThreadsPageState, + EmailThreadsPageType, +} from '@/activities/emails/state/emailThreadsPageState'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { @@ -15,13 +20,19 @@ import { import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Card } from '@/ui/layout/card/components/Card'; import { Section } from '@/ui/layout/section/components/Section'; -import { TimelineThread } from '~/generated/graphql'; +import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMoreLoader'; +import { + GetTimelineThreadsFromPersonIdQueryVariables, + TimelineThread, +} from '~/generated/graphql'; const StyledContainer = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(6)}; padding: ${({ theme }) => theme.spacing(6, 6, 2)}; + height: 100%; + overflow: auto; `; const StyledH1Title = styled(H1Title)` @@ -39,42 +50,75 @@ export const EmailThreads = ({ entity: ActivityTargetableObject; }) => { const { openEmailThread } = useEmailThread(); - const { enqueueSnackBar } = useSnackBar(); - const threadQuery = + const [emailThreadsPage, setEmailThreadsPage] = + useRecoilState(emailThreadsPageState); + + const [isFetchingMoreEmails, setIsFetchingMoreEmails] = useState(false); + + const [threadQuery, queryName] = entity.targetObjectNameSingular === CoreObjectNameSingular.Person - ? getTimelineThreadsFromPersonId - : getTimelineThreadsFromCompanyId; + ? [getTimelineThreadsFromPersonId, 'getTimelineThreadsFromPersonId'] + : [getTimelineThreadsFromCompanyId, 'getTimelineThreadsFromCompanyId']; const threadQueryVariables = { ...(entity.targetObjectNameSingular === CoreObjectNameSingular.Person ? { personId: entity.id } : { companyId: entity.id }), page: 1, - pageSize: TIMELINE_THREADS_DEFAULT_PAGE_SIZE, - }; + pageSize: 10, + } as GetTimelineThreadsFromPersonIdQueryVariables; - const threads = useQuery(threadQuery, { + const { data, loading, fetchMore, error } = useQuery(threadQuery, { variables: threadQueryVariables, }); - if (threads.error) { - enqueueSnackBar(threads.error.message || 'Error loading email threads', { + const fetchMoreRecords = async () => { + if (emailThreadsPage.hasNextPage && !isFetchingMoreEmails) { + setIsFetchingMoreEmails(true); + + await fetchMore({ + variables: { + ...threadQueryVariables, + page: emailThreadsPage.pageNumber + 1, + }, + updateQuery: (prev, { fetchMoreResult }) => { + if (!fetchMoreResult || !fetchMoreResult?.[queryName].length) { + setEmailThreadsPage((emailThreadsPage) => ({ + ...emailThreadsPage, + hasNextPage: false, + })); + return prev; + } + + return { + [queryName]: [ + ...(prev?.[queryName] ?? []), + ...(fetchMoreResult?.[queryName] ?? []), + ], + }; + }, + }); + setEmailThreadsPage((emailThreadsPage) => ({ + ...emailThreadsPage, + pageNumber: emailThreadsPage.pageNumber + 1, + })); + setIsFetchingMoreEmails(false); + } + }; + + if (error) { + enqueueSnackBar(error.message || 'Error loading email threads', { variant: 'error', }); } - if (threads.loading) { + if (loading) { return; } - const timelineThreads: TimelineThread[] = - threads?.data?.[ - entity.targetObjectNameSingular === CoreObjectNameSingular.Person - ? 'getTimelineThreadsFromPersonId' - : 'getTimelineThreadsFromCompanyId' - ] ?? []; + const timelineThreads: TimelineThread[] = data?.[queryName] ?? []; return ( @@ -98,6 +142,10 @@ export const EmailThreads = ({ /> ))} + ); diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx index 2609f77e8..13f8d8f2f 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx @@ -1,14 +1,14 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader'; import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage'; -import { RightDrawerEmailThreadFetchMoreLoader } from '@/activities/emails/right-drawer/components/RightDrawerEmailThreadFetchMoreLoader'; import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState'; import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMoreLoader'; const StyledContainer = styled.div` box-sizing: border-box; @@ -26,9 +26,10 @@ export const RightDrawerEmailThread = () => { const { records: messages, loading, - fetchMoreRecords: fetchMoreMessages, + fetchMoreRecords, } = useFindManyRecords({ depth: 3, + limit: 10, filter: { messageThreadId: { eq: viewableEmailThread?.id, @@ -42,6 +43,12 @@ export const RightDrawerEmailThread = () => { useRecordsWithoutConnection: true, }); + const fetchMoreMessages = useCallback(() => { + if (!loading) { + fetchMoreRecords(); + } + }, [fetchMoreRecords, loading]); + if (!viewableEmailThread) { return null; } @@ -60,10 +67,7 @@ export const RightDrawerEmailThread = () => { sentAt={message.receivedAt} /> ))} - + ); }; diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThreadFetchMoreLoader.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThreadFetchMoreLoader.tsx deleted file mode 100644 index 39edb1995..000000000 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThreadFetchMoreLoader.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMoreLoader'; - -type RightDrawerEmailThreadFetchMoreLoaderProps = { - loading: boolean; - fetchMoreMessages: () => void; -}; - -export const RightDrawerEmailThreadFetchMoreLoader = ({ - loading, - fetchMoreMessages, -}: RightDrawerEmailThreadFetchMoreLoaderProps) => { - const onLastRowVisible = useRecoilCallback( - () => async () => { - if (!loading) { - fetchMoreMessages(); - } - }, - [fetchMoreMessages, loading], - ); - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/activities/emails/state/emailThreadsPageState.ts b/packages/twenty-front/src/modules/activities/emails/state/emailThreadsPageState.ts new file mode 100644 index 000000000..641cf92bb --- /dev/null +++ b/packages/twenty-front/src/modules/activities/emails/state/emailThreadsPageState.ts @@ -0,0 +1,11 @@ +import { atom } from 'recoil'; + +export type EmailThreadsPageType = { + pageNumber: number; + hasNextPage: boolean; +}; + +export const emailThreadsPageState = atom({ + key: 'EmailThreadsPageState', + default: { pageNumber: 1, hasNextPage: true }, +});