Right drawer to edit records (#5551)

This PR introduces a new side panel to edit records and the ability to
minimize the side panel.

The goal is leverage this sidepanel to be able to create records while
being in another show page.

I'm opening the PR for feedback since it involved refactoring and
therefore already touches a lot of files, even though it was quick to
implement.

<img width="1503" alt="Screenshot 2024-05-23 at 17 41 37"
src="https://github.com/twentyhq/twenty/assets/6399865/6f17e7a8-f4e9-4eb4-b392-c756db7198ac">
This commit is contained in:
Félix Malfait
2024-06-03 17:15:05 +02:00
committed by GitHub
parent 8e8078d596
commit 09bfb617b2
61 changed files with 957 additions and 452 deletions

View File

@ -1,5 +1,4 @@
import styled from '@emotion/styled';
import { IconMail, Tag } from 'twenty-ui';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
@ -43,7 +42,6 @@ export const EmailThreadHeader = ({
}: EmailThreadHeaderProps) => {
return (
<StyledContainer>
<Tag Icon={IconMail} color="gray" text="Email" onClick={() => {}} />
<StyledHead>
<StyledHeading>{subject}</StyledHeading>
<StyledContent>

View File

@ -2,7 +2,7 @@ import { act, renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
import { viewableEmailThreadIdState } from '@/activities/emails/states/viewableEmailThreadIdState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
const viewableEmailThreadId = '1234';
@ -13,24 +13,22 @@ describe('useEmailThread', () => {
() => {
const emailThread = useEmailThread();
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
const viewableEmailThreadId = useRecoilValue(
viewableEmailThreadIdState,
);
const viewableRecordId = useRecoilValue(viewableRecordIdState);
return { ...emailThread, isRightDrawerOpen, viewableEmailThreadId };
return { ...emailThread, isRightDrawerOpen, viewableRecordId };
},
{ wrapper: RecoilRoot },
);
expect(result.current.isRightDrawerOpen).toBe(false);
expect(result.current.viewableEmailThreadId).toBeNull();
expect(result.current.viewableRecordId).toBeNull();
act(() => {
result.current.openEmailThread(viewableEmailThreadId);
});
expect(result.current.isRightDrawerOpen).toBe(true);
expect(result.current.viewableEmailThreadId).toBe(viewableEmailThreadId);
expect(result.current.viewableRecordId).toBe(viewableEmailThreadId);
});
it('should close email thread if trying to open the same thread id', () => {
@ -40,15 +38,16 @@ describe('useEmailThread', () => {
const [isRightDrawerOpen, setIsRightDrawerOpen] = useRecoilState(
isRightDrawerOpenState,
);
const [viewableEmailThreadId, setViewableEmailThreadId] =
useRecoilState(viewableEmailThreadIdState);
const [viewableRecordId, setViewableRecordId] = useRecoilState(
viewableRecordIdState,
);
return {
...emailThread,
isRightDrawerOpen,
viewableEmailThreadId,
viewableRecordId,
setIsRightDrawerOpen,
setViewableEmailThreadId,
setViewableRecordId,
};
},
{ wrapper: RecoilRoot },
@ -56,7 +55,7 @@ describe('useEmailThread', () => {
act(() => {
result.current.setIsRightDrawerOpen(true);
result.current.setViewableEmailThreadId(viewableEmailThreadId);
result.current.setViewableRecordId(viewableEmailThreadId);
});
act(() => {
@ -64,6 +63,6 @@ describe('useEmailThread', () => {
});
expect(result.current.isRightDrawerOpen).toBe(false);
expect(result.current.viewableEmailThreadId).toBeNull();
expect(result.current.viewableRecordId).toBeNull();
});
});

View File

@ -1,13 +1,13 @@
import { useRecoilCallback } from 'recoil';
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
import { viewableEmailThreadIdState } from '@/activities/emails/states/viewableEmailThreadIdState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
export const useEmailThread = () => {
const { closeRightDrawer } = useRightDrawer();
const openEmailThredRightDrawer = useOpenEmailThreadRightDrawer();
const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer();
const openEmailThread = useRecoilCallback(
({ snapshot, set }) =>
@ -17,19 +17,19 @@ export const useEmailThread = () => {
.getValue();
const viewableEmailThreadId = snapshot
.getLoadable(viewableEmailThreadIdState)
.getLoadable(viewableRecordIdState)
.getValue();
if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
set(viewableEmailThreadIdState, null);
set(viewableRecordIdState, null);
closeRightDrawer();
return;
}
openEmailThredRightDrawer();
set(viewableEmailThreadIdState, threadId);
openEmailThreadRightDrawer();
set(viewableRecordIdState, threadId);
},
[closeRightDrawer, openEmailThredRightDrawer],
[closeRightDrawer, openEmailThreadRightDrawer],
);
return { openEmailThread };

View File

@ -4,16 +4,16 @@ import gql from 'graphql-tag';
import { useRecoilValue } from 'recoil';
import { fetchAllThreadMessagesOperationSignatureFactory } from '@/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory';
import { viewableEmailThreadIdState } from '@/activities/emails/states/viewableEmailThreadIdState';
import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
export const useRightDrawerEmailThread = () => {
const viewableEmailThreadId = useRecoilValue(viewableEmailThreadIdState);
const viewableRecordId = useRecoilValue(viewableRecordIdState);
const apolloClient = useApolloClient();
const thread = apolloClient.readFragment({
id: `TimelineThread:${viewableEmailThreadId}`,
id: `TimelineThread:${viewableRecordId}`,
fragment: gql`
fragment timelineThread on TimelineThread {
id
@ -25,7 +25,7 @@ export const useRightDrawerEmailThread = () => {
const FETCH_ALL_MESSAGES_OPERATION_SIGNATURE =
fetchAllThreadMessagesOperationSignatureFactory({
messageThreadId: viewableEmailThreadId,
messageThreadId: viewableRecordId,
});
const {
@ -39,7 +39,7 @@ export const useRightDrawerEmailThread = () => {
FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.objectNameSingular,
orderBy: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.orderBy,
recordGqlFields: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.fields,
skip: !viewableEmailThreadId,
skip: !viewableRecordId,
});
const fetchMoreMessages = useCallback(() => {

View File

@ -1,6 +0,0 @@
import { createState } from 'twenty-ui';
export const viewableEmailThreadIdState = createState<string | null>({
key: 'viewableEmailThreadIdState',
defaultValue: null,
});