Files
twenty/packages/twenty-front/src/modules/activities/emails/components/EmailsCard.tsx
T
neo773 814b43ca41 feat(server): derive email/calendar timelines from object relations (#21684)
Simplifies our existing implementation that uses three different GraphQL
endpoints to just one `getTimelineEventsFrom{Person, Company,
Opportunity}Id` to `getTimelineCalendarEventsFromObjectRecord`


/closes https://github.com/twentyhq/twenty/issues/19676

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21684?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-06-19 02:22:27 +02:00

116 lines
3.6 KiB
TypeScript

import { styled } from '@linaria/react';
import { ActivityList } from '@/activities/components/ActivityList';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { ComposeEmailButton } from '@/activities/emails/components/ComposeEmailButton';
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
import { EmptyInboxPlaceholder } from '@/activities/emails/components/EmptyInboxPlaceholder';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
import { getTimelineThreadsFromObjectRecord } from '@/activities/emails/graphql/queries/getTimelineThreadsFromObjectRecord';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { Trans } from '@lingui/react/macro';
import { H1Title, H1TitleFontColor } from 'twenty-ui/typography';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import {
type TimelineThread,
type TimelineThreadsWithTotal,
} from '~/generated/graphql';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing[6]};
height: 100%;
overflow: auto;
padding: ${themeCssVariables.spacing[6]} ${themeCssVariables.spacing[6]}
${themeCssVariables.spacing[2]};
`;
const StyledHeaderRow = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: ${themeCssVariables.spacing[4]};
`;
const StyledH1Title = styled(H1Title)`
display: flex;
gap: ${themeCssVariables.spacing[2]};
margin-bottom: 0;
`;
const StyledEmailCount = styled.span`
color: ${themeCssVariables.font.color.light};
`;
export const EmailsCard = () => {
const targetRecord = useTargetRecord();
const { data, firstQueryLoading, isFetchingMore, fetchMoreRecords } =
useCustomResolver<TimelineThreadsWithTotal>(
getTimelineThreadsFromObjectRecord,
'getTimelineThreadsFromObjectRecord',
'timelineThreads',
targetRecord,
TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
);
const { totalNumberOfThreads, timelineThreads } =
data?.getTimelineThreadsFromObjectRecord ?? {};
const hasMoreTimelineThreads =
timelineThreads && totalNumberOfThreads
? timelineThreads?.length < totalNumberOfThreads
: false;
const handleLastRowVisible = async () => {
if (hasMoreTimelineThreads) {
await fetchMoreRecords();
}
};
if (firstQueryLoading) {
return <SkeletonLoader />;
}
if (!firstQueryLoading && !timelineThreads?.length) {
return (
<StyledContainer>
<EmptyInboxPlaceholder />
</StyledContainer>
);
}
return (
<StyledContainer>
<Section>
<StyledHeaderRow>
<StyledH1Title
title={
<>
<Trans>Inbox</Trans>{' '}
<StyledEmailCount>{totalNumberOfThreads}</StyledEmailCount>
</>
}
fontColor={H1TitleFontColor.Primary}
/>
<ComposeEmailButton />
</StyledHeaderRow>
{!firstQueryLoading && (
<ActivityList>
{timelineThreads?.map((thread: TimelineThread) => (
<EmailThreadPreview key={thread.id} thread={thread} />
))}
</ActivityList>
)}
<CustomResolverFetchMoreLoader
loading={isFetchingMore || firstQueryLoading}
onLastRowVisible={handleLastRowVisible}
/>
</Section>
</StyledContainer>
);
};