TWNTY-4203 - Improve Email Thread Visibility with Collapse/Expansion Rules (#5202)
### Description Improve Email Thread Visibility with Collapse/Expansion Rules ### Refs #4203 ### Demo https://github.com/twentyhq/twenty/assets/140154534/ece1d783-57ef-45c9-9895-3b4b0e02b9e2 Fixes #4203 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
6065201acd
commit
c946572fde
@ -31,14 +31,16 @@ type EmailThreadMessageProps = {
|
|||||||
body: string;
|
body: string;
|
||||||
sentAt: string;
|
sentAt: string;
|
||||||
participants: EmailThreadMessageParticipant[];
|
participants: EmailThreadMessageParticipant[];
|
||||||
|
isExpanded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmailThreadMessage = ({
|
export const EmailThreadMessage = ({
|
||||||
body,
|
body,
|
||||||
sentAt,
|
sentAt,
|
||||||
participants,
|
participants,
|
||||||
|
isExpanded = false,
|
||||||
}: EmailThreadMessageProps) => {
|
}: EmailThreadMessageProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(isExpanded);
|
||||||
|
|
||||||
const from = participants.find((participant) => participant.role === 'from');
|
const from = participants.find((participant) => participant.role === 'from');
|
||||||
const receivers = participants.filter(
|
const receivers = participants.filter(
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IconArrowsVertical } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
||||||
|
import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage';
|
||||||
|
import { Button } from '@/ui/input/button/components/Button';
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled.div`
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
padding: 16px 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const IntermediaryMessages = ({
|
||||||
|
messages,
|
||||||
|
}: {
|
||||||
|
messages: EmailThreadMessageType[];
|
||||||
|
}) => {
|
||||||
|
const [areMessagesOpen, setAreMessagesOpen] = useState(false);
|
||||||
|
|
||||||
|
if (messages.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return areMessagesOpen ? (
|
||||||
|
messages.map((message) => (
|
||||||
|
<EmailThreadMessage
|
||||||
|
key={message.id}
|
||||||
|
participants={message.messageParticipants}
|
||||||
|
body={message.text}
|
||||||
|
sentAt={message.receivedAt}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<StyledButtonContainer>
|
||||||
|
<Button
|
||||||
|
Icon={IconArrowsVertical}
|
||||||
|
title={`${messages.length} email${messages.length > 1 ? 's' : ''}`}
|
||||||
|
size="small"
|
||||||
|
onClick={() => setAreMessagesOpen(true)}
|
||||||
|
/>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,8 +5,10 @@ import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMore
|
|||||||
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
|
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
|
||||||
import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
|
import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
|
||||||
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
||||||
|
import { IntermediaryMessages } from '@/activities/emails/right-drawer/components/IntermediaryMessages';
|
||||||
import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread';
|
import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread';
|
||||||
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
|
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
|
||||||
|
import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage';
|
||||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
|
|
||||||
@ -20,6 +22,17 @@ const StyledContainer = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const getVisibleMessages = (messages: EmailThreadMessageType[]) =>
|
||||||
|
messages.filter(({ messageParticipants }) => {
|
||||||
|
const from = messageParticipants.find(
|
||||||
|
(participant) => participant.role === 'from',
|
||||||
|
);
|
||||||
|
const receivers = messageParticipants.filter(
|
||||||
|
(participant) => participant.role !== 'from',
|
||||||
|
);
|
||||||
|
return from && receivers.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
export const RightDrawerEmailThread = () => {
|
export const RightDrawerEmailThread = () => {
|
||||||
const { thread, messages, fetchMoreMessages, loading } =
|
const { thread, messages, fetchMoreMessages, loading } =
|
||||||
useRightDrawerEmailThread();
|
useRightDrawerEmailThread();
|
||||||
@ -44,6 +57,18 @@ export const RightDrawerEmailThread = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const visibleMessages = getVisibleMessages(messages);
|
||||||
|
const visibleMessagesCount = visibleMessages.length;
|
||||||
|
const is5OrMoreMessages = visibleMessagesCount >= 5;
|
||||||
|
const firstMessages = visibleMessages.slice(
|
||||||
|
0,
|
||||||
|
is5OrMoreMessages ? 2 : visibleMessagesCount - 1,
|
||||||
|
);
|
||||||
|
const intermediaryMessages = is5OrMoreMessages
|
||||||
|
? visibleMessages.slice(2, visibleMessagesCount - 1)
|
||||||
|
: [];
|
||||||
|
const lastMessage = visibleMessages[visibleMessagesCount - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<EmailThreadHeader
|
<EmailThreadHeader
|
||||||
@ -54,7 +79,7 @@ export const RightDrawerEmailThread = () => {
|
|||||||
<EmailLoader loadingText="Loading thread" />
|
<EmailLoader loadingText="Loading thread" />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{messages.map((message) => (
|
{firstMessages.map((message) => (
|
||||||
<EmailThreadMessage
|
<EmailThreadMessage
|
||||||
key={message.id}
|
key={message.id}
|
||||||
participants={message.messageParticipants}
|
participants={message.messageParticipants}
|
||||||
@ -62,6 +87,14 @@ export const RightDrawerEmailThread = () => {
|
|||||||
sentAt={message.receivedAt}
|
sentAt={message.receivedAt}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<IntermediaryMessages messages={intermediaryMessages} />
|
||||||
|
<EmailThreadMessage
|
||||||
|
key={lastMessage.id}
|
||||||
|
participants={lastMessage.messageParticipants}
|
||||||
|
body={lastMessage.text}
|
||||||
|
sentAt={lastMessage.receivedAt}
|
||||||
|
isExpanded
|
||||||
|
/>
|
||||||
<FetchMoreLoader
|
<FetchMoreLoader
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onLastRowVisible={fetchMoreMessages}
|
onLastRowVisible={fetchMoreMessages}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export {
|
|||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
|
IconArrowsVertical,
|
||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconArrowUpRight,
|
IconArrowUpRight,
|
||||||
IconAt,
|
IconAt,
|
||||||
|
|||||||
Reference in New Issue
Block a user